package bluej.terminal;
import bluej.BlueJEvent;
import bluej.BlueJEventListener;
import bluej.BlueJTheme;
import bluej.Config;
import bluej.collect.DataCollector;
import bluej.debugger.Debugger;
import bluej.debugger.DebuggerField;
import bluej.debugger.DebuggerObject;
import bluej.debugger.DebuggerTerminal;
import bluej.debugmgr.ExecutionEvent;
import bluej.editor.moe.MoeEditor;
import bluej.pkgmgr.Package;
import bluej.pkgmgr.Project;
import bluej.prefmgr.PrefMgr;
import bluej.testmgr.record.InvokerRecord;
import bluej.utility.Debug;
import bluej.utility.DialogManager;
import bluej.utility.FileUtility;
import bluej.utility.JavaNames;
import bluej.utility.Utility;
import bluej.utility.javafx.JavaFXUtil;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.event.EventHandler;
import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import org.fxmisc.flowless.VirtualFlow;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.CharacterHit;
import org.fxmisc.richtext.NavigationActions.SelectionPolicy;
import org.fxmisc.richtext.StyledTextArea;
import org.fxmisc.richtext.TextExt;
import org.fxmisc.richtext.model.EditableStyledDocument;
import org.fxmisc.richtext.model.GenericEditableStyledDocument;
import org.fxmisc.richtext.model.ReadOnlyStyledDocument;
import org.fxmisc.richtext.model.SegmentOps;
import org.fxmisc.wellbehaved.event.EventPattern;
import org.fxmisc.wellbehaved.event.InputMap;
import org.fxmisc.wellbehaved.event.Nodes;
import threadchecker.OnThread;
import threadchecker.Tag;
| The Frame part of the Terminal window used for I/O when running programs
| under BlueJ.
|
| @author Michael Kolling
| @author Philip Stevens
|
@SuppressWarnings("serial")
public final class Terminal
implements BlueJEventListener, DebuggerTerminal{
private static final int MAX_BUFFER_LINES = 200;
private VirtualizedScrollPane<?> errorScrollPane;
private static interface TextAreaStyle
{
public String getCSSClass();
}
private static enum StdoutStyle implements TextAreaStyle
{
OUTPUT("terminal-output"), INPUT("terminal-input"), METHOD_RECORDING("terminal-method-record");
private final String cssClass;
private StdoutStyle(String cssClass)
{
this.cssClass = cssClass;
}
public String getCSSClass()
{
return cssClass;
}
}
private static enum StderrStyleType
{
NORMAL("terminal-error"), LINKED_STACK_TRACE("terminal-stack-link"), FOREIGN_STACK_TRACE("terminal-stack-foreign");
private final String cssClass;
private StderrStyleType(String cssClass)
{
this.cssClass = cssClass;
}
public String getCSSClass()
{
return cssClass;
}
}
private static class StderrStyle
implements TextAreaStyle
{
private final StderrStyleType type;
private final ExceptionSourceLocation exceptionSourceLocation;
private StderrStyle(StderrStyleType type)
{
this.type = type;
this.exceptionSourceLocation = null;
}
public StderrStyle(ExceptionSourceLocation exceptionSourceLocation)
{
this.type = StderrStyleType.LINKED_STACK_TRACE;
this.exceptionSourceLocation = exceptionSourceLocation;
}
public static final StderrStyle NORMAL = new StderrStyle(StderrStyleType.NORMAL);
public static final StderrStyle FOREIGN_STACK_TRACE = new StderrStyle(StderrStyleType.FOREIGN_STACK_TRACE);
@Override
public String getCSSClass()
{
return type.getCSSClass();
}
}
private static final String WINDOWTITLE = Config.getApplicationName() + ": " + Config.getString("terminal.title");
private static final String RECORDMETHODCALLSPROPNAME = "bluej.terminal.recordcalls";
private static final String CLEARONMETHODCALLSPROPNAME = "bluej.terminal.clearscreen";
private static final String UNLIMITEDBUFFERINGCALLPROPNAME = "bluej.terminal.buffering";
private final String title;
private final Project project;
private final StyledTextArea<Void, StdoutStyle> text;
private StyledTextArea<Void, StderrStyle> errorText = null;
private final TextField input;
private final SplitPane splitPane;
private boolean isActive = false;
private static BooleanProperty recordMethodCalls =
Config.getPropBooleanProperty(RECORDMETHODCALLSPROPNAME);
private static BooleanProperty clearOnMethodCall =
Config.getPropBooleanProperty(CLEARONMETHODCALLSPROPNAME);
private static BooleanProperty unlimitedBufferingCall =
Config.getPropBooleanProperty(UNLIMITEDBUFFERINGCALLPROPNAME);
private boolean newMethodCall = true;
private boolean errorShown = false;
private final InputBuffer buffer;
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 Writer err = new TerminalWriter(true);
private Stage window;
| Create a new terminal window with default specifications.
|
public Terminal(Project project)
{
this.title = WINDOWTITLE + " - " + project.getProjectName();
this.project = project;
buffer = new InputBuffer(256);
text = new StyledTextArea<Void, StdoutStyle>(null, (t, v) -> {
}, StdoutStyle.OUTPUT, this::applyStyle);
text.selectionProperty().addListener(s -> {
if (errorText != null && errorText.getSelection().getLength() != 0)
{
errorText.deselect();
}
});
VirtualizedScrollPane<?> scrollPane = new VirtualizedScrollPane<>(text);
text.setEditable(false);
text.getStyleClass().add("terminal");
text.styleProperty().bind(PrefMgr.getEditorFontCSS(true));
unlimitedBufferingCall.addListener(c -> {
trimToMaxBufferLines(text);
});
input = new TextField();
input.getStyleClass().add("terminal-input-field");
input.setOnAction(e -> {
sendInput(false);
e.consume();
});
input.styleProperty().bind(PrefMgr.getEditorFontCSS(true));
input.setEditable(false);
input.disableProperty().bind(input.editableProperty().not());
input.promptTextProperty().bind(Bindings.when(input.editableProperty()).then(Config.getString("terminal.running")).otherwise(Config.getString("terminal.notRunning")));
Nodes.addInputMap(input, InputMap.sequence(
InputMap.consume(EventPattern.keyPressed(new KeyCodeCombination(KeyCode.D, KeyCombination.CONTROL_DOWN)), e -> {sendInput(true); e.consume();
}),
InputMap.consume(EventPattern.keyPressed(new KeyCodeCombination(KeyCode.Z, KeyCombination.CONTROL_DOWN)), e -> {sendInput(true); e.consume();
}),
InputMap.consume(EventPattern.keyPressed(new KeyCodeCombination(KeyCode.EQUALS, KeyCombination.SHORTCUT_DOWN)), e -> Utility.increaseFontSize(PrefMgr.getEditorFontSize())),
InputMap.consume(EventPattern.keyPressed(new KeyCodeCombination(KeyCode.MINUS, KeyCombination.SHORTCUT_DOWN)), e -> Utility.decreaseFontSize(PrefMgr.getEditorFontSize())),
InputMap.consume(EventPattern.keyPressed(new KeyCodeCombination(KeyCode.DIGIT0, KeyCombination.SHORTCUT_DOWN)), e -> PrefMgr.getEditorFontSize().set(PrefMgr.DEFAULT_JAVA_FONT_SIZE))
));
text.setOnMouseClicked(e -> {
if (e.getButton() == MouseButton.PRIMARY)
{
input.requestFocus();
e.consume();
}
});
splitPane = new SplitPane(new BorderPane(scrollPane, null, null, input, null));
JavaFXUtil.addStyleClass(splitPane, "terminal-split");
BorderPane mainPanel = new BorderPane();
mainPanel.setCenter(splitPane);
mainPanel.setTop(makeMenuBar());
window = new Stage();
window.setWidth(500);
window.setHeight(500);
BlueJTheme.setWindowIconFX(window);
window.setTitle(title);
Scene scene = new Scene(mainPanel);
Config.addTerminalStylesheets(scene);
window.setScene(scene);
window.setOnCloseRequest(e -> {
e.consume();
if (project != null) {
if (project.getDebugger().getStatus() == Debugger.RUNNING)
return;
}
showHide(false);
});
window.setOnShown(e -> {
showingProperty.set(true);
});
window.setOnHidden(e -> showingProperty.set(false));
JavaFXUtil.addChangeListenerPlatform(showingProperty, this::showHide);
splitPane.setContextMenu(new ContextMenu(
JavaFXUtil.makeMenuItem(Config.getString("terminal.copy"), () ->
{
doCopy();
}, null)
));
Config.loadAndTrackPositionAndSize(window, "bluej.terminal");
BlueJEvent.addListener(this);
}
| Copy whichever of the stdout/stderr panes actually has a selection.
|
private void doCopy()
{
if (errorText != null && errorText.selectionProperty().getValue().getLength() != 0)
{
errorText.copy();
}
else if (text.selectionProperty().getValue().getLength() != 0)
{
text.copy();
}
}
private void sendInput(boolean eof)
{
String inputString = this.input.getText() + (eof ? "" : "\n");
buffer.putString(inputString);
if (eof)
{
buffer.signalEOF();
}
else
{
buffer.notifyReaders();
}
this.input.clear();
writeToPane(text, inputString, StdoutStyle.INPUT);
}
private void applyStyle(TextExt t, TextAreaStyle s)
{
JavaFXUtil.addStyleClass(t, s.getCSSClass());
}
| Show or hide the Terminal window.
|
public void showHide(boolean show)
{
DataCollector.showHideTerminal(project, show);
if (show)
{
window.show();
input.requestFocus();
}
else
{
window.hide();
}
}
public void dispose()
{
showHide(false);
window = null;
}
| Return true if the window is currently displayed.
|
public boolean isShown()
{
return window.isShowing();
}
| Make the input field active, or not
|
public void activate(boolean active)
{
if (active != isActive) {
input.setEditable(active);
isActive = active;
}
}
| Clear the terminal.
|
public void clear()
{
text.replaceText(" ");
if (errorText!=null) {
errorText.replaceText(" ");
}
hideErrorPane();
}
| Save the terminal text to file.
|
public void save()
{
File fileName = FileUtility.getSaveFileFX(window,
Config.getString("terminal.save.title"),
null, false);
if (fileName != null) {
if (fileName.exists()){
if (DialogManager.askQuestionFX(window, "error-file-exists") != 0)
return;
}
try
{
FileWriter writer = new FileWriter(fileName);
String output = text.getText().replace("\n", System.lineSeparator()) ;
writer.write(output);
writer.close();
}
catch (IOException ex)
{
DialogManager.showErrorFX(window, "error-save-file");
}
}
}
@OnThread(Tag.FXPlatform)
public void print()
{
PrinterJob job = JavaFXUtil.createPrinterJob();
if (job == null)
{
DialogManager.showErrorFX(window,"print-no-printers");
}
else if (job.showPrintDialog(window))
{
EditableStyledDocument<Void, String, StdoutStyle> doc = new GenericEditableStyledDocument<>(null, StdoutStyle.OUTPUT, SegmentOps.styledTextOps());
doc.replace(0, 0, ReadOnlyStyledDocument.from(text.getDocument()));
StyledTextArea<Void, StdoutStyle> offScreenEditor = new StyledTextArea<Void, StdoutStyle>(
null, (t, v) -> {
}, StdoutStyle.OUTPUT, this::applyStyle, doc);
Scene scene = new Scene(offScreenEditor);
Config.addTerminalStylesheets(scene);
double pixelWidth = job.getJobSettings().getPageLayout().getPrintableWidth();
double pixelHeight = job.getJobSettings().getPageLayout().getPrintableHeight();
offScreenEditor.resize(pixelWidth, pixelHeight);
offScreenEditor.setWrapText(true);
offScreenEditor.requestLayout();
offScreenEditor.layout();
offScreenEditor.applyCss();
VirtualFlow<?, ?> virtualFlow = (VirtualFlow<?, ?>) offScreenEditor.lookup(".virtual-flow");
new Thread(new Runnable()
{
@Override
@OnThread(value = Tag.FX, ignoreParent = true)
public void run()
{
MoeEditor.printPages(job, offScreenEditor, n -> {
}, offScreenEditor, virtualFlow);
job.endJob();
}
}).start();
}
}
| Write some text to the terminal.
|
private <S extends TextAreaStyle> void writeToPane(StyledTextArea<Void, S> pane, String s, S style)
{
prepare();
if (pane == errorText)
showErrorPane();
int n = s.lastIndexOf('\f');
if (n != -1) {
clear();
s = s.substring(n + 1);
}
pane.append(styled(s, style));
if (pane != errorText)
{
trimToMaxBufferLines(pane);
}
pane.end(SelectionPolicy.CLEAR);
pane.requestFollowCaret();
}
private <S extends TextAreaStyle> void trimToMaxBufferLines(StyledTextArea<Void, S> pane)
{
if (!unlimitedBufferingCall.get() && pane.getParagraphs().size() >= MAX_BUFFER_LINES)
{
int newStart = pane.position(pane.getParagraphs().size() - MAX_BUFFER_LINES, 0).toOffset();
pane.replaceText(0, newStart, "");
}
}
| Prepare the terminal for I/O.
|
private void prepare()
{
if (newMethodCall) {
showHide(true);
newMethodCall = false;
}
else if (Config.isGreenfoot()) {
if (!window.isShowing()) {
showHide(true);
}
}
}
| An interactive method call has been made by a user.
|
private void methodCall(String callString)
{
newMethodCall = false;
if (clearOnMethodCall.get()) {
clear();
}
if (recordMethodCalls.get()) {
text.append(styled(callString + "\n", StdoutStyle.METHOD_RECORDING));
}
newMethodCall = true;
}
| Check if "clear on method call" option is selected.
|*/
public boolean clearOnMethodCall()
{
return clearOnMethodCall.getValue();
}
private static <S> ReadOnlyStyledDocument<Void, String, S> styled(String text, S style)
|
|{
|
|return ReadOnlyStyledDocument.fromString(text, null, style, SegmentOps.styledTextOps());
|
|}
|
|private void constructorCall(InvokerRecord ir)
|
|{
|
|newMethodCall = false;
|
|if (clearOnMethodCall.get()) {
|
|clear();
|
|}
|
|if (recordMethodCalls.get()) {
|
|String callString = ir.getResultTypeString() + " " + ir.getResultName() + " = " + ir.toExpression() + ";";
text.append(styled(callString + "\n", StdoutStyle.METHOD_RECORDING));
}
newMethodCall = true;
}
private void methodResult(ExecutionEvent event)
{
if (recordMethodCalls.get()) {
|
|String result = null;
|
|String resultType = event.getResult();
|
|if (resultType == ExecutionEvent.NORMAL_EXIT) {
|
|DebuggerObject object = event.getResultObject();
|
|if (object != null) {
|
|if (event.getClassName() != null && event.getMethodName() == null) {
|
|// Constructor call - the result object is the created object.
|
|// Don't display the result separately:
|
|return;
|
|}
|
|else {
|
|// if the method returns a void, we must handle it differently
|
|if (object.isNullObject()) {
|
|return; // Don't show result of void calls
|
|}
|
|else {
|
|// other - the result object is a wrapper with a single result field
|
|DebuggerField resultField = object.getField(0);
|
|result = " returned " + resultField.getType().toString(true) + " ";
result += resultField.getValueString();
}
}
}
}
else if (resultType == ExecutionEvent.EXCEPTION_EXIT) {
|
|result = " Exception occurred.";
}
else if (resultType == ExecutionEvent.TERMINATED_EXIT) {
result = " VM terminated.";
}
if (result != null) {
text.append(styled(result + "\n", StdoutStyle.METHOD_RECORDING));
}
}
}
/**
* Looks through the contents of the terminal for lines
* that look like they are part of a stack trace.
*/
private void scanForStackTrace()
|
|{
|
|try {
|
|String content = errorText.getText();
|
|Pattern p = java.util.regex.Pattern.compile("at (\\S+)\\((\\S+)\\.java:(\\d+)\\)");
// Matches things like:
// at greenfoot.localdebugger.LocalDebugger$QueuedExecution.run(LocalDebugger.java:267)
// ^--------------------group 1----------------------------^ ^--group 2--^ ^3^
|
|Matcher m = p.matcher(content);
|
|while (m.find())
|
|{
|
|String fullyQualifiedMethodName = m.group(1);
|
|String javaFile = m.group(2);
|
|int lineNumber = Integer.parseInt(m.group(3));
|
|// The fully qualified method name will end in ".method", so we can
// definitely remove that:
String fullyQualifiedClassName = JavaNames.getPrefix(fullyQualifiedMethodName);
// The class name may be an inner class, so we want to take the package:
|
|String packageName = JavaNames.getPrefix(fullyQualifiedClassName);
|
|//Find out if that file is available, and only link if it is:
|
|Package pkg = project.getPackage(packageName);
|
|if (pkg != null && pkg.getAllClassnames().contains(javaFile))
|
|{
|
|errorText.setStyle(m.start(1), m.end(), new StderrStyle(new ExceptionSourceLocation(m.start(1), m.end(), pkg, javaFile, lineNumber)));
|
|}
|
|else
|
|{
|
|errorText.setStyle(m.start(), m.end(), StderrStyle.FOREIGN_STACK_TRACE);
|
|}
|
|}
|
|//Also mark up native method lines in stack traces with a marker for font colour:
|
|p = java.util.regex.Pattern.compile("at \\S+\\((Native Method|Unknown Source)\\)");
// Matches things like:
// at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
m = p.matcher(content);
while (m.find())
{
errorText.setStyle(m.start(), m.end(), StderrStyle.FOREIGN_STACK_TRACE);
|
|}
|
|}
|
|catch (NumberFormatException e ) {
|
|//In case it looks like an exception but has a large line number:
|
|e.printStackTrace();
|
|}
|
|}
|
|/**
| Return the input stream that can be used to read from this terminal.
|
@OnThread(value = Tag.Any, ignoreParent = true)
public Reader getReader()
{
return in;
}
| Return the output stream that can be used to write to this terminal
|
@OnThread(value = Tag.Any, ignoreParent = true)
public Writer getWriter()
{
return out;
}
| It implements the method on the interface DebuggerTerminal which is called
| when there is reading request from the terminal on the remote virtual machine
|
@OnThread(Tag.Any)
public void showOnInput()
{
Platform.runLater(() -> {
if (!this.isShown()) {
this.showHide(true);
}
if (this.isShown()) {
Utility.bringToFrontFX(window);
input.requestFocus();
}
});
}
| Return the output stream that can be used to write error output to this terminal
|
@OnThread(value = Tag.Any, ignoreParent = true)
public Writer getErrorWriter()
{
return err;
}
| Called when a BlueJ event is raised. The event can be any BlueJEvent
| type. The implementation of this method should check first whether
| the event type is of interest an return immediately if it isn't.
|
| @param eventId A constant identifying the event. One of the event id
| constants defined in BlueJEvent.
| @param arg An event specific parameter. See BlueJEvent for
| definition.
| @param prj A project where the event happens
|
@Override
public void blueJEvent(int eventId, Object arg, Project prj)
{
if (eventId == BlueJEvent.METHOD_CALL && this.project == prj) {
InvokerRecord ir = (InvokerRecord) arg;
if (ir.getResultName() != null) {
constructorCall(ir);
}
else {
boolean isVoid = ir.hasVoidResult();
if (isVoid) {
methodCall(ir.toStatement());
}
else {
methodCall(ir.toExpression());
}
}
}
else if (eventId == BlueJEvent.EXECUTION_RESULT) {
methodResult((ExecutionEvent) arg);
}
}
| Show the errorPane for error output
|
private void showErrorPane()
{
if (errorShown) {
return;
}
if (errorText == null) {
errorText = new StyledTextArea<Void, StderrStyle>(null, (t, v) -> {
}, StderrStyle.NORMAL, this::applyStyle);
errorText.getStyleClass().add("terminal-error");
errorScrollPane = new VirtualizedScrollPane<>(errorText);
errorText.styleProperty().bind(PrefMgr.getEditorFontCSS(true));
errorText.setEditable(false);
errorText.selectionProperty().addListener(s -> {
if (text != null && text.getSelection().getLength() != 0)
{
text.deselect();
}
});
errorText.plainTextChanges().subscribe(c -> scanForStackTrace());
EventHandler<MouseEvent> onClick = e ->
{
CharacterHit hit = errorText.hit(e.getX(), e.getY());
StderrStyle style = errorText.getStyleAtPosition(hit.getInsertionIndex());
if (style.exceptionSourceLocation != null)
{
style.exceptionSourceLocation.showInEditor();
}
else
{
errorText.moveTo(hit.getInsertionIndex(), SelectionPolicy.CLEAR);
}
};
errorText.setOnOutsideSelectionMousePressed(onClick);
errorText.setOnInsideSelectionMousePressReleased(onClick);
}
splitPane.getItems().add(errorScrollPane);
Config.rememberDividerPosition(window, splitPane, "bluej.terminal.dividerpos");
errorShown = true;
}
| Hide the pane with the error output.
|
private void hideErrorPane()
{
if (!errorShown) {
return;
}
splitPane.getItems().remove(errorScrollPane);
errorShown = false;
}
public BooleanProperty showingProperty()
{
return showingProperty;
}
| Create the terminal's menubar, all menus and items.
|
private MenuBar makeMenuBar()
{
MenuBar menubar = new MenuBar();
menubar.setUseSystemMenuBar(true);
Menu menu = new Menu(Config.getString("terminal.options"));
MenuItem clearItem = new MenuItem(Config.getString("terminal.clear"));
clearItem.setOnAction(e -> clear());
clearItem.setAccelerator(new KeyCodeCombination(KeyCode.K, KeyCombination.SHORTCUT_DOWN));
MenuItem copyItem = new MenuItem(Config.getString("terminal.copy"));
copyItem.setOnAction(e -> doCopy());
copyItem.setAccelerator(new KeyCodeCombination(KeyCode.C, KeyCombination.SHORTCUT_DOWN));
MenuItem saveItem = new MenuItem(Config.getString("terminal.save"));
saveItem.setOnAction(e -> save());
saveItem.setAccelerator(new KeyCodeCombination(KeyCode.S, KeyCombination.SHORTCUT_DOWN));
MenuItem printItem = new MenuItem(Config.getString("terminal.print"));
printItem.setOnAction(e -> print());
printItem.setAccelerator(new KeyCodeCombination(KeyCode.P, KeyCombination.SHORTCUT_DOWN));
menu.getItems().addAll(clearItem, copyItem, saveItem, printItem, new SeparatorMenuItem());
CheckMenuItem autoClear = new CheckMenuItem(Config.getString("terminal.clearScreen"));
autoClear.selectedProperty().bindBidirectional(clearOnMethodCall);
CheckMenuItem recordCalls = new CheckMenuItem(Config.getString("terminal.recordCalls"));
recordCalls.selectedProperty().bindBidirectional(recordMethodCalls);
CheckMenuItem unlimitedBuffering = new CheckMenuItem(Config.getString("terminal.buffering"));
unlimitedBuffering.selectedProperty().bindBidirectional(unlimitedBufferingCall);
menu.getItems().addAll(autoClear, recordCalls, unlimitedBuffering);
MenuItem closeItem = new MenuItem(Config.getString("terminal.close"));
closeItem.setOnAction(e -> showHide(false));
closeItem.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN));
menu.getItems().addAll(new SeparatorMenuItem(), closeItem);
menubar.getMenus().add(menu);
return menubar;
}
public Stage getWindow()
{
return window;
}
| Cleanup any resources or listeners the terminal has created/registered.
| Called when the project is closing.
|
public void cleanup()
{
BlueJEvent.removeListener(this);
}
| A Reader which reads from the terminal.
|
@OnThread(Tag.Any)
private class TerminalReader
extends Reader
{
public int read(char[] cbuf, int off, int len)
{
int charsRead = 0;
while (charsRead < len) {
cbuf[off + charsRead] = buffer.getChar();
charsRead++;
if (buffer.isEmpty())
break;
}
return charsRead;
}
@Override
public boolean ready()
{
return ! buffer.isEmpty();
}
public void close()
{
}
}
| A writer which writes to the terminal. It can be flagged for error output.
| The idea is that error output could be presented differently from standard
| output.
|
@OnThread(Tag.Any)
private class TerminalWriter
extends Writer
{
private boolean isErrorOut;
TerminalWriter(boolean isError)
{
super();
isErrorOut = isError;
}
public void write(final char[] cbuf, final int off, final int len)
{
try {
CompletableFuture<Boolean> written = new CompletableFuture<>();
Platform.runLater(() -> {
try
{
String s = new String(cbuf, off, len);
if (isErrorOut)
{
showErrorPane();
writeToPane(errorText, s, StderrStyle.NORMAL);
}
else{ writeToPane(text, s, StdoutStyle.OUTPUT);
}
}
finally
{
written.complete(true);
}
});
written.get(2000, TimeUnit.MILLISECONDS);
}
catch (InterruptedException | ExecutionException | TimeoutException ie)
{
Debug.reportError(ie);
}
}
public void flush()
{
}
public void close()
{
}
}
}
. - Terminal
top,
use,
map,
interface Terminal . TextAreaStyle
. getCSSClass
. StdoutStyle
. getCSSClass
. StderrStyleType
. getCSSClass
top,
use,
map,
class Terminal . TextAreaStyle . StderrStyle
. StderrStyle
. StderrStyle
. getCSSClass
. Terminal
. doCopy
. sendInput
. applyStyle
. showHide
. dispose
. isShown
. activate
. clear
. save
. print
. run
. writeToPane
. trimToMaxBufferLines
. prepare
. methodCall
. getReader
. getWriter
. showOnInput
. getErrorWriter
. blueJEvent
. showErrorPane
. hideErrorPane
. showingProperty
. makeMenuBar
. getWindow
. cleanup
top,
use,
map,
class TerminalReader
. read
. ready
. close
top,
use,
map,
class TerminalWriter
. write
. flush
. close
944 neLoCode
+ 101 LoComm