From d06106277f926740353a89a8d4b3f5f7dc244811 Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Wed, 10 Jan 2024 10:39:53 +0000 Subject: [PATCH 1/9] Changed a field that was being used like an enum, but with some iffy string == comparisons, into an actual enum. --- .../java/bluej/debugmgr/ExecutionEvent.java | 42 +++++++++++-------- .../java/bluej/debugmgr/codepad/CodePad.java | 6 +-- .../objectbench/ResultWatcherBase.java | 8 ++-- .../java/bluej/extensions2/DirectInvoker.java | 21 +++++----- .../event/InvocationFinishedEvent.java | 11 ++--- .../main/java/bluej/terminal/Terminal.java | 9 ++-- tools.properties | 6 +-- 7 files changed, 57 insertions(+), 46 deletions(-) diff --git a/bluej/src/main/java/bluej/debugmgr/ExecutionEvent.java b/bluej/src/main/java/bluej/debugmgr/ExecutionEvent.java index 674c767084..8e60320ef9 100644 --- a/bluej/src/main/java/bluej/debugmgr/ExecutionEvent.java +++ b/bluej/src/main/java/bluej/debugmgr/ExecutionEvent.java @@ -1,6 +1,6 @@ /* This file is part of the BlueJ program. - Copyright (C) 1999-2009,2010,2014 Michael Kolling and John Rosenberg + Copyright (C) 1999-2009,2010,2014,2024 Michael Kolling and John Rosenberg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -37,26 +37,35 @@ of the License, or (at your option) any later version. @OnThread(Tag.Any) public class ExecutionEvent { - /** - * The execution has finished normally; - */ - public static final String NORMAL_EXIT = "Normal exit"; + public static enum Result + { + /** + * The execution has finished normally; + */ + NORMAL_EXIT("Normal exit"), - /** - * The execution has finished due to an exception - */ - public static final String EXCEPTION_EXIT = "An exception occurred"; + /** + * The execution has finished due to an exception + */ + EXCEPTION_EXIT("An exception occurred"), - /** - * The execution has finished because the user has forcefully terminated it - */ - public static final String TERMINATED_EXIT = "User terminated"; + /** + * The execution has finished because the user has forcefully terminated it + */ + TERMINATED_EXIT("User terminated"); + + private final String text; + private Result(String text) + { + this.text = text; + } + } private String className, objectName; private String methodName; private JavaType[] signature; private String[] parameters; - private String result; + private Result result; private String command; private Package pkg; private DebuggerObject resultObject; // If there is a result object it goes here. @@ -109,11 +118,10 @@ public void setParameters (JavaType[] signature, String[] parameters) /** * Set the result of the execution. This should be one of: * NORMAL_EXIT - the execution terminated successfully - * FORCED_EXIT - System.exit() was called * EXCEPTION_EXIT - the execution failed due to an exception * TERMINATED_EXIT - the user terminated the VM before execution completed */ - public void setResult (String result) + public void setResult (Result result) { this.result = result; } @@ -198,7 +206,7 @@ public String[] getParameters() * EXCEPTION_EXIT - the execution failed due to an exception * TERMINATED_EXIT - the user terminated the VM before execution completed */ - public String getResult() + public Result getResult() { return result; } diff --git a/bluej/src/main/java/bluej/debugmgr/codepad/CodePad.java b/bluej/src/main/java/bluej/debugmgr/codepad/CodePad.java index 5a5f800e3d..cc3b723750 100644 --- a/bluej/src/main/java/bluej/debugmgr/codepad/CodePad.java +++ b/bluej/src/main/java/bluej/debugmgr/codepad/CodePad.java @@ -1,6 +1,6 @@ /* This file is part of the BlueJ program. - Copyright (C) 1999-2009,2010,2011,2012,2013,2016,2017,2018,2019,2020,2021,2022,2023 Michael Kolling and John Rosenberg + Copyright (C) 1999-2009,2010,2011,2012,2013,2016,2017,2018,2019,2020,2021,2022,2023,2024 Michael Kolling and John Rosenberg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -857,7 +857,7 @@ public void putResult(final DebuggerObject result, final String name, final Invo ExecutionEvent executionEvent = new ExecutionEvent(frame.getPackage()); executionEvent.setCommand(command); - executionEvent.setResult(ExecutionEvent.NORMAL_EXIT); + executionEvent.setResult(ExecutionEvent.Result.NORMAL_EXIT); executionEvent.setResultObject(result); BlueJEvent.raiseEvent(BlueJEvent.EXECUTION_RESULT, executionEvent); @@ -934,7 +934,7 @@ public void putException(ExceptionDescription exception, InvokerRecord ir) { ExecutionEvent executionEvent = new ExecutionEvent(frame.getPackage()); executionEvent.setCommand(command); - executionEvent.setResult(ExecutionEvent.EXCEPTION_EXIT); + executionEvent.setResult(ExecutionEvent.Result.EXCEPTION_EXIT); executionEvent.setException(exception); BlueJEvent.raiseEvent(BlueJEvent.EXECUTION_RESULT, executionEvent); updateInspectors(); diff --git a/bluej/src/main/java/bluej/debugmgr/objectbench/ResultWatcherBase.java b/bluej/src/main/java/bluej/debugmgr/objectbench/ResultWatcherBase.java index 8965b64f2b..4ebdc98db4 100644 --- a/bluej/src/main/java/bluej/debugmgr/objectbench/ResultWatcherBase.java +++ b/bluej/src/main/java/bluej/debugmgr/objectbench/ResultWatcherBase.java @@ -1,6 +1,6 @@ /* This file is part of the BlueJ program. - Copyright (C) 2017,2018,2019 Michael Kolling and John Rosenberg + Copyright (C) 2017,2018,2019,2024 Michael Kolling and John Rosenberg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -96,7 +96,7 @@ public void putResult(DebuggerObject result, String name, InvokerRecord ir) executionEvent.setMethodName(mv.getName()); } executionEvent.setParameters(method.getParamTypes(false), ir.getArgumentValues()); - executionEvent.setResult(ExecutionEvent.NORMAL_EXIT); + executionEvent.setResult(ExecutionEvent.Result.NORMAL_EXIT); executionEvent.setResultObject(result); BlueJEvent.raiseEvent(BlueJEvent.EXECUTION_RESULT, executionEvent); @@ -152,7 +152,7 @@ public void putException(ExceptionDescription exception, InvokerRecord ir) { ExecutionEvent executionEvent = new ExecutionEvent(pkg, className, objInstanceName); executionEvent.setParameters(method.getParamTypes(false), ir.getArgumentValues()); - executionEvent.setResult(ExecutionEvent.EXCEPTION_EXIT); + executionEvent.setResult(ExecutionEvent.Result.EXCEPTION_EXIT); executionEvent.setException(exception); BlueJEvent.raiseEvent(BlueJEvent.EXECUTION_RESULT, executionEvent); @@ -165,7 +165,7 @@ public void putVMTerminated(InvokerRecord ir, boolean terminatedByUserCode) { ExecutionEvent executionEvent = new ExecutionEvent(pkg, className, objInstanceName); executionEvent.setParameters(method.getParamTypes(false), ir.getArgumentValues()); - executionEvent.setResult(terminatedByUserCode ? ExecutionEvent.NORMAL_EXIT : ExecutionEvent.TERMINATED_EXIT); + executionEvent.setResult(terminatedByUserCode ? ExecutionEvent.Result.NORMAL_EXIT : ExecutionEvent.Result.TERMINATED_EXIT); BlueJEvent.raiseEvent(BlueJEvent.EXECUTION_RESULT, executionEvent); } } diff --git a/bluej/src/main/java/bluej/extensions2/DirectInvoker.java b/bluej/src/main/java/bluej/extensions2/DirectInvoker.java index c02c220059..2e79d91980 100644 --- a/bluej/src/main/java/bluej/extensions2/DirectInvoker.java +++ b/bluej/src/main/java/bluej/extensions2/DirectInvoker.java @@ -25,6 +25,7 @@ of the License, or (at your option) any later version. import bluej.debugger.DebuggerObject; import bluej.debugger.ExceptionDescription; import bluej.debugmgr.ExecutionEvent; +import bluej.debugmgr.ExecutionEvent.Result; import bluej.debugmgr.Invoker; import bluej.debugmgr.ResultWatcher; import bluej.debugmgr.objectbench.ObjectWrapper; @@ -106,7 +107,7 @@ OffThreadWaiter invokeConstructor(ConstructorView callable, Object[] args) DebuggerObject result = watcher.getResult(); Platform.runLater(() -> { - String resultType = watcher.getResultType(); + Result resultType = watcher.getResultType(); if (resultType != null) { ExecutionEvent ee = new ExecutionEvent(pkgFrame.getPackage(), callable.getClassName(), null); @@ -199,7 +200,7 @@ OffThreadWaiter invokeMethod(ObjectWrapper onThisObjectInstance, MethodView call DebuggerObject result = watcher.getResult(); Platform.runLater(() -> { - String resultType = watcher.getResultType(); + Result resultType = watcher.getResultType(); if (resultType != null) { ExecutionEvent ee = new ExecutionEvent(pkgFrame.getPackage(), callable.getClassName(), @@ -228,15 +229,15 @@ OffThreadWaiter invokeMethod(ObjectWrapper onThisObjectInstance, MethodView call private static void raiseEvent(ExecutionEvent event, CallableView callable, String [] argStrings, DirectResultWatcher watcher, DebuggerObject result) { - String resultType = watcher.getResultType(); + Result resultType = watcher.getResultType(); event.setParameters(callable.getParamTypes(false), argStrings); event.setResult(resultType); - if (resultType == ExecutionEvent.NORMAL_EXIT) { + if (resultType == ExecutionEvent.Result.NORMAL_EXIT) { event.setResultObject(result); event.setObjectName(watcher.getResultName()); } - else if (resultType == ExecutionEvent.EXCEPTION_EXIT) { + else if (resultType == ExecutionEvent.Result.EXCEPTION_EXIT) { event.setException(watcher.getException()); } @@ -364,7 +365,7 @@ class DirectResultWatcher implements ResultWatcher private boolean resultReady; @OnThread(value = Tag.Any, requireSynchronized = true) private boolean isFailed; - private String resultType; + private ExecutionEvent.Result resultType; private DebuggerObject result; private ExceptionDescription exception; @@ -454,7 +455,7 @@ public void beginExecution(InvokerRecord ir) public synchronized void putResult(DebuggerObject aResult, String anObjectName, InvokerRecord ir) { result = aResult; - resultType = ExecutionEvent.NORMAL_EXIT; + resultType = ExecutionEvent.Result.NORMAL_EXIT; resultName = anObjectName; resultReady = true; notifyAll(); @@ -482,7 +483,7 @@ public synchronized void putError(String error, InvokerRecord ir) public synchronized void putException(ExceptionDescription exception, InvokerRecord ir) { this.exception = exception; - resultType = ExecutionEvent.EXCEPTION_EXIT; + resultType = ExecutionEvent.Result.EXCEPTION_EXIT; putError(exception.getText(), ir); } @@ -492,7 +493,7 @@ public synchronized void putException(ExceptionDescription exception, InvokerRec */ public void putVMTerminated(InvokerRecord ir, boolean terminatedByUserCode) { - resultType = terminatedByUserCode ? ExecutionEvent.NORMAL_EXIT : ExecutionEvent.TERMINATED_EXIT; + resultType = terminatedByUserCode ? ExecutionEvent.Result.NORMAL_EXIT : ExecutionEvent.Result.TERMINATED_EXIT; putError("Terminated", ir); } @@ -527,7 +528,7 @@ public String getResultName() * ExecutionEvent.TERMINATED_EXIT if the user VM exited for any reason;
* null if compilation failure occurred. */ - public String getResultType() + public ExecutionEvent.Result getResultType() { return resultType; } diff --git a/bluej/src/main/java/bluej/extensions2/event/InvocationFinishedEvent.java b/bluej/src/main/java/bluej/extensions2/event/InvocationFinishedEvent.java index 82b3a29cf4..06654da661 100644 --- a/bluej/src/main/java/bluej/extensions2/event/InvocationFinishedEvent.java +++ b/bluej/src/main/java/bluej/extensions2/event/InvocationFinishedEvent.java @@ -1,6 +1,6 @@ /* This file is part of the BlueJ program. - Copyright (C) 1999-2009,2010,2014,2019,2021 Michael Kolling and John Rosenberg + Copyright (C) 1999-2009,2010,2014,2019,2021,2024 Michael Kolling and John Rosenberg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -25,6 +25,7 @@ of the License, or (at your option) any later version. import bluej.debugger.gentype.JavaPrimitiveType; import bluej.debugger.gentype.JavaType; import bluej.debugmgr.ExecutionEvent; +import bluej.debugmgr.ExecutionEvent.Result; import bluej.debugmgr.objectbench.ObjectWrapper; import bluej.extensions2.BPackage; import bluej.extensions2.ExtensionBridge; @@ -95,16 +96,16 @@ public static enum EventType public InvocationFinishedEvent(ExecutionEvent exevent) { terminationType = EventType.UNKNOWN_EXIT; - String resultType = exevent.getResult(); + Result resultType = exevent.getResult(); switch (resultType) { - case ExecutionEvent.NORMAL_EXIT: + case NORMAL_EXIT: terminationType = EventType.NORMAL_EXIT; break; - case ExecutionEvent.EXCEPTION_EXIT: + case EXCEPTION_EXIT: terminationType = EventType.EXCEPTION_EXIT; break; - case ExecutionEvent.TERMINATED_EXIT: + case TERMINATED_EXIT: terminationType = EventType.TERMINATED_EXIT; break; } diff --git a/bluej/src/main/java/bluej/terminal/Terminal.java b/bluej/src/main/java/bluej/terminal/Terminal.java index 7d818c28de..4497c15e89 100644 --- a/bluej/src/main/java/bluej/terminal/Terminal.java +++ b/bluej/src/main/java/bluej/terminal/Terminal.java @@ -31,6 +31,7 @@ of the License, or (at your option) any later version. import bluej.debugger.DebuggerObject; import bluej.debugger.DebuggerTerminal; import bluej.debugmgr.ExecutionEvent; +import bluej.debugmgr.ExecutionEvent.Result; import bluej.editor.base.LineDisplay; import bluej.editor.base.TextLine; import bluej.editor.base.TextLine.StyledSegment; @@ -539,9 +540,9 @@ private void methodResult(ExecutionEvent event) { if (recordMethodCalls.get()) { String result = null; - String resultType = event.getResult(); + Result resultType = event.getResult(); - if (resultType == ExecutionEvent.NORMAL_EXIT) { + if (resultType == ExecutionEvent.Result.NORMAL_EXIT) { DebuggerObject object = event.getResultObject(); if (object != null) { if (event.getClassName() != null && event.getMethodName() == null) { @@ -563,10 +564,10 @@ private void methodResult(ExecutionEvent event) } } } - else if (resultType == ExecutionEvent.EXCEPTION_EXIT) { + else if (resultType == ExecutionEvent.Result.EXCEPTION_EXIT) { result = " Exception occurred."; } - else if (resultType == ExecutionEvent.TERMINATED_EXIT) { + else if (resultType == ExecutionEvent.Result.TERMINATED_EXIT) { result = " VM terminated."; } diff --git a/tools.properties b/tools.properties index 077a28bd38..a78d594a47 100644 --- a/tools.properties +++ b/tools.properties @@ -1,4 +1,4 @@ -mingw_root= -wix_bin= +wix_bin=C:/Program Files (x86)/WiX Toolset v3.10/bin +mingw_root=C:/mingw64/mingw64 # If not on your PATH, supply the fully qualified path here: -ant_exe=ant +ant_exe=S:/apache-ant-1.9.4/bin/ant.bat From a535a720211b03c1a85426fa43bbbd728dc1c8cc Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Tue, 23 Jan 2024 15:19:34 +0000 Subject: [PATCH 2/9] Removed duplication in two very similar methods. --- bluej/src/main/java/bluej/BlueJEvent.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/bluej/src/main/java/bluej/BlueJEvent.java b/bluej/src/main/java/bluej/BlueJEvent.java index 801750ccea..016f12f60b 100644 --- a/bluej/src/main/java/bluej/BlueJEvent.java +++ b/bluej/src/main/java/bluej/BlueJEvent.java @@ -1,6 +1,6 @@ /* This file is part of the BlueJ program. - Copyright (C) 1999-2009,2010,2014,2016,2019 Michael Kolling and John Rosenberg + Copyright (C) 1999-2009,2010,2014,2016,2019,2024 Michael Kolling and John Rosenberg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License @@ -90,11 +90,7 @@ public class BlueJEvent @OnThread(Tag.FXPlatform) public static void raiseEvent(int eventId, Object arg) { - Object[] listenersCopy = listeners.toArray(); - for (int i = listenersCopy.length - 1; i >= 0; i--) { - BlueJEventListener listener = (BlueJEventListener) listenersCopy[i]; - listener.blueJEvent(eventId, arg, null); - } + raiseEvent(eventId, arg, null); } /** From 621898bdce7247bf96a3d5c2e0f8074fcea875a7 Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Wed, 24 Jan 2024 13:41:42 +0000 Subject: [PATCH 3/9] Refactored the main part of getLeftEdgeX of FlowEditorPane into a method in LineDisplay, ready for re-use. --- .../java/bluej/editor/base/LineDisplay.java | 41 +++++++++++++++++++ .../bluej/editor/flow/FlowEditorPane.java | 35 +--------------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/bluej/src/main/java/bluej/editor/base/LineDisplay.java b/bluej/src/main/java/bluej/editor/base/LineDisplay.java index 5dd6aeb6ed..f05ed6a4dd 100644 --- a/bluej/src/main/java/bluej/editor/base/LineDisplay.java +++ b/bluej/src/main/java/bluej/editor/base/LineDisplay.java @@ -24,6 +24,7 @@ of the License, or (at your option) any later version. import bluej.editor.base.BaseEditorPane.BaseEditorPaneListener; import bluej.editor.base.TextLine.StyledSegment; import bluej.editor.flow.Document; +import bluej.prefmgr.PrefMgr; import bluej.utility.Debug; import bluej.utility.javafx.FXFunction; import com.google.common.cache.Cache; @@ -34,7 +35,10 @@ of the License, or (at your option) any later version. import javafx.geometry.Point2D; import javafx.scene.Scene; import javafx.scene.shape.Path; +import javafx.scene.shape.PathElement; +import javafx.scene.text.Font; import javafx.scene.text.HitInfo; +import javafx.scene.text.Text; import threadchecker.OnThread; import threadchecker.Tag; @@ -45,8 +49,10 @@ of the License, or (at your option) any later version. import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; /** * A class to handle the display of the set of visible lines in an editor window. @@ -90,6 +96,41 @@ public LineDisplay(DoubleExpression horizScrollProperty, StringExpression fontCS this.showLeftMargin = showLeftMargin; } + /** + * Gets the X pixel position of the left edge of the given position + * @param lineIndex The line index (0 based in document, using same index as getVisibleLine) + * @param posInLine The column position in the line + * @return The position if we could calculate it, or empty if not (because we need a layout first) + */ + @OnThread(Tag.FXPlatform) + public Optional calculateLeftEdgeX(int lineIndex, int posInLine) + { + if (isLineVisible(lineIndex)) + { + TextLine line = getVisibleLine(lineIndex).textLine; + // If the line needs layout, the positions won't be accurate: + if (line.isNeedsLayout()) + return Optional.empty(); + // Sometimes, it seems that the line can have the CSS for the font, + // and claim it doesn't need layout, but the font on the Text items + // has not actually been switched to the right font. In this case + // the positions will be inaccurate, so we should not calculate: + Font curFont = line.getChildren().stream().flatMap(n -> n instanceof Text ? Stream.of(((Text)n).getFont()) : Stream.empty()).findFirst().orElse(null); + if (curFont != null && !curFont.getFamily().equals(PrefMgr.getEditorFontFamily())) + return Optional.empty(); + PathElement[] elements = line.caretShape(posInLine, true); + Path path = new Path(elements); + Bounds bounds = path.getBoundsInLocal(); + // If the bounds are at left edge but char is not, might not have laid out yet: + if (posInLine > 0 && bounds.getMaxX() < 2.0) + { + return Optional.empty(); + } + return Optional.of((bounds.getMinX() + bounds.getMaxX()) / 2.0); + } + return Optional.empty(); + } + /** * Gets the visible line object corresponding to the given document line. * Throws an exception if that line is not visible (you should check first via isLineVisible). diff --git a/bluej/src/main/java/bluej/editor/flow/FlowEditorPane.java b/bluej/src/main/java/bluej/editor/flow/FlowEditorPane.java index 544ff7c7c0..9ff3585f13 100644 --- a/bluej/src/main/java/bluej/editor/flow/FlowEditorPane.java +++ b/bluej/src/main/java/bluej/editor/flow/FlowEditorPane.java @@ -33,28 +33,21 @@ of the License, or (at your option) any later version. import bluej.editor.base.MarginAndTextLine.MarginDisplay; import bluej.editor.base.TextLine.HighlightType; import bluej.editor.base.TextLine.StyledSegment; -import bluej.prefmgr.PrefMgr; import bluej.utility.javafx.JavaFXUtil; -import javafx.collections.ObservableList; import javafx.geometry.Bounds; import javafx.geometry.Point2D; import javafx.geometry.Rectangle2D; import javafx.scene.AccessibleAttribute; -import javafx.scene.Node; import javafx.scene.control.IndexRange; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; -import javafx.scene.input.ScrollEvent; import javafx.scene.layout.BackgroundFill; -import javafx.scene.layout.Region; import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; import javafx.scene.shape.PathElement; -import javafx.scene.text.Font; -import javafx.scene.text.Text; import threadchecker.OnThread; import threadchecker.Tag; @@ -70,7 +63,6 @@ of the License, or (at your option) any later version. import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.stream.Stream; /** * A FlowEditorPane is a component with (optional) horizontal and vertical scroll bars. @@ -515,31 +507,8 @@ public Optional getLeftEdgeX(int leftOfCharIndex) static Optional getLeftEdgeX(int leftOfCharIndex, Document document, LineDisplay lineDisplay) { int lineIndex = document.getLineFromPosition(leftOfCharIndex); - if (lineDisplay.isLineVisible(lineIndex)) - { - TextLine line = lineDisplay.getVisibleLine(lineIndex).textLine; - // If the line needs layout, the positions won't be accurate: - if (line.isNeedsLayout()) - return Optional.empty(); - // Sometimes, it seems that the line can have the CSS for the font, - // and claim it doesn't need layout, but the font on the Text items - // has not actually been switched to the right font. In this case - // the positions will be inaccurate, so we should not calculate: - Font curFont = line.getChildren().stream().flatMap(n -> n instanceof Text ? Stream.of(((Text)n).getFont()) : Stream.empty()).findFirst().orElse(null); - if (curFont != null && !curFont.getFamily().equals(PrefMgr.getEditorFontFamily())) - return Optional.empty(); - int posInLine = leftOfCharIndex - document.getLineStart(lineIndex); - PathElement[] elements = line.caretShape(posInLine, true); - Path path = new Path(elements); - Bounds bounds = path.getBoundsInLocal(); - // If the bounds are at left edge but char is not, might not have laid out yet: - if (posInLine > 0 && bounds.getMaxX() < 2.0) - { - return Optional.empty(); - } - return Optional.of((bounds.getMinX() + bounds.getMaxX()) / 2.0); - } - return Optional.empty(); + int posInLine = leftOfCharIndex - document.getLineStart(lineIndex); + return lineDisplay.calculateLeftEdgeX(lineIndex, posInLine); } public Optional getCaretBoundsOnScreen(int position) From 903276dd9ef9b7e63ac08b6add445eecdf532a83 Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Wed, 24 Jan 2024 13:47:43 +0000 Subject: [PATCH 4/9] Added a feature to the terminal where it shows section boundaries (like our scope highlighting) around sections of output from different methods. --- .../main/java/bluej/terminal/Terminal.java | 27 ++- .../java/bluej/terminal/TerminalTextPane.java | 193 +++++++++++++++++- 2 files changed, 217 insertions(+), 3 deletions(-) diff --git a/bluej/src/main/java/bluej/terminal/Terminal.java b/bluej/src/main/java/bluej/terminal/Terminal.java index 4497c15e89..25562b284b 100644 --- a/bluej/src/main/java/bluej/terminal/Terminal.java +++ b/bluej/src/main/java/bluej/terminal/Terminal.java @@ -50,7 +50,6 @@ of the License, or (at your option) any later version. import bluej.utility.Utility; import bluej.utility.javafx.JavaFXUtil; import javafx.application.Platform; -import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ReadOnlyDoubleWrapper; import javafx.beans.property.ReadOnlyStringWrapper; @@ -89,6 +88,7 @@ of the License, or (at your option) any later version. import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -143,7 +143,7 @@ public final class Terminal private final BooleanProperty showingProperty = new SimpleBooleanProperty(false); @OnThread(Tag.Any) private final Reader in = new TerminalReader(); - @OnThread(Tag.Any) private final Writer out = new TerminalWriter(false); + @OnThread(Tag.Any) private final TerminalWriter out = new TerminalWriter(false); @OnThread(Tag.Any) private final Writer err = new TerminalWriter(true); private Stage window; @@ -177,6 +177,7 @@ else if (errorText != null) } }; text.getStyleClass().add("terminal-output"); + text.styleProperty().bind(PrefMgr.getEditorFontCSS(PrefMgr.FontCSS.EDITOR_SIZE_AND_FAMILY)); text.addSelectionListener((caret, anchor) -> { if (errorText != null && errorText.getCaretEditorPosition().getPosition() != errorText.getAnchorEditorPosition().getPosition()) { @@ -509,6 +510,7 @@ private void methodCall(String callString) if(clearOnMethodCall.get()) { clear(); } + text.markNewSection(); if(recordMethodCalls.get()) { text.append(new StyledSegment(STDOUT_METHOD_RECORDING, callString + "\n")); } @@ -724,6 +726,24 @@ public void blueJEvent(int eventId, Object arg, Project prj) } else if (eventId == BlueJEvent.EXECUTION_RESULT) { methodResult((ExecutionEvent) arg); + endSectionWhenNoPendingWrites(); + } + } + + /** + * End the output section, if there are no writes pending to stdout. + * Otherwise, reschedule ourselves (to end the section once no more writes are pending) + */ + @OnThread(Tag.FXPlatform) + private void endSectionWhenNoPendingWrites() + { + if (out.pendingWrites.get() > 0) + { + JavaFXUtil.runAfterCurrent(() -> endSectionWhenNoPendingWrites()); + } + else + { + text.endSection(); } } @@ -889,6 +909,7 @@ public void close() { } private class TerminalWriter extends Writer { private boolean isErrorOut; + private AtomicInteger pendingWrites = new AtomicInteger(0); TerminalWriter(boolean isError) { @@ -899,6 +920,7 @@ private class TerminalWriter extends Writer public void write(final char[] cbuf, final int off, final int len) { try { + pendingWrites.incrementAndGet(); // We use a wait so that terminal output is limited to // the processing speed of the event queue. This means the UI // will still respond to user input even if the output is really @@ -922,6 +944,7 @@ public void write(final char[] cbuf, final int off, final int len) finally { written.complete(true); + pendingWrites.decrementAndGet(); } }); // Timeout in case something goes wrong with the printing: diff --git a/bluej/src/main/java/bluej/terminal/TerminalTextPane.java b/bluej/src/main/java/bluej/terminal/TerminalTextPane.java index 9a875bdbef..19468d92eb 100644 --- a/bluej/src/main/java/bluej/terminal/TerminalTextPane.java +++ b/bluej/src/main/java/bluej/terminal/TerminalTextPane.java @@ -22,6 +22,7 @@ of the License, or (at your option) any later version. package bluej.terminal; import bluej.Config; +import bluej.editor.base.BackgroundItem; import bluej.editor.base.BaseEditorPane; import bluej.editor.base.EditorPosition; import bluej.editor.base.TextLine.StyledSegment; @@ -29,6 +30,7 @@ of the License, or (at your option) any later version. import bluej.utility.javafx.JavaFXUtil; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import javafx.geometry.Insets; import javafx.scene.Cursor; import javafx.scene.control.ContextMenu; import javafx.scene.input.Clipboard; @@ -37,12 +39,18 @@ of the License, or (at your option) any later version. import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.input.ScrollEvent; +import javafx.scene.layout.BackgroundFill; +import javafx.scene.layout.CornerRadii; +import javafx.scene.paint.Color; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.ListIterator; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; /** @@ -65,6 +73,99 @@ public abstract class TerminalTextPane extends BaseEditorPane private Pos caretPos = new Pos(0, 0, 0); private Pos anchorPos = new Pos(0, 0, 0); + // The line may be negative in some circumstances (see Section, below). + // In this case, column should be ignored. If the column is Integer.MAX_VALUE + // it means to take the whole line as included, no matter how long. + // Both column and line are zero-based. + record TerminalPos(int line, int column) + { + public TerminalPos subtractLines(int linesToSubtract) + { + return new TerminalPos(line - linesToSubtract, column); + } + } + + // endLine is negative if ongoing. startLine is negative if the start has scrolled off the top + // It is possible for them to both be negative if the section is very long and ongoing. + // All values of the pos are inclusive. The columns should be ignored if the line is negative. + record Section(TerminalPos start, TerminalPos end) {} + + private final ArrayList
currentSections = new ArrayList<>(); + + // Get the current end position of the content as a start position + // This is different to getCurEnd() because it does not do any extra + // calculation about trailing newlines. + private TerminalPos getCurStart() + { + if (content.isEmpty()) + { + return new TerminalPos(0, 0); + } + else + { + return new TerminalPos(content.size() - 1, content.get(content.size() - 1).getText().length()); + } + } + + // Get the current end position of the content as an end position + // This is different to getCurStart() because if the content ends if a newline + // (the last content is a blank line), we take the end position as being the end + // of the previous line, not the start of the new line (if there's no content on that line) + private TerminalPos getCurEnd() + { + // If the final line is empty, we count the current end as the whole of the line before + if (content.isEmpty()) + { + return new TerminalPos(0, 0); + } + else + { + String lastLine = content.get(content.size() - 1).getText(); + if (lastLine.isEmpty()) + { + return new TerminalPos(content.size() - 2, Integer.MAX_VALUE); + } + else + { + return new TerminalPos(content.size() - 1, lastLine.length()); + } + } + } + + public void markNewSection() + { + if (!currentSections.isEmpty()) + { + int lastLineIndex = currentSections.size() - 1; + // If current last section was marked as ongoing, finish it: + if (currentSections.get(lastLineIndex).end.line < 0) + { + currentSections.set(lastLineIndex, new Section(currentSections.get(lastLineIndex).start, getCurEnd())); + } + } + currentSections.add(new Section(getCurStart(), new TerminalPos(-1, 0))); + } + + // End the current section of content. + public void endSection() + { + if (!currentSections.isEmpty()) + { + // If the content is empty we get rid of all sections: + if (content.isEmpty()) + { + currentSections.clear(); + } + else + { + int lastSection = currentSections.size() - 1; + currentSections.set(lastSection, new Section(currentSections.get(lastSection).start, getCurEnd())); + updateRender(false); + } + } + } + + public TerminalTextPane() { super(false, new BaseEditorPaneListener() @@ -210,7 +311,82 @@ protected void keyPressed(KeyEvent event) public abstract void focusPrevious(); public abstract void focusNext(); - + + @Override + protected void updateRender(boolean ensureCaretVisible) + { + super.updateRender(ensureCaretVisible); + // Recalculate all sections in the terminal: + HashMap> map = new HashMap<>(); + boolean reschedule = false; + for (int i = 0; i < content.size(); i++) + { + for (Section s : currentSections) + { + final double singleRadius = 5; + // All are specific to this section, on this line: + double topRadius = 0, bottomRadius = 0; + // Top or bottom inset of 0 basically means "don't draw the grey line": + double topInset = 0, bottomInset = 0; + // Default is whole width: + double leftInset = 0, rightInset = getTextDisplayWidth()-1.0; + + // Each section could begin and/or end on the current line + // If neither, it may be ongoing through this line, or just not overlapping at all. + // So there's quite a few circumstances to consider. We start with beginning: + if (s.start.line == i) + { + topRadius = singleRadius; + topInset = 1; + if (s.start.column >= 0 && s.start.column < content.get(i).getText().length()) + { + Optional edge = lineDisplay.calculateLeftEdgeX(i, s.start.column); + reschedule |= edge.isEmpty(); + leftInset = edge.orElse(leftInset); + } + if (s.end.line == i) + { + bottomRadius = singleRadius; + bottomInset = 1; + if (s.end.column >= 0 && s.end.column <= content.get(i).getText().length()) + { + Optional edge = lineDisplay.calculateLeftEdgeX(i, s.end.column); + reschedule |= edge.isEmpty(); + rightInset = edge.orElse(rightInset); + } + } + } + else if (s.end.line == i) + { + bottomRadius = singleRadius; + bottomInset = 1; + if (s.end.column >= 0 && s.end.column <= content.get(i).getText().length()) + { + Optional edge = lineDisplay.calculateLeftEdgeX(i, s.end.column); + reschedule |= edge.isEmpty(); + rightInset = edge.orElse(rightInset); + } + } + else if (!(s.start.line < i && (s.end.line == -1 || s.end.line > i))) + { + // Does not overlap this line at all: + continue; + } + + CornerRadii radii = new CornerRadii(topRadius, topRadius, bottomRadius, bottomRadius, false); + Insets bodyInsets = new Insets(topInset, 1, bottomInset, 1); + map.computeIfAbsent(i, _i -> new ArrayList<>()).add(new BackgroundItem(leftInset, rightInset - leftInset, + new BackgroundFill(Color.LIGHTGRAY, radii, null), + new BackgroundFill(Color.WHITE, radii, bodyInsets))); + } + } + lineDisplay.applyScopeBackgrounds(map); + if (reschedule) + { + JavaFXUtil.runAfterNextLayout(getScene(), () -> updateRender(false)); + } + } + public final void requestFocusAndShowCaret() { requestFocus(); @@ -318,6 +494,20 @@ public void trimToMostRecentNLines(int numLines) anchorPos = makePosition( newAnchorLine, Math.min(anchorPos.getColumn(), getLineLength(newAnchorLine)) ); + // Adjust line offset of any current lines in the display to match what we've just changed: + for (ListIterator
iterator = currentSections.listIterator(); iterator.hasNext(); ) + { + Section s = iterator.next(); + // Check for scrolling off the top entirely, if it's not currently ongoing: + if (s.end.line > 0 && s.end.line < linesToSubtract) + { + iterator.remove(); + } + else + { + iterator.set(new Section(s.start.subtractLines(linesToSubtract), s.end.subtractLines(linesToSubtract))); + } + } updateRender(false); } } @@ -354,6 +544,7 @@ public void clear() caretPos = new Pos(0, 0, 0); anchorPos = new Pos(0, 0, 0); setContent(Collections.singletonList(new ContentLine(new ArrayList<>()))); + currentSections.clear(); } /** From 7064c8e55c462d0ce177d236736db59b549727df Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Wed, 24 Jan 2024 13:48:17 +0000 Subject: [PATCH 5/9] Update some copyright years. --- bluej/src/main/java/bluej/editor/flow/FlowEditorPane.java | 2 +- bluej/src/main/java/bluej/terminal/TerminalTextPane.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bluej/src/main/java/bluej/editor/flow/FlowEditorPane.java b/bluej/src/main/java/bluej/editor/flow/FlowEditorPane.java index 9ff3585f13..762317b49d 100644 --- a/bluej/src/main/java/bluej/editor/flow/FlowEditorPane.java +++ b/bluej/src/main/java/bluej/editor/flow/FlowEditorPane.java @@ -1,6 +1,6 @@ /* This file is part of the BlueJ program. - Copyright (C) 2019,2020,2021,2022 Michael Kolling and John Rosenberg + Copyright (C) 2019,2020,2021,2022,2024 Michael Kolling and John Rosenberg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License diff --git a/bluej/src/main/java/bluej/terminal/TerminalTextPane.java b/bluej/src/main/java/bluej/terminal/TerminalTextPane.java index 19468d92eb..97cee2d69b 100644 --- a/bluej/src/main/java/bluej/terminal/TerminalTextPane.java +++ b/bluej/src/main/java/bluej/terminal/TerminalTextPane.java @@ -1,6 +1,6 @@ /* This file is part of the BlueJ program. - Copyright (C) 2021,2022,2023 Michael Kolling and John Rosenberg + Copyright (C) 2021,2022,2023,2024 Michael Kolling and John Rosenberg This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License From 7d0364e84c52623b076a9d5af85c0ab7f2ca9dda Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Wed, 24 Jan 2024 13:51:11 +0000 Subject: [PATCH 6/9] Clear the stderr pane in the terminal on each constructor or method call. --- bluej/src/main/java/bluej/terminal/Terminal.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bluej/src/main/java/bluej/terminal/Terminal.java b/bluej/src/main/java/bluej/terminal/Terminal.java index 25562b284b..695e057641 100644 --- a/bluej/src/main/java/bluej/terminal/Terminal.java +++ b/bluej/src/main/java/bluej/terminal/Terminal.java @@ -511,6 +511,8 @@ private void methodCall(String callString) clear(); } text.markNewSection(); + if (errorText != null) + errorText.clear(); if(recordMethodCalls.get()) { text.append(new StyledSegment(STDOUT_METHOD_RECORDING, callString + "\n")); } @@ -531,6 +533,9 @@ private void constructorCall(InvokerRecord ir) if(clearOnMethodCall.get()) { clear(); } + text.markNewSection(); + if (errorText != null) + errorText.clear(); if(recordMethodCalls.get()) { String callString = ir.getResultTypeString() + " " + ir.getResultName() + " = " + ir.toExpression() + ";"; text.append(new StyledSegment(STDOUT_METHOD_RECORDING, callString + "\n")); From 1986e0d06ac3abdebec49b084b3da4cf84492505 Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Wed, 24 Jan 2024 14:22:13 +0000 Subject: [PATCH 7/9] Added a tooltip in the terminal which shows which method that output was from. --- .../main/java/bluej/terminal/Terminal.java | 6 +- .../java/bluej/terminal/TerminalTextPane.java | 72 ++++++++++++++++--- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/bluej/src/main/java/bluej/terminal/Terminal.java b/bluej/src/main/java/bluej/terminal/Terminal.java index 695e057641..88b001161f 100644 --- a/bluej/src/main/java/bluej/terminal/Terminal.java +++ b/bluej/src/main/java/bluej/terminal/Terminal.java @@ -510,7 +510,7 @@ private void methodCall(String callString) if(clearOnMethodCall.get()) { clear(); } - text.markNewSection(); + text.markNewSection(callString); if (errorText != null) errorText.clear(); if(recordMethodCalls.get()) { @@ -533,11 +533,11 @@ private void constructorCall(InvokerRecord ir) if(clearOnMethodCall.get()) { clear(); } - text.markNewSection(); + String callString = ir.getResultTypeString() + " " + ir.getResultName() + " = " + ir.toExpression() + ";"; + text.markNewSection(callString); if (errorText != null) errorText.clear(); if(recordMethodCalls.get()) { - String callString = ir.getResultTypeString() + " " + ir.getResultName() + " = " + ir.toExpression() + ";"; text.append(new StyledSegment(STDOUT_METHOD_RECORDING, callString + "\n")); } newMethodCall = true; diff --git a/bluej/src/main/java/bluej/terminal/TerminalTextPane.java b/bluej/src/main/java/bluej/terminal/TerminalTextPane.java index 97cee2d69b..05410ca29f 100644 --- a/bluej/src/main/java/bluej/terminal/TerminalTextPane.java +++ b/bluej/src/main/java/bluej/terminal/TerminalTextPane.java @@ -26,6 +26,7 @@ of the License, or (at your option) any later version. import bluej.editor.base.BaseEditorPane; import bluej.editor.base.EditorPosition; import bluej.editor.base.TextLine.StyledSegment; +import bluej.utility.Debug; import bluej.utility.javafx.FXPlatformRunnable; import bluej.utility.javafx.JavaFXUtil; import com.google.common.collect.ImmutableList; @@ -33,6 +34,7 @@ of the License, or (at your option) any later version. import javafx.geometry.Insets; import javafx.scene.Cursor; import javafx.scene.control.ContextMenu; +import javafx.scene.control.Tooltip; import javafx.scene.input.Clipboard; import javafx.scene.input.DataFormat; import javafx.scene.input.KeyEvent; @@ -43,6 +45,7 @@ of the License, or (at your option) any later version. import javafx.scene.layout.CornerRadii; import javafx.scene.paint.Color; +import javax.tools.Tool; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -67,12 +70,15 @@ public abstract class TerminalTextPane extends BaseEditorPane private final ArrayList content = new ArrayList<>(); // Listeners to call when the content of the pane changes private final ArrayList contentListeners = new ArrayList<>(); - + // The position of the caret and the anchor. The Pos class is immutable so the instance // will be swapped out as a whole if it changes. private Pos caretPos = new Pos(0, 0, 0); private Pos anchorPos = new Pos(0, 0, 0); - + + private final Tooltip tooltip = new Tooltip(); + private EditorPosition lastMousePos; + // The line may be negative in some circumstances (see Section, below). // In this case, column should be ignored. If the column is Integer.MAX_VALUE // it means to take the whole line as included, no matter how long. @@ -88,7 +94,7 @@ public TerminalPos subtractLines(int linesToSubtract) // endLine is negative if ongoing. startLine is negative if the start has scrolled off the top // It is possible for them to both be negative if the section is very long and ongoing. // All values of the pos are inclusive. The columns should be ignored if the line is negative. - record Section(TerminalPos start, TerminalPos end) {} + record Section(TerminalPos start, TerminalPos end, String title) {} private final ArrayList
currentSections = new ArrayList<>(); @@ -132,18 +138,19 @@ private TerminalPos getCurEnd() } } - public void markNewSection() + public void markNewSection(String sectionTitle) { if (!currentSections.isEmpty()) { int lastLineIndex = currentSections.size() - 1; // If current last section was marked as ongoing, finish it: - if (currentSections.get(lastLineIndex).end.line < 0) + Section last = currentSections.get(lastLineIndex); + if (last.end.line < 0) { - currentSections.set(lastLineIndex, new Section(currentSections.get(lastLineIndex).start, getCurEnd())); + currentSections.set(lastLineIndex, new Section(last.start, getCurEnd(), last.title)); } } - currentSections.add(new Section(getCurStart(), new TerminalPos(-1, 0))); + currentSections.add(new Section(getCurStart(), new TerminalPos(-1, 0), sectionTitle)); } // End the current section of content. @@ -159,7 +166,8 @@ public void endSection() else { int lastSection = currentSections.size() - 1; - currentSections.set(lastSection, new Section(currentSections.get(lastSection).start, getCurEnd())); + Section last = currentSections.get(lastSection); + currentSections.set(lastSection, new Section(last.start, getCurEnd(), last.title)); updateRender(false); } } @@ -191,7 +199,53 @@ public void scrollEventOnTextLine(ScrollEvent e, BaseEditorPane editorPane) }); // Set the content to be empty on construction: clear(); + + Tooltip.install(this, tooltip); + + tooltip.setOnShowing(e -> { + if (lastMousePos != null) + { + for (Section s : currentSections) + { + if (s.start.line > lastMousePos.getLine()) + continue; // We are before its start line + if (s.end.line >= 0 && s.end.line < lastMousePos.getLine()) + continue; // We are after its end line + if (s.start.line == lastMousePos.getLine() && lastMousePos.getColumn() < s.start.column) + continue; // We are on the start line, but before its start column + if (s.end.line == lastMousePos.getLine() && lastMousePos.getColumn() > s.end.column) + continue; // We are on the end line, but after its end column + // We're inside! + tooltip.setText(s.title); + return; + } + tooltip.setText(""); + } + }); + // Because the tooltip is for the whole node, it will show even while you mouse around. + // To avoid that, we listen for mouse movements and hide it: + addEventFilter(MouseEvent.MOUSE_MOVED, e -> { + lastMousePos = getCaretPositionForMouseEvent(e).orElse(null); + // If we hide it, it will not show again until you mouse around for a while + // So instead we uninstall and reinstall, which allows reshowing sooner: + if (tooltip.isShowing()) + { + Tooltip.uninstall(this, tooltip); + Tooltip.install(this, tooltip); + } + }); + addEventFilter(ScrollEvent.ANY, e -> { + // Hide tooltip and invalidate mouse position: + lastMousePos = null; + if (tooltip.isShowing()) + { + Tooltip.uninstall(this, tooltip); + Tooltip.install(this, tooltip); + } + }); + } + @Override protected void keyPressed(KeyEvent event) @@ -505,7 +559,7 @@ public void trimToMostRecentNLines(int numLines) } else { - iterator.set(new Section(s.start.subtractLines(linesToSubtract), s.end.subtractLines(linesToSubtract))); + iterator.set(new Section(s.start.subtractLines(linesToSubtract), s.end.subtractLines(linesToSubtract), s.title)); } } updateRender(false); From 4c6257622bea5562cb97c1e5835b4394656657cf Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Wed, 24 Jan 2024 14:25:41 +0000 Subject: [PATCH 8/9] Revert accidental commit to a properties file. --- tools.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools.properties b/tools.properties index a78d594a47..077a28bd38 100644 --- a/tools.properties +++ b/tools.properties @@ -1,4 +1,4 @@ -wix_bin=C:/Program Files (x86)/WiX Toolset v3.10/bin -mingw_root=C:/mingw64/mingw64 +mingw_root= +wix_bin= # If not on your PATH, supply the fully qualified path here: -ant_exe=S:/apache-ant-1.9.4/bin/ant.bat +ant_exe=ant From 9a03133049ae6bb2ac47cd015e0cbc3093e95c91 Mon Sep 17 00:00:00 2001 From: Neil Brown Date: Wed, 24 Jan 2024 16:37:22 +0000 Subject: [PATCH 9/9] A few code tidy-ups based on code review. --- bluej/src/main/java/bluej/terminal/Terminal.java | 2 ++ bluej/src/main/java/bluej/terminal/TerminalTextPane.java | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bluej/src/main/java/bluej/terminal/Terminal.java b/bluej/src/main/java/bluej/terminal/Terminal.java index 88b001161f..eff0b61557 100644 --- a/bluej/src/main/java/bluej/terminal/Terminal.java +++ b/bluej/src/main/java/bluej/terminal/Terminal.java @@ -914,6 +914,8 @@ public void close() { } private class TerminalWriter extends Writer { private boolean isErrorOut; + // The number of pending writes (content that has been received on the + // IO thread, but not yet displayed in the terminal via the FX thread) private AtomicInteger pendingWrites = new AtomicInteger(0); TerminalWriter(boolean isError) diff --git a/bluej/src/main/java/bluej/terminal/TerminalTextPane.java b/bluej/src/main/java/bluej/terminal/TerminalTextPane.java index 05410ca29f..3cba973c0f 100644 --- a/bluej/src/main/java/bluej/terminal/TerminalTextPane.java +++ b/bluej/src/main/java/bluej/terminal/TerminalTextPane.java @@ -79,6 +79,7 @@ public abstract class TerminalTextPane extends BaseEditorPane private final Tooltip tooltip = new Tooltip(); private EditorPosition lastMousePos; + // A record holding a location in the terminal window. // The line may be negative in some circumstances (see Section, below). // In this case, column should be ignored. If the column is Integer.MAX_VALUE // it means to take the whole line as included, no matter how long. @@ -114,7 +115,7 @@ private TerminalPos getCurStart() } // Get the current end position of the content as an end position - // This is different to getCurStart() because if the content ends if a newline + // This is different to getCurStart() because if the content ends in a newline // (the last content is a blank line), we take the end position as being the end // of the previous line, not the start of the new line (if there's no content on that line) private TerminalPos getCurEnd()