package bluej.editor.moe;

import bluej.BlueJEvent;
import bluej.BlueJEventListener;
import bluej.Config;
import bluej.compiler.CompileReason;
import bluej.compiler.CompileType;
import bluej.compiler.Diagnostic;
import bluej.debugger.DebuggerThread;
import bluej.editor.EditorWatcher;
import bluej.editor.moe.BlueJSyntaxView.ParagraphAttribute;
import bluej.editor.moe.MoeActions.MoeAbstractAction;
import bluej.pkgmgr.Project;
import bluej.prefmgr.PrefMgr.PrintSize;
import bluej.editor.moe.MoeErrorManager.ErrorDetails;
import bluej.editor.moe.MoeSyntaxDocument.Element;
import bluej.editor.moe.PrintDialog.PrintChoices;
import bluej.editor.stride.FXTabbedEditor;
import bluej.editor.stride.FrameEditor;
import bluej.editor.stride.MoeFXTab;
import bluej.parser.AssistContent;
import bluej.parser.AssistContent.ParamInfo;
import bluej.parser.ExpressionTypeInfo;
import bluej.parser.ImportsCollection;
import bluej.parser.ImportsCollection.LocatableImport;
import bluej.parser.ParseUtils;
import bluej.parser.SourceLocation;
import bluej.parser.entity.EntityResolver;
import bluej.parser.entity.JavaEntity;
import bluej.parser.lexer.LocatableToken;
import bluej.parser.nodes.MethodNode;
import bluej.parser.nodes.NodeTree.NodeAndPosition;
import bluej.parser.nodes.ParsedCUNode;
import bluej.parser.nodes.ParsedNode;
import bluej.parser.symtab.ClassInfo;
import bluej.parser.symtab.Selection;
import bluej.pkgmgr.JavadocResolver;
import bluej.prefmgr.PrefMgr;
import bluej.stride.framedjava.elements.CallElement;
import bluej.stride.framedjava.elements.CodeElement;
import bluej.stride.framedjava.elements.NormalMethodElement;
import bluej.stride.framedjava.slots.ExpressionCompletionCalculator;
import bluej.stride.generic.AssistContentThreadSafe;
import bluej.stride.slots.SuggestionList;
import bluej.stride.slots.SuggestionList.SuggestionDetails;
import bluej.stride.slots.SuggestionList.SuggestionDetailsWithHTMLDoc;
import bluej.stride.slots.SuggestionList.SuggestionListListener;
import bluej.stride.slots.SuggestionList.SuggestionListParent;
import bluej.stride.slots.SuggestionList.SuggestionShown;
import bluej.utility.Debug;
import bluej.utility.DialogManager;
import bluej.utility.FileUtility;
import bluej.utility.Utility;
import bluej.utility.javafx.FXConsumer;
import bluej.utility.javafx.FXPlatformConsumer;
import bluej.utility.javafx.FXPlatformRunnable;
import bluej.utility.javafx.FXRunnable;
import bluej.utility.javafx.FXSupplier;
import bluej.utility.javafx.JavaFXUtil;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.binding.DoubleExpression;
import javafx.beans.binding.StringExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.print.PrinterJob;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.TilePane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.scene.web.WebView;
import javafx.stage.PopupWindow;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.fxmisc.flowless.VirtualFlow;
import org.fxmisc.flowless.VirtualizedScrollPane;
import org.fxmisc.richtext.GenericStyledArea;
import org.fxmisc.richtext.event.MouseOverTextEvent;
import org.fxmisc.richtext.model.TwoDimensional.Bias;
import org.fxmisc.richtext.model.TwoDimensional.Position;
import org.fxmisc.wellbehaved.event.*;
import org.w3c.dom.NodeList;
import org.w3c.dom.events.EventTarget;
import threadchecker.OnThread;
import threadchecker.Tag;

import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;


| Moe is the editor of the BlueJ environment. This class is the main class of | this editor and implements the top-level functionality. | | <p>MoeEditor implements the Editor interface, which defines the interface to the | rest of the BlueJ system. | | @author Michael Kolling | @author Bruce Quig | @author Damiano Bolla | @OnThread(Tag.FXPlatform) public final class MoeEditor extends ScopeColorsBorderPane implements bluej.editor.TextEditor, BlueJEventListener{ final static int version = 400; final static String versionString = "3.3.0"; final static String LabelSuffix = "Label"; final static String ActionSuffix = "Action"; final static String TooltipSuffix = "Tooltip"; final static String AcceleratorSuffix = "Accelerator"; final static String COMPILED = "compiled"; private final static String CRASHFILE_SUFFIX = "#"; private static boolean matchBrackets = false;
| List of actions that are disabled in the readme text file | private static ArrayList<String> readMeActions; private final String interfaceString = Config.getString("editor.interfaceLabel"); private final String implementationString = Config.getString("editor.implementationLabel"); private final FXSupplier<FXTabbedEditor> defaultFXTabbedEditor; @OnThread(Tag.Any) private FXTabbedEditor fxTabbedEditor; private @OnThread(Tag.FX) MoeFXTab fxTab; public MoeUndoManager undoManager; private boolean showingChangedOnDiskDialog = false;
| Watcher - provides interface to BlueJ core. May be null (eg for README.txt file). | private final EditorWatcher watcher; private final Properties resources; private MoeSyntaxDocument sourceDocument; private MoeActions actions; private MoeEditorPane sourcePane; private WebView htmlPane; private Info info; private StatusLabel saveState; private ComboBox<String> interfaceToggle; private FindPanel finder; private final ObjectProperty<FindNavigator> currentSearchResult = new SimpleObjectProperty<>(null); private MenuBar menubar; private String filename; private long lastModified; private String windowTitle; private String docFilename; private Charset characterSet; private final boolean sourceIsCode; private final BooleanProperty viewingHTML; private int currentStepLineNumber; private boolean mayHaveBreakpoints; private boolean ignoreChanges = false; private boolean tabsAreExpanded = false;
| Used to obtain javadoc for arbitrary methods | private final JavadocResolver javadocResolver; private ReparseRunner reparseRunner;
| Property map, allows BlueJ extensions to associate property values with | this editor instance; otherwise unused. | private final HashMap<String,Object> propertyMap = new HashMap<>(); private int oldCaretLineNumber = -1; private ErrorDisplay errorDisplay; private boolean compilationQueued = false; private boolean compilationQueuedExplicit = false; private boolean compilationStarted = false; private boolean requeueForCompilation = false; private CompileReason requeueReason; private CompileType requeueType;
| Manages display of compiler and parse errors | private final MoeErrorManager errorManager = new MoeErrorManager(this, enable -> { }); private int mouseCaretPos = -1;
| A callback to call (on the Swing thread) when this editor is opened. | private final FXPlatformRunnable callbackOnOpen; @OnThread(Tag.FX) private final List<Menu> fxMenus = new ArrayList<>(); private final BooleanProperty compiledProperty = new SimpleBooleanProperty(true); @OnThread(Tag.FXPlatform) private boolean respondingToChange; private String lastSearchString = "";
| Constructor. Title may be null. | public MoeEditor(MoeEditorParameters parameters, FXSupplier<FXTabbedEditor> getDefaultEditor) { super(); this.defaultFXTabbedEditor = getDefaultEditor; final String fxWindowTitle = parameters.getTitle(); watcher = parameters.getWatcher(); resources = parameters.getResources(); javadocResolver = parameters.getJavadocResolver(); filename = null; windowTitle = parameters.getTitle(); sourceIsCode = parameters.isCode(); viewingHTML = new SimpleBooleanProperty(false); currentStepLineNumber = -1; mayHaveBreakpoints = false; matchBrackets = PrefMgr.getFlag(PrefMgr.MATCH_BRACKETS); initWindow(parameters.getProjectResolver()); callbackOnOpen = parameters.getCallbackOnOpen(); this.fxTabbedEditor = getDefaultEditor.get(); this.fxTab = new MoeFXTab(this, fxWindowTitle); }
| Find the position of a substring in a given string, | can specify direction and whether the search should ignoring case | Return the position of the substring or -1. | | @param text the full string to be searched | @param sub the substring that we're looking for | @param ignoreCase if true, case is ignored | @param backwards Description of the Parameter | @param foundPos Offset for the string search | @return Description of the Return Value | @returns the index of the substring, or -1 if not found | private static int findSubstring(String text, String sub, boolean ignoreCase, boolean backwards, int foundPos) { int strlen = text.length(); int sublen = sub.length(); if (sublen == 0) { return -1; } boolean found = false; int pos = foundPos; boolean itsOver = (backwards ? (pos < 0) : (pos + sublen > strlen)); while (!found && !itsOver){ found = text.regionMatches(ignoreCase, pos, sub, 0, sublen); if (found) { return pos; } if (!found) { pos = (backwards ? pos - 1 : pos + 1); itsOver = (backwards ? (pos < 0) : (pos + sublen > strlen)); } } return -1; }
| Check whether an action is not valid for the project "readme" (i.e. if it is only |* valid for source files). * * @param actionName String representing the action name * @return true if it is an action that should be disabled while editing the readme file, * or false otherwise private static boolean isNonReadmeAction(String actionName) { List<String> flaggedActions = getNonReadmeActions(); return flaggedActions.contains(actionName); }
| Get a list of actions not applicable in the readme.txt file | private static ArrayList getNonReadmeActions() { if (readMeActions == null) { readMeActions = new ArrayList<>(); readMeActions.add("compile"); readMeActions.add("autoindent"); readMeActions.add("insert-method"); readMeActions.add("add-javadoc"); readMeActions.add("toggle-interface-view"); } return readMeActions; }
| Check whether the source file has changed on disk. If it has, reload. | private void checkForChangeOnDisk() { if (filename == null) { return; } File file = new File(filename); long modified = file.lastModified(); if (modified > lastModified + 1000 && !showingChangedOnDiskDialog) { Debug.message("File " + filename + " changed on disk; our record is " + lastModified + " but file was " + modified); if (saveState.isChanged()) { showingChangedOnDiskDialog = true; int answer = DialogManager.askQuestionFX(getWindow(), "changed-on-disk"); if (answer == 0) { doReload(); } else { setLastModified(modified); } showingChangedOnDiskDialog = false; } else { doReload(); } } }
| | Load the file "filename" and show the editor window. |*/ @Override @OnThread(Tag.FXPlatform) public boolean showFile(String filename, Charset charset, boolean compiled, String docFilename) { this.filename = filename; | |this.docFilename = docFilename; | |this.characterSet = charset; | |boolean loaded = false; | |if (filename != null) { | |setupJavadocMangler(); | |try { | |// check for crash file | |String crashFilename = filename + CRASHFILE_SUFFIX; | |String backupFilename = crashFilename + "backup"; File crashFile = new File(crashFilename); if (crashFile.exists()) { File backupFile = new File(backupFilename); backupFile.delete(); | |crashFile.renameTo(backupFile); | |DialogManager.showMessageFX(fxTabbedEditor.getWindow(), "editor-crashed"); } // FileReader reader = new FileReader(filename); FileInputStream inputStream = new FileInputStream(filename); Reader reader = new InputStreamReader(inputStream, charset); | |ignoreChanges = true; | |sourcePane.read(reader); | |ignoreChanges = false; | |try { | |reader.close(); | |inputStream.close(); | |} | |catch (IOException ioe) { | |} | |File file = new File(filename); | |setLastModified(file.lastModified()); | |listenToChanges(sourceDocument); | |//NAVIFX | |//naviView.setDocument(sourceDocument); | |sourceDocument.enableParser(false); | |loaded = true; | |scheduleReparseRunner(); | |} | |catch (IOException ex) { | |// TODO display user-visible error | |Debug.reportError("Couldn't open file", ex); } } else { if (docFilename != null) { if (new File(docFilename).exists()) { showInterface(true); loaded = true; | |interfaceToggle.setDisable(true); | |} | |} | |} | |if (!loaded) { | |// should exist, but didn't | |return false; | |} | |setCompileStatus(compiled); | |return true; | |} | |/* | Reload the editor content from the associated file, discarding unsaved | edits. | @Override public void reloadFile() { doReload(); }
| | Wipe out contents of the editor. | @Override @OnThread(Tag.FXPlatform) public void clear() { ignoreChanges = true; sourcePane.setText(""); ignoreChanges = false; }
| Insert a string into the buffer. The editor is not immediately | redisplayed. This function is typically used in a sequence "clear; |* [insertText]*; setVisible(true)". If the selection is on, it is replaced * by the new text. * * @param text the text to be inserted * @param caretBack move the caret to the beginning of the inserted text */ @Override @OnThread(Tag.FXPlatform) | |public void insertText(String text, boolean caretBack) | |{ | |sourcePane.replaceSelection(text); | |if (caretBack) { | |sourcePane.setCaretPosition(sourcePane.getCaretPosition() - text.length()); | |} | |} | |/** | Show the editor window. This includes whatever is necessary of the | following: make visible, de-iconify, bring to front of window stack. | | @param vis The new visible value | @param openInNewWindow if this is true, the editor opens in a new window | @Override @OnThread(Tag.FXPlatform) public void setEditorVisible(boolean vis, boolean openInNewWindow) { if (vis) { checkBracketStatus(); if (sourceIsCode && !compiledProperty.get() && sourceDocument.notYetShown) { scheduleCompilation(CompileReason.LOADED, CompileType.ERROR_CHECK_ONLY); } } if (fxTabbedEditor == null) { if (openInNewWindow) { fxTabbedEditor = defaultFXTabbedEditor.get().getProject().createNewFXTabbedEditor(); } else { fxTabbedEditor = defaultFXTabbedEditor.get(); } } else { if (openInNewWindow && !fxTabbedEditor.containsTab(fxTab) ) { fxTabbedEditor = defaultFXTabbedEditor.get().getProject().createNewFXTabbedEditor(); } } if (vis) { fxTabbedEditor.addTab(fxTab, vis, true); } fxTabbedEditor.setWindowVisible(vis, fxTab); if (vis) { fxTabbedEditor.bringToFront(fxTab); if (callbackOnOpen != null) { callbackOnOpen.run(); } sourceDocument.notYetShown = false; sourcePane.requestFollowCaret(); sourcePane.layout(); } }
| Refresh the editor window. | @Override public void refresh() { checkBracketStatus(); scheduleReparseRunner(); }
| | Save the buffer to disk under current filename, if there any changes. | This method may be called often. | @Override @OnThread(Tag.FXPlatform) public void save() throws IOException { IOException failureException = null; if (saveState.isChanged()) { recordEdit(true); checkForChangeOnDisk(); if (! saveState.isChanged()) { return; } Writer writer = null; try { String crashFilename = filename + CRASHFILE_SUFFIX; FileUtility.copyFile(filename, crashFilename); OutputStream ostream = new BufferedOutputStream(new FileOutputStream(filename)); writer = new OutputStreamWriter(ostream, characterSet); sourcePane.write(writer); writer.close(); writer = null; setLastModified(new File(filename).lastModified()); File crashFile = new File(crashFilename); crashFile.delete(); setSaved(); } catch (IOException ex) { failureException = ex; info.message (Config.getString("editor.info.errorSaving") + " - " + ex.getLocalizedMessage()); } finally { try { if (writer != null) writer.close(); } catch (IOException ex) { failureException = ex; } } } if (failureException != null) { info.message (Config.getString("editor.info.errorSaving") + " - " + failureException.getLocalizedMessage()); throw failureException; } }
| The editor wants to close. Do this through the EditorManager so that we | can be removed from the list of open editors. | @Override @OnThread(Tag.FXPlatform) public void close() { cancelFreshState(); try { save(); } catch (IOException ioe) { } doClose(); }
| Display a message (used for compile/runtime errors). An editor must | support at least two lines of message text, so the message can contain a | newline character. | @param message the message to be displayed | @param lineNumber The line to highlight | @param column the column to move the cursor to | @Override public void displayMessage(String message, int lineNumber, int column) { switchToSourceView(); Element line = getSourceLine(lineNumber); int pos = line.getStartOffset(); sourcePane.setCaretPosition(pos); sourcePane.moveCaretPosition(line.getEndOffset() - 1); sourcePane.requestFollowCaret(); info.messageImportant(message); } @Override public boolean displayDiagnostic(Diagnostic diagnostic, int errorIndex, CompileType compileType) { if (compileType.showEditorOnError()) { setEditorVisible(true, false); } switchToSourceView(); Element line = getSourceLine((int) diagnostic.getStartLine()); if (line != null) { int startPos = getPosFromColumn(line, (int) diagnostic.getStartColumn()); int endPos; if (diagnostic.getStartLine() != diagnostic.getEndLine()) { endPos = line.getEndOffset() - 1; } else { endPos = getPosFromColumn(line, (int) diagnostic.getEndColumn()); } if (endPos == startPos) { if (endPos < getTextLength() - 1 && !sourceDocument.getText(endPos, 1).equals("\n")) { endPos += 1; } else if (startPos > 0 && !sourceDocument.getText(startPos - 1, 1).equals("\n")) { startPos -= 1; } } errorManager.addErrorHighlight(startPos, endPos, diagnostic.getMessage(), diagnostic.getIdentifier()); repaint(); } return true; } @Override public boolean setStepMark(int lineNumber, String message, boolean isBreak, DebuggerThread thread) { switchToSourceView(); if (isBreak) { setStepMark(lineNumber); } sourceDocument.showStepLine(lineNumber); sourcePane.setCaretPosition(getOffsetFromLineColumn(new SourceLocation(lineNumber, 1))); sourcePane.requestFollowCaret(); if (message != null) { info.messageImportant(message); } return false; }
| Get a position in a line from a column number, where the column number assumes | tab stops are every 8 spaces. | private int getPosFromColumn(Element line, int column) { int spos = line.getStartOffset(); int epos = line.getEndOffset(); int testPos = Math.min(epos - spos - 1, column - 1); if (testPos <= 0) { return spos; } int cpos = 0; int tpos = 0; String lineText = sourceDocument.getText(spos, testPos); while (cpos < column - 1) { int tabPos = lineText.indexOf('\t', tpos); if (tabPos == -1) { tpos += column - cpos - 1; return Math.min(spos + tpos, epos - 1); } int newcpos = cpos + (tabPos - tpos); if (newcpos >= column) { tpos += column - cpos - 1; return spos + tpos; } cpos = newcpos; cpos += 8; cpos -= cpos % 8; tpos = tabPos + 1; } return spos; }
| Set the selection of the editor (in the source pane) to be {}code len} characters on | line {}code lineNumber}, starting with column {}code columnNumber}. | | @param lineNumber the line to select characters on | @param columnNumber the column to start selection at (1st column is 1 - not 0) | @param len the number of characters to select | @Override @OnThread(Tag.FXPlatform) public void setSelection(int lineNumber, int columnNumber, int len) { Element line = getSourceLine(lineNumber); sourcePane.select(line.getStartOffset() + columnNumber - 1, line.getStartOffset() + columnNumber + len - 1); }
| Select a specified area of text in the source pane. | | @param lineNumber1 The new selection value | @param columnNumber1 The new selection value | @param lineNumber2 The new selection value | @param columnNumber2 The new selection value | @Override @OnThread(Tag.FXPlatform) public void setSelection(int lineNumber1, int columnNumber1, int lineNumber2, int columnNumber2) {
| | if (lineNumber2 < lineNumber1) return; if (lineNumber2 == lineNumber1 && | (columnNumber2 < columnNumber1)) return; | Element line1 = getSourceLine(lineNumber1); Element line2 = getSourceLine(lineNumber2); sourcePane.select(line1.getStartOffset() + columnNumber1 - 1, line2.getStartOffset() + columnNumber2 - 1); }
| Remove the step mark (the mark that shows the current line when | single-stepping through code). If it is not currently displayed, do | nothing. | @Override @OnThread(Tag.FXPlatform) public void removeStepMark() { if (currentStepLineNumber != -1) { sourceDocument.setParagraphAttributesForLineNumber(currentStepLineNumber, Collections.singletonMap(ParagraphAttribute.STEP_MARK, false)); currentStepLineNumber = -1; sourcePane.setCaretPosition(sourcePane.getCaretPosition()); repaint(); } }
| Change class name. | | @param title new window title | @param filename new file name | @Override public void changeName(String title, String filename, String javaFilename, String docFilename) { this.filename = filename; this.docFilename = docFilename; windowTitle = title; setWindowTitle(); }
| Set the "compiled" status |* * @param compiled True if the class has been compiled. */ @Override public void setCompiled(boolean compiled) { setCompileStatus(compiled); if (compiled) { errorManager.removeAllErrorHighlights(); | |} | |} | |/** | Schedule an immediate compilation for the specified reason and of the specified type. | @param reason The reason for compilation | @param ctype The type of compilation | private void scheduleCompilation(CompileReason reason, CompileType ctype) { if (watcher != null) { if (! compilationQueued ) { watcher.scheduleCompilation(true, reason, ctype); compilationQueued = true; } else if (compilationStarted || (ctype != CompileType.ERROR_CHECK_ONLY && ! compilationQueuedExplicit)) { if (! requeueForCompilation || ctype == CompileType.ERROR_CHECK_ONLY) { requeueForCompilation = true; requeueReason = reason; requeueType = ctype; } } } } @Override public void compileFinished(boolean successful, boolean classesKept) { compilationStarted = false; if (requeueForCompilation) { requeueForCompilation = false; if (classesKept) { compilationQueued = false; } else { compilationQueuedExplicit = (requeueType != CompileType.ERROR_CHECK_ONLY); watcher.scheduleCompilation(true, requeueReason, requeueType); } } else { compilationQueued = false; } if (isVisible() && classesKept) { if (successful) { info.messageImportant(Config.getString("editor.info.compiled")); } else { info.messageImportant(getCompileErrorLabel()); } } } private String getCompileErrorLabel() { return Config.getString("editor.info.compileError").replace("$", actions.getKeyStrokesForAction("compile").stream().map(KeyCodeCombination::getDisplayText).collect(Collectors.joining(" " + Config.getString("or") + " "))); }
| Called when all the breakpoints have been cleared. The editor should | update its display to show that no breakpoints are set. | @Override public void removeBreakpoints() { JavaFXUtil.runAfterCurrent(() -> clearAllBreakpoints()); }
| The editor must re-set all its breakpoints via the EditorWatcher | interface. | @Override public void reInitBreakpoints() { if (mayHaveBreakpoints) { mayHaveBreakpoints = false; for (int i = 1; i <= numberOfLines(); i++) { if (lineHasBreakpoint(i)) { if (watcher != null) watcher.breakpointToggleEvent(i, true); mayHaveBreakpoints = true; } } } }
| Determine whether this buffer has been modified. | | @return a boolean indicating whether the file is modified | @Override public boolean isModified() { return (saveState.isChanged()); }
| Returns if this editor is read-only. Accessor for the setReadOnly | property. | | @return a boolean indicating whether the editor is read-only. | @Override public boolean isReadOnly() { return !sourcePane.isEditable(); }
| Set this editor to read-only. | | @param readOnly The new readOnly value | @Override public void setReadOnly(boolean readOnly) { if (readOnly) { saveState.setState(StatusLabel.Status.READONLY); } sourcePane.setEditable(!readOnly); }
| Set this editor to display either the interface or the source code of | this class | | @param interfaceStatus If true, display class interface, otherwise source. | @Override @OnThread(Tag.FXPlatform) public void showInterface(boolean interfaceStatus) { interfaceToggle.getSelectionModel().select(interfaceStatus ? 1 : 0); }
| Tell whether the editor is currently displaying the interface or the | source of the class. | | @return True, if interface is currently shown, false otherwise. | public boolean isShowingInterface() { return viewingHTML.get(); }
| Returns the current caret location within the edited text. | | @return An object describing the current caret location. | @Override public SourceLocation getCaretLocation() { int caretOffset = sourcePane.getCaretPosition(); return getLineColumnFromOffset(caretOffset); }
| Sets the current Caret location within the edited text (source pane). | | @param location The location in the text to set the Caret to. | @throws IllegalArgumentException | if the specified TextLocation represents a position which | does not exist in the text. | @Override @OnThread(Tag.FXPlatform) public void setCaretLocation(SourceLocation location) { sourcePane.setCaretPosition(getOffsetFromLineColumn(location)); }
| Returns the SourceLocation object corresponding to the given offset in the | source text. | | @param offset The number of characters from the beginning of text (starting | from zero) | @return the SourceLocation object or null if the offset points outside the | text. | @Override @OnThread(Tag.FXPlatform) public SourceLocation getLineColumnFromOffset(int offset) { if (offset < 0) { return null; } Element map = sourceDocument.getDefaultRootElement(); int lineNumber = map.getElementIndex(offset); Element lineElement = map.getElement(lineNumber); if (offset > lineElement.getEndOffset()) { return null; } int column = offset - lineElement.getStartOffset(); return new SourceLocation(lineNumber+1, column+1); }
| Returns the location where the current selection (in the source pane) | begins. | | @return the current beginning of the selection or null if no text is | selected. | @Override public SourceLocation getSelectionBegin() { if (sourcePane.getCaretDot() == sourcePane.getCaretMark()) { return null; } int beginOffset = Math.min(sourcePane.getCaretDot(), sourcePane.getCaretMark()); return getLineColumnFromOffset(beginOffset); }
| Returns the location where the current selection (in the | source pane) ends. | | @return the current end of the selection or null if no text is selected. | @Override @OnThread(value = Tag.FXPlatform, ignoreParent = true) public SourceLocation getSelectionEnd() { if (sourcePane.getCaretDot() == sourcePane.getCaretMark()) { return null; } int endOffset = Math.max(sourcePane.getCaretDot(), sourcePane.getCaretMark()); return getLineColumnFromOffset(endOffset); } | Returns the source text between two locations as a string. | | @param begin The beginning of the text to get | @param end The end of the text to get | @return The text between the 'begin' and 'end' positions. | @throws IllegalArgumentException | if either of the specified TextLocations represent a position | which does not exist in the text. | @Override @OnThread(Tag.FXPlatform) public String getText(SourceLocation begin, SourceLocation end) { int first = getOffsetFromLineColumn(begin); int last = getOffsetFromLineColumn(end); int beginOffset = Math.min(first, last); int endOffset = Math.max(first, last); return sourceDocument.getText(beginOffset, endOffset - beginOffset); } | Request to the editor to replace the text between 'begin' and 'end' with | the given newText. If begin and end point to the same location, the text | is inserted. | | @param begin The start position of text to replace | @param end The end position of text to replace | @param newText The text to insert | @throws IllegalArgumentException | if either of the specified SourceLocation represent a position | which does not exist in the text. | @Override @OnThread(Tag.FXPlatform) public void setText(SourceLocation begin, SourceLocation end, String newText) { int start = getOffsetFromLineColumn(begin); int finish = getOffsetFromLineColumn(end); int beginOffset = Math.min(start, finish); int endOffset = Math.max(start, finish); if (beginOffset != endOffset) { sourceDocument.remove(beginOffset, endOffset - beginOffset); } sourceDocument.insertString(beginOffset, newText); }
| Request to the editor to mark the source text between two positions as selected. | | @param begin The start position of the selection | @param end The end position of the selection | @throws IllegalArgumentException | if either of the specified TextLocations represent a position | which does not exist in the text. | @Override public void setSelection(SourceLocation begin, SourceLocation end) { int start = getOffsetFromLineColumn(begin); int finish = getOffsetFromLineColumn(end); int selectionStart = Math.min(start, finish); int selectionEnd = Math.max(start, finish); sourcePane.setCaretPosition(selectionStart); sourcePane.moveCaretPosition(selectionEnd); }
| Translates a SourceLocation into an offset into the source text | held by the editor. | | @param location position to be translated | @return the offset into the content of this editor | @throws IllegalArgumentException | if the specified SourceLocation represent a position which does | not exist in the text. | @Override @OnThread(Tag.FXPlatform) public int getOffsetFromLineColumn(SourceLocation location) { int col = location.getColumn() - 1; int line = location.getLine() - 1; if (line < 0 || col < 0) { throw new IllegalArgumentException("line or column < 1"); } Element map = sourceDocument.getDefaultRootElement(); if (line >= map.getElementCount()) { throw new IllegalArgumentException("line=" + location.getLine() + " is out of bound"); } Element lineElement = sourceDocument.getDefaultRootElement() .getElement(line); int lineOffset = lineElement.getStartOffset(); int lineLen = lineElement.getEndOffset() - lineOffset; if (col > lineLen) { throw new IllegalArgumentException("column=" + location.getColumn() + " greater than line len=" + lineLen); } return lineOffset + col; } | Returns a property of the current editor. | | @param propertyKey The propertyKey of the property to retrieve. | @return the property value or null if it is not found | @Override public Object getProperty(String propertyKey) { return propertyMap.get(propertyKey); }
| Set a property for the current editor. Any existing property with | this key will be overwritten. | | @param propertyKey The property key of the new property | @param value The new property value | @Override public void setProperty(String propertyKey, Object value) { if ( propertyKey == null ) { return; } propertyMap.put(propertyKey,value); }
| Returns the length of the line indicated in the edited text. | Zero is a valid value if the given line has no characters in it. | | @param line the line in the text for which the length should be calculated, starting from 0 | @return the length of the line, -1 if line is invalid | @Override @OnThread(value = Tag.FXPlatform, ignoreParent = true) public int getLineLength(int line) { if (line < 0) { return -1; } Element lineElement = sourceDocument.getDefaultRootElement().getElement(line); if (lineElement == null) { return -1; } int startOffset = lineElement.getStartOffset(); return lineElement.getEndOffset() - startOffset; } | Returns the length of the source document. | | It is possible to obtain the line and column of the last character of text by using | this method together with the getLineColumnFromOffset() method. | | @return the source length (>= 0) | @Override @OnThread(Tag.FXPlatform) public int getTextLength() { return sourceDocument.getLength(); } | Return the number of lines in the source document. | @Override @OnThread(Tag.FXPlatform) public int numberOfLines() { return sourceDocument.getDefaultRootElement().getElementCount(); } | | @see bluej.editor.Editor#getParsedNode() | @Override @OnThread(Tag.FXPlatform) public ParsedCUNode getParsedNode() { return sourceDocument.getParser(); } | Schedule the ReparseRunner on the FX Platform queue, if it is not already scheduled. | private void scheduleReparseRunner() { if (reparseRunner == null) { reparseRunner = new ReparseRunner(this); JavaFXUtil.runPlatformLater(reparseRunner); } } | Informs the editor that the re-parse runner has de-scheduled itself due to lack | of work. | public void reparseRunnerFinished() { reparseRunner = null; } | A BlueJEvent was raised. Check whether it is one that we're interested in. | @Override public void blueJEvent(int eventId, Object arg, Project prj) { switch(eventId) { case BlueJEvent.DOCU_GENERATED : BlueJEvent.removeListener(this); refreshHtmlDisplay(); break; case BlueJEvent.DOCU_ABORTED : BlueJEvent.removeListener(this); info.message (Config.getString("editor.info.docAborted")); break; } } private void listenToChanges(MoeSyntaxDocument msd) { msd.getDocument().plainChanges().subscribe(c -> { if (!ignoreChanges) { boolean singleLineChange = !c.getInserted().contains("\n") && !c.getRemoved().contains("\n"); boolean inserted = !c.getInserted().isEmpty(); documentContentChanged(singleLineChange, inserted, c.getPosition(), c.getInsertionEnd() - c.getPosition(), c.getInserted()); } }); }
| A change has been made to the source code content. | private void documentContentChanged(boolean singleLineChange, boolean inserted, int offset, int insertionLength, String insertedContent) { if (respondingToChange) { return; } respondingToChange = true; if (!saveState.isChanged()) { saveState.setState(StatusLabel.Status.CHANGED); setChanged(); } if (!singleLineChange) saveState.setState(StatusLabel.Status.CHANGED); setChanged(); if (sourceIsCode && watcher != null) { scheduleCompilation(CompileReason.MODIFIED, CompileType.ERROR_CHECK_ONLY); } } clearMessage(); JavaFXUtil.runAfterCurrent(() -> { removeSearchHighlights(); currentSearchResult.setValue(null); errorManager.removeAllErrorHighlights(); errorManager.documentContentChanged(); showErrorOverlay(null, 0); }); actions.userAction(); if (inserted && "}".equals(insertedContent) && PrefMgr.getFlag(PrefMgr.AUTO_INDENT)) { JavaFXUtil.runAfterCurrent(() -> { if (offset + insertionLength <= getSourcePane().getLength() && sourcePane.getText(offset, offset + insertionLength).equals("}")) { actions.closingBrace(offset); } }); } recordEdit(false); scheduleReparseRunner(); respondingToChange = false; } | Clear the message in the info area. | public void clearMessage() { info.clear(); } | Display a message into the info area. | | @param msg the message to display | @Override @OnThread(Tag.FXPlatform) public void writeMessage(String msg) { info.message(msg); }
| Write a warning message into the info area. Typically some form of | unexpected behaviour has occurred. | | @param msg Description of the Parameter | public void writeWarningMessage(String msg) { info.message (msg); }
| User requests "save" |*/ public void userSave() { if (saveState.isSaved()) info.message(Config.getString("editor.info.noChanges")); else { try { save(); } catch (IOException ioe) { } // Note we can safely ignore the exception here: a message has // already been displayed in the editor status bar | |} | |} | |/** | Prints source code from Editor | | @param printerJob A PrinterJob to print to. | @Override @OnThread(Tag.FXPlatform) public FXRunnable printTo(PrinterJob printerJob, PrintSize printSize, boolean printLineNumbers, boolean printBackground) { ScopeColorsBorderPane scopeColorsPane = new ScopeColorsBorderPane(); MoeSyntaxDocument doc = new MoeSyntaxDocument(scopeColorsPane); doc.markAsForPrinting(); doc.copyFrom(sourceDocument); MoeEditorPane editorPane = doc.makeEditorPane(null, null); Label pageNumberLabel = new Label(""); String timestamp = new SimpleDateFormat("yyyy-MMM-dd HH:mm").format(new Date()); BorderPane header = new BorderPane(new Label(timestamp), null, pageNumberLabel, null, new Label(getTitle())); for (Node node : header.getChildren()) { node.setStyle(PrefMgr.getEditorFontFamilyCSS()); } header.setBackground(new Background(new BackgroundFill(Color.LIGHTGRAY, null, null))); header.setPadding(new Insets(5)); BorderPane rootPane = new BorderPane(editorPane, header, null, scopeColorsPane, null); scopeColorsPane.setManaged(false); scopeColorsPane.setVisible(false); rootPane.setBackground(null); double pixelWidth = printerJob.getJobSettings().getPageLayout().getPrintableWidth(); double pixelHeight = printerJob.getJobSettings().getPageLayout().getPrintableHeight(); Scene scene = new Scene(rootPane, pixelWidth, pixelHeight, Color.GRAY); Config.addEditorStylesheets(scene); editorPane.setPrinting(true, printSize, printLineNumbers); editorPane.setWrapText(true); editorPane.applyCss(); for (Node node : editorPane.lookupAll(".scroll-bar")) { node.setVisible(false); node.setManaged(false); } rootPane.requestLayout(); rootPane.layout(); rootPane.applyCss(); if (!printBackground) { for (int i = 0; i < doc.getDocument().getParagraphs().size(); i++) { doc.getDocument().setParagraphStyle(i, null); } } else { doc.notYetShown = false; doc.enableParser(true); doc.getParser(); doc.recalculateAllScopes(); } VirtualFlow<?, ?> virtualFlow = (VirtualFlow<?, ?>) editorPane.lookup(".virtual-flow"); FXConsumer<Integer> updatePageNumber = n -> { pageNumberLabel.setText("Page " + n); rootPane.requestLayout(); rootPane.layout(); rootPane.applyCss(); }; return () -> printPages(printerJob, rootPane, updatePageNumber, editorPane, virtualFlow); }
| Prints the editor, using multiple pages if necessary | | @param printerJob The overall printer job | @param printNode The node to print, each page. This may just be the | editor pane, or it may be a wrapper around the editor | pane that also shows a header and/or footer. | @param updatePageNumber A callback to update the header/footer each time | the page number changes. Cannot be null. | @param editorPane The editor pane to print | @param virtualFlow The virtual flow inside the editor pane. | @param <T> Parameter type of the VirtualFlow. Will be inferred. | @param <C> Parameter type of the VirtualFlow. Will be inferred. | @OnThread(Tag.FX) public static > void printPages(PrinterJob printerJob, Node printNode, FXConsumer<Integer> updatePageNumber, GenericStyledArea<?, ?, ?> editorPane, VirtualFlow<T, C> virtualFlow) { virtualFlow.scrollXToPixel(0); int topLine = 0; boolean lastPage = false; int editorLines = editorPane.getParagraphs().size(); int pageNumber = 1; while (topLine < editorLines && !lastPage) { virtualFlow.showAsFirst(topLine); virtualFlow.requestLayout(); virtualFlow.layout(); virtualFlow.applyCss(); List<C> visibleCells = new ArrayList<>(virtualFlow.visibleCells()); if (visibleCells.isEmpty()) { return; } C lastCell = visibleCells.get(visibleCells.size() - 1); lastPage = virtualFlow.getCellIfVisible(editorLines - 1).isPresent(); if (!lastPage) { double limitY = virtualFlow.cellToViewport(lastCell, 0, 0).getY(); editorPane.setClip(new javafx.scene.shape.Rectangle(editorPane.getWidth(), limitY)); topLine += visibleCells.size() - 1; } else { editorPane.setClip(new javafx.scene.shape.Rectangle(editorPane.getWidth(), editorPane.getHeight())); editorPane.setTranslateY(-virtualFlow.cellToViewport(virtualFlow.getCell(topLine), 0, 0).getY()); } updatePageNumber.accept(pageNumber); try { Thread.sleep(1000); } catch (InterruptedException e) { } printerJob.printPage(printNode); pageNumber += 1; } }
| Generalised version of print function. This is what is typically called | when print is initiated from within the source code editor menu. This | sets up and runs the print process as a separate lower priority thread. | public void print() { Optional<PrintChoices> choices = new PrintDialog(getWindow(), null).showAndWait(); if (!choices.isPresent()) return; PrinterJob job = JavaFXUtil.createPrinterJob(); if (job == null) { DialogManager.showErrorFX(getWindow(),"print-no-printers"); } else if (job.showPrintDialog(getWindow())) { FXRunnable printAction = printTo(job, choices.get().printSize, choices.get().printLineNumbers, choices.get().printHighlighting); new Thread("PRINT") { @Override @OnThread(value = Tag.FX, ignoreParent = true) public void run() { printAction.run(); job.endJob(); } }.start(); } }
| The editor has been closed. Hide the editor window now. | public void doClose() { setEditorVisible(false, false); if (watcher != null) { watcher.closeEvent(this); } } | Check whether TABs need expanding in this editor. If they do, return | true, and mark tabs as no longer needing expanding (i.e. subsequent | calls will return false). | public boolean checkExpandTabs() { if (tabsAreExpanded) { return false; } tabsAreExpanded = true; return true; } | Opens or close the replace panel (and if opening it, set the focus into | the find field). | protected void showReplacePanel() { if (!finder.isVisible()) { finder.setVisible(true); } finder.requestFindfieldFocus(); finder.setReplaceEnabled(true); }
| Implementation of "find-next" user function. |*/ public void findNext(boolean backwards) { if (currentSearchResult.get() == null || !currentSearchResult.get().validProperty().get()) { String search = sourcePane.getSelectedText(); | |if (search.isEmpty()) | |search = lastSearchString; | |doFind(search, true); | |} | |if (currentSearchResult.get() != null) | |{ | |if (backwards) | |currentSearchResult.get().selectPrev(); | |else{ currentSearchResult.get().selectNext(false); | |} | |} | |} | |/** | Do a find with info in the info area. | p.public boolean findString(String s, boolean backward, boolean ignoreCase, boolean wrap) { if (s.length() == 0) { info.message(" "); return false; } boolean found; if (backward){ found = doFind(s, ignoreCase) != null; } else { setCaretPositionForward(1); found = doFind(s, ignoreCase) != null; } StringBuilder msg = new StringBuilder(Config.getString("editor.find.find.label") + " "); msg.append(backward ? Config.getString("editor.find.backward") : Config.getString("editor.find.forward")); if (ignoreCase || wrap) { msg.append(" ("); } if (ignoreCase) { msg.append(Config.getString("editor.find.ignoreCase").toLowerCase()).append(", "); } if (wrap) { msg.append(Config.getString("editor.find.wrapAround").toLowerCase()).append(", "); } if (ignoreCase || wrap) { msg.replace(msg.length() - 2, msg.length(), "): "); } else { msg.append(": "); } msg.append(s); if (found) { info.message(msg.toString()); } else { info.message (msg.toString(), Config.getString("editor.info.notFound")); } return found; }
| Shows the preferences pane, and makes the given pane index (i.e. given tab index | in the preferences) the active showing tab. 0 is general, 1 is key bindings, and so on. | If in doubt, pass 0. | public void showPreferences(int paneIndex) { watcher.showPreferences(paneIndex); } @Override @OnThread(Tag.FXPlatform) public void setLastModified(long lastModified) { this.lastModified = lastModified; }
| An interface for dealing with search results. | public static interface FindNavigator {
| Highlights all search results | public void highlightAll();
| Selects the next search result, wrapping if necessary | | @param canBeAtCurrentPos If true, "next" result can include one beginning |* at the start of the current selection (e.g. when * typing in search field). If false, next result * must be beyond start of selection (e.g. when pressing | find next button). | public void selectNext(boolean canBeAtCurrentPos);
| Selects the previous search result, wrapping backwards if necessary. | public void selectPrev();
| Is this search result still valid? Search results get invalidated | by modifying the document, or performing a new search. | public BooleanExpression validProperty();
| Replaces the current selected search result with the given replacement | string, and returns the updated search. THis search object will no longer | be valid, and you should switch to using the returned result. | public FindNavigator replaceCurrent(String replacement);
| Replaces all search results with the given replacement string. | This search result will no longer be valid, but there's no point | searching again, as all instances will have been replaced. | public void replaceAll(String replacement); }
| Do a find forwards or backwards, and highlight all cases. | | The case after the cursor (if backwards is false) or before it (if | backwards is true) is given a special highlight. | | Returns null if nothing was found. If something was found, gives | you back a class you can use to cycle between search results. It | becomes invalid next time doFind is called, or if the document is modified. | p.public FindNavigator doFind(String searchFor, boolean ignoreCase) { removeSearchHighlights(); sourcePane.moveTo(Math.min(sourcePane.getAnchor(), sourcePane.getCaretPosition())); lastSearchString = searchFor; String content = sourcePane.getText(); int curPosition = 0; boolean finished = false; List<Integer> foundStarts = new ArrayList<>(); while (!finished) { int foundPos = findSubstring(content, searchFor, ignoreCase, false, curPosition); if (foundPos != -1) { foundStarts.add(foundPos); curPosition = foundPos + searchFor.length(); } else { finished = true; } } currentSearchResult.set(foundStarts.isEmpty() ? null : new FindNavigator() { @Override public void highlightAll() { for (Integer foundPos : foundStarts) { sourceDocument.markFindResult(foundPos, foundPos + searchFor.length()); } } @Override public FindNavigator replaceCurrent(String replacement) { if (!sourcePane.getSelectedText().equals(searchFor)) { selectNext(true); } int pos = sourcePane.getSelection().getStart(); sourceDocument.replace(pos, searchFor.length(), replacement); sourcePane.setCaretPosition(pos + searchFor.length()); JavaFXUtil.runAfter(Duration.millis(200), () -> sourcePane.requestFollowCaret()); return doFind(searchFor, ignoreCase); } public void replaceAll(String replacement) { foundStarts.stream().sorted(Comparator.reverseOrder()).forEach(pos -> sourceDocument.replace(pos, searchFor.length(), replacement) ); } @Override public void selectNext(boolean canBeAtCurrentPos) { if (validProperty().get()) { int selStart = sourcePane.getSelection().getStart(); int position = foundStarts.stream() .filter(pos -> pos > selStart || (canBeAtCurrentPos && pos == selStart)) .findFirst() .orElse(foundStarts.get(0)); select(position); } } private void select(int position) { sourcePane.select(position, position + searchFor.length()); sourcePane.requestFollowCaret(); } @Override public void selectPrev() { if (validProperty().get()) { int selStart = sourcePane.getSelection().getStart(); int position = Utility.streamReversed(foundStarts) .filter(pos -> pos < selStart) .findFirst() .orElse(foundStarts.get(foundStarts.size() - 1)); select(position); } } @Override public BooleanExpression validProperty() { return currentSearchResult.isEqualTo(this); } }); return currentSearchResult.get(); }
| Transfers caret to user specified line number location. | public void goToLine() { final int numberOfLines = numberOfLines(); GoToLineDialog goToLineDialog = new GoToLineDialog(fxTabbedEditor.getWindow()); goToLineDialog.setRangeMax(numberOfLines); Optional<Integer> o = goToLineDialog.showAndWait(); o.ifPresent(n -> { setSelection(n , 1, 0); ensureCaretVisible(); }); } private void ensureCaretVisible() { sourcePane.requestFollowCaret(); } | Implementation of "toggle-interface-view" user function. The menu has |* already been changed - now see what it is and do it. */ public void toggleInterface() { if (isShowingInterface()) switchToSourceView(); else{ switchToInterfaceView(); | |} | |} | |/** | Switch on the source view (if it isn't showing already). | private void switchToSourceView() { if (!viewingHTML.get()) { return; } resetMenuToolbar(true); viewingHTML.set(false); interfaceToggle.getSelectionModel().selectFirst(); watcher.showingInterface(false); clearMessage(); }
| Switch on the javadoc interface view (it it isn't showing already). If | necessary, generate it first. | private void switchToInterfaceView() { if (viewingHTML.get()) { return; } resetMenuToolbar(false); try { save(); info.message(Config.getString("editor.info.loadingDoc")); boolean generateDoc = ! docUpToDate(); if (generateDoc) { info.message(Config.getString("editor.info.generatingDoc")); BlueJEvent.addListener(this); if (watcher != null) { watcher.generateDoc(); } } else { refreshHtmlDisplay(); } interfaceToggle.getSelectionModel().selectLast(); viewingHTML.set(true); watcher.showingInterface(true); } catch (IOException ioe) { } }
| Traverses the document using the given traversal operation (next parameter), | until the stopWhen test returns true, beginning at the start node. The start | node is not tested. Once the traversal returns null, this method returns null. | private static org.w3c.dom.Node findHTMLNode(org.w3c.dom.Node start, UnaryOperator<org.w3c.dom.Node> next, Predicate<org.w3c.dom.Node> stopWhen) { org.w3c.dom.Node n = start; while (n != null) { n = next.apply(n); if (n != null && stopWhen.test(n)) return n; } return null; }
| Refresh the HTML display. | private void refreshHtmlDisplay() { try { File urlFile = new File(getDocPath()); if (!urlFile.exists()) { return; } URL myURL = urlFile.toURI().toURL(); String location = htmlPane.getEngine().getLocation(); if (Objects.equals(location == null ? null : new URL(location), myURL)) { htmlPane.getEngine().reload(); } else { htmlPane.getEngine().load(myURL.toString()); } info.message(Config.getString("editor.info.docLoaded")); } catch (IOException exc) { info.message (Config.getString("editor.info.docDisappeared"), getDocPath()); Debug.reportError("loading class interface failed: " + exc); } }
| Sets up the processor for loaded Javdoc. Currently this inserts a link | next to a method name to allow you to jump back to the BlueJ source, if | there is source code available. | private void setupJavadocMangler() { JavaFXUtil.addChangeListenerPlatform(htmlPane.getEngine().documentProperty(), doc -> { if (doc != null) {
| Javadoc looks like this: | |<a id="sampleMethod(java.lang.String)"> <!-- --> </a> <ul> <li> <h4>sampleMethod</h4> */ // First find the anchor. Ignore anchors with ids that do not end in a closing bracket (they are not methods): | |NodeList anchors = doc.getElementsByTagName("a"); for (int i = 0; i < anchors.getLength(); i++) { org.w3c.dom.Node anchorItem = anchors.item(i); org.w3c.dom.Node anchorName = anchorItem.getAttributes().getNamedItem("id"); if (anchorName != null && anchorName.getNodeValue() != null && anchorName.getNodeValue().endsWith(")")) { // Then find the ul child, then the li child of that, then the h4 child of that: org.w3c.dom.Node ulNode = findHTMLNode(anchorItem, org.w3c.dom.Node::getNextSibling, n -> "ul".equals(n.getLocalName())); if (ulNode == null) continue; org.w3c.dom.Node liNode = findHTMLNode(ulNode.getFirstChild(), org.w3c.dom.Node::getNextSibling, n -> "li".equals(n.getLocalName())); if (liNode == null) continue; org.w3c.dom.Node headerNode = findHTMLNode(liNode.getFirstChild(), org.w3c.dom.Node::getNextSibling, n -> "h4".equals(n.getLocalName())); if (headerNode != null) { // Make a link, and set a listener for it: org.w3c.dom.Element newLink = doc.createElement("a"); newLink.setAttribute("style", "padding-left: 2em;cursor:pointer;"); newLink.insertBefore(doc.createTextNode("[Show source in BlueJ]"), null); headerNode.insertBefore(newLink, null); ((EventTarget) newLink).addEventListener("click", e -> { String[] tokens = anchorName.getNodeValue().split("[(,)]"); List<String> paramTypes = new ArrayList<>(); for (int t = 1; t < tokens.length; t++) { | |paramTypes.add(tokens[t]); | |} | |focusMethod(tokens[0].equals("<init>") ? windowTitle : tokens[0], paramTypes); }, false); } } } } | |}); | |} | |// -------------------------------------------------------------------- | |/** | Check whether javadoc file is up to date. | | @return True is the currently existing documentation is up-to-date. | private boolean docUpToDate() { if (filename == null) { return true; } try { File src = new File(filename); File doc = new File(docFilename); if (!doc.exists() || (src.exists() && (src.lastModified() > doc.lastModified()))) { return false; } } catch (Exception e) { e.printStackTrace(); return false; } return true; } | This method resets the value of the menu and toolbar according to the view | | @param sourceView true if called from source view setup; false from documentation view setup | private void resetMenuToolbar(boolean sourceView) { if (sourceView) actions.makeAllAvailable(); else{ actions.makeAllUnavailableExcept("close", "toggle-interface-view"); } } | Implementation of "toggle-breakpoint" user function. |*/ public void toggleBreakpoint() { if (!viewingCode()) { info.message (" "); return; } toggleBreakpoint(sourcePane.getCaretPosition()); } // -------------------------------------------------------------------- /** * Toggle a breakpoint at a given position. public void toggleBreakpoint(int pos) { if (positionHasBreakpoint(pos)) { setUnsetBreakpoint(pos, false); } else { setUnsetBreakpoint(pos, true); } } | Clear all known breakpoints. | private void clearAllBreakpoints() { if (mayHaveBreakpoints) { for (int i = 1; i <= numberOfLines(); i++) { if (lineHasBreakpoint(i)) { doRemoveBreakpoint(i); } } mayHaveBreakpoints = false; } } | Check whether a position in the current document has a breakpoint set | (should only be called after a check that the current document is the source doc) | private boolean positionHasBreakpoint(int pos) { return lineHasBreakpoint(getLineNumberAt(pos)); } | Check whether a line in the source document has a breakpoint set | private boolean lineHasBreakpoint(int lineNo) { return sourceDocument.getParagraphAttributes(lineNo).contains(ParagraphAttribute.BREAKPOINT); } | Try to set or remove a breakpoint (depending on the parameter) at the | given position in the source document. Informs the watcher. | private void setUnsetBreakpoint(int pos, boolean set) { if (watcher != null) { int line = getLineNumberAt(pos); String result = watcher.breakpointToggleEvent(line, set); if (result == null) { if (set) { mayHaveBreakpoints = true; } sourceDocument.setParagraphAttributesForLineNumber(line, Collections.singletonMap(ParagraphAttribute.BREAKPOINT, set)); } else { info.message (result); } repaint(); } else { info.message (Config.getString("editor.info.cannotSetBreak")); } } | Remove a breakpoint without question. | private void doRemoveBreakpoint(int lineNumber) { sourceDocument.setParagraphAttributesForLineNumber(lineNumber, Collections.singletonMap(ParagraphAttribute.BREAKPOINT, false)); repaint(); } | Try to set or remove a step mark (depending on the parameter) at the | given position. | | @param pos A position in the line where we'd like the step mark. | private void setStepMark(int lineNumber) { removeStepMark(); sourceDocument.setParagraphAttributesForLineNumber(lineNumber, Collections.singletonMap(ParagraphAttribute.STEP_MARK, true)); currentStepLineNumber = lineNumber; repaint(); } private void repaint() { }
| Return a boolean representing whether in source editing view | private boolean viewingCode() { return sourceIsCode && (!viewingHTML.get()); }
| Find and return a line (by line number) in the source document | private Element getSourceLine(int lineNo) { Element map = sourceDocument.getDefaultRootElement(); if (map.getElementCount() >= lineNo) { return sourceDocument.getDefaultRootElement().getElement(lineNo - 1); } else { return null; } } | Return the number of the line containing position 'pos' in the source document. | private int getLineNumberAt(int pos) { return sourceDocument.getDefaultRootElement().getElementIndex(pos) + 1; }
| Revert the buffer contents to the last saved version. Do not ask any | question - just do it. Must have a file name. | public void doReload() { removeSearchHighlights(); Reader reader = null; try { FileInputStream inputStream = new FileInputStream(filename); reader = new InputStreamReader(inputStream, characterSet); sourcePane.read(reader); try { reader.close(); inputStream.close(); } catch (IOException ioe) { } File file = new File(filename); setLastModified(file.lastModified()); sourceDocument.enableParser(false); listenToChanges(sourceDocument); saveState.setState(StatusLabel.Status.SAVED); setChanged(); setSaved(); scheduleReparseRunner(); scheduleCompilation(CompileReason.LOADED, CompileType.ERROR_CHECK_ONLY); } catch (FileNotFoundException ex) { info.message (Config.getString("editor.info.fileDisappeared")); } catch (IOException ex) { info.message (Config.getString("editor.info.fileReadError")); setChanged(); } finally { try { if (reader != null) { reader.close(); } } catch (IOException ioe) { } if (finder != null && finder.isVisible()) { findNext(false); } } }
| Checks that current status of syntax highlighting option is consistent | with desired option eg off/on. Called when refreshing or making visible | to pick up any Preference Manager changes to this functionality | private void checkBracketStatus() { matchBrackets = PrefMgr.getFlag(PrefMgr.MATCH_BRACKETS); if (matchBrackets) { doBracketMatch(); } else { removeBracketHighlight(); } }
| Toggle the editor's 'compiled' status. This affects display (left-hand margin colour) | and whether breakpoints can be set. | private void setCompileStatus(boolean compiled) { actions.getActionByName("toggle-breakpoint").setEnabled(compiled && viewingCode()); compiledProperty.set(compiled); }
| Set the saved/changed status of this buffer to SAVED. | private void setSaved() { saveState.setState(StatusLabel.Status.SAVED); if (watcher != null) { watcher.saveEvent(this); } }
| Buffer just went from saved to changed state (called by StatusLabel) | private void setChanged() { if (ignoreChanges) { return; } setCompileStatus(false); if (watcher != null) { watcher.modificationEvent(this); } }
| Notification (from the caret) that the caret position has moved. | public void caretMoved() { int caretPos = sourcePane.getCaretPosition(); showErrorPopupForCaretPos(caretPos, false); if (matchBrackets) { doBracketMatch(); } actions.userAction(); if (oldCaretLineNumber != getLineNumberAt(caretPos) && isOpen()) { recordEdit(true); cancelFreshState(); JavaFXUtil.runAfterCurrent(() -> { ensureCaretVisible(); layout(); }); } oldCaretLineNumber = getLineNumberAt(caretPos); } private void showErrorPopupForCaretPos(int caretPos, boolean mousePosition) { ErrorDetails err = caretPos == -1 ? null : errorManager.getErrorAtPosition(caretPos); if (err != null) { showErrorOverlay(err, caretPos); } else { if (errorDisplay != null && (!mousePosition || !errorDisplay.details.containsPosition(sourcePane.getCaretPosition()))) { showErrorOverlay(null, caretPos); } } } | Schedule compilation, if any edits have occurred since last compile. | @OnThread(Tag.FXPlatform) public void cancelFreshState() { if (sourceIsCode && saveState.isChanged()) { scheduleCompilation(CompileReason.MODIFIED, CompileType.ERROR_CHECK_ONLY); } } @Override @OnThread(Tag.FXPlatform) public void focusMethod(String methodName, List<String> paramTypes) { focusMethod(methodName, paramTypes, new NodeAndPosition<ParsedNode>(getParsedNode(), 0, 0), 0); } private boolean focusMethod(String methodName, List<String> paramTypes, NodeAndPosition<ParsedNode> tree, int offset) { if (tree.getNode().getNodeType() == ParsedNode.NODETYPE_METHODDEF && methodName.equals(tree.getNode().getName()) && paramsMatch(tree.getNode(), paramTypes)) { switchToSourceView(); sourcePane.setCaretPosition(offset); sourcePane.requestFollowCaret(); return true; } else { for (NodeAndPosition<ParsedNode> child : (Iterable<NodeAndPosition<ParsedNode>>)(() -> tree.getNode().getChildren(0))) { if (focusMethod(methodName, paramTypes, child, offset + child.getPosition())) return true; } } return false; }
| Checks if the parameter types match the parameters of the given node, | if it is a method node. (If not a method node, false is returned) | @param node The node which should be a MethodNode. | @param paramTypes Parameter types. null matches anything. | @return | private boolean paramsMatch(ParsedNode node, List<String> paramTypes) { if (paramTypes == null) return true; if (node instanceof MethodNode) { MethodNode methodNode = (MethodNode)node; if (methodNode.getParamTypes().size() != paramTypes.size()) return false; for (int i = 0; i < paramTypes.size(); i++) { JavaEntity paramType = methodNode.getParamTypes().get(i); if (!paramType.getName().equals(paramTypes.get(i))) return false; } return true; } return false; } | Show the given error overlay, or hide the existing overlay | @param details If non-null, show the given error details. If null, hide existing overlay. | @param displayPosition The character position at which to show. | private void showErrorOverlay(ErrorDetails details, int displayPosition) { if (details != null) { if (errorDisplay == null || errorDisplay.details != details) { if (errorDisplay != null) { ErrorDisplay old = errorDisplay; old.popup.hide(); } Bounds pos = null; boolean before = false; try { pos = sourcePane.getCharacterBoundsOnScreen(displayPosition, displayPosition + 1).orElse(null); if (pos == null) { pos = sourcePane.getCharacterBoundsOnScreen(displayPosition - 1, displayPosition).orElse(null); before = true; } } catch (IllegalArgumentException e) { } if (pos == null) return; int xpos = (int)(before ? pos.getMaxX() : pos.getMinX()); int ypos = (int)(pos.getMinY() + (4*pos.getHeight()/3)); errorDisplay = new ErrorDisplay(details); ErrorDisplay newDisplay = errorDisplay; newDisplay.createPopup(); newDisplay.popup.setAnchorLocation(PopupWindow.AnchorLocation.WINDOW_TOP_LEFT); newDisplay.popup.setAnchorX(xpos); newDisplay.popup.setAnchorY(ypos); newDisplay.popup.show(getWindow()); if (watcher != null) { watcher.recordShowErrorMessage(details.identifier, Collections.emptyList()); } } } else if (errorDisplay != null) { ErrorDisplay old = errorDisplay; old.popup.hide(); errorDisplay = null; } }
| Returns the position of the matching bracket for the source pane's | current caret position. Returns -1 if not found or not valid/appropriate | | @return the int representing bracket position | public int getBracketMatch() { int pos = -1; int caretPos = sourcePane.getCaretPosition(); if (caretPos != 0) { caretPos--; } pos = TextUtilities.findMatchingBracket(sourceDocument, caretPos); return pos; } | Delegates bracket matching to the source pane's caret | private void doBracketMatch() { int originalPos = getSourcePane().getCaretPosition(); int matchBracket = getBracketMatch(); JavaFXUtil.runPlatformLater(() -> { removeBracketHighlight(); if (matchBracket != -1 && originalPos > 0 && originalPos == getSourcePane().getCaretPosition()) { sourceDocument.addStyle(originalPos - 1, originalPos, MoeSyntaxDocument.MOE_BRACKET_HIGHLIGHT); sourceDocument.addStyle(matchBracket, matchBracket + 1, MoeSyntaxDocument.MOE_BRACKET_HIGHLIGHT); } }); } private void removeBracketHighlight() { sourceDocument.removeStyleThroughout(MoeSyntaxDocument.MOE_BRACKET_HIGHLIGHT); } | Set the window title to show the defined title, or else the file name. | private void setWindowTitle() { String title = windowTitle; if (title == null) { if (filename == null) { title = "Moe: <no name>"; } else { title = "Moe: " + filename; } } fxTab.setWindowTitle(title); } | Return the path to the class documentation. | private String getDocPath() { return docFilename; }
| Gets the resource attribute of the MoeEditor object | private String getResource(String name) { return Config.getPropString(name, null, resources); }
| Create all the Window components. | | @param projectResolver the entity resolver for the project. If this is null | then it is assumed that this editor is for a README or other plain text file. | private void initWindow(EntityResolver projectResolver) { BorderPane bottomArea = new BorderPane(); JavaFXUtil.addStyleClass(bottomArea, "moe-bottom-bar"); finder=new FindPanel(this); finder.setVisible(false); saveState = new StatusLabel(StatusLabel.Status.SAVED, this, errorManager); info = new Info(); BorderPane commentsPanel = new BorderPane(); commentsPanel.setCenter(info); commentsPanel.setRight(saveState); BorderPane.setAlignment(info, Pos.TOP_LEFT); BorderPane.setAlignment(saveState, Pos.CENTER_RIGHT); JavaFXUtil.addStyleClass(commentsPanel, "moe-bottom-status-row"); commentsPanel.styleProperty().bind(PrefMgr.getEditorFontCSS(false)); bottomArea.setBottom(commentsPanel); bottomArea.setTop(finder); setBottom(bottomArea); if (projectResolver != null) { sourceDocument = new MoeSyntaxDocument(projectResolver, this); } else { sourceDocument = new MoeSyntaxDocument((ScopeColors) null); } listenToChanges(sourceDocument); sourcePane = sourceDocument.makeEditorPane(this, compiledProperty); sourcePane.setCaretPosition(0); undoManager = new MoeUndoManager(sourcePane); sourcePane.setUndoManager(undoManager.getUndoManager()); JavaFXUtil.addChangeListenerPlatform(sourcePane.caretPositionProperty(), e -> caretMoved()); JavaFXUtil.addChangeListenerPlatform(sourcePane.estimatedScrollYProperty(), d -> { JavaFXUtil.runAfterCurrent(() -> { showErrorPopupForCaretPos(sourcePane.getCaretPosition(), false); }); }); sourcePane.setMouseOverTextDelay(java.time.Duration.ofMillis(400)); sourcePane.addEventHandler(MouseOverTextEvent.ANY, this::mouseOverText); Nodes.addInputMap(sourcePane, org.fxmisc.wellbehaved.event.InputMap.consume(EventPattern.keyPressed(KeyCode.ESCAPE), e -> { if (finder != null && finder.isVisible()) { finder.close(); } })); HBox editorPane = new HBox(); htmlPane = new WebView(); htmlPane.visibleProperty().bind(viewingHTML); editorPane.setFillHeight(true); BorderPane background = new BorderPane(); JavaFXUtil.addStyleClass(background, "moe-background"); setCenter(new StackPane(background, new VirtualizedScrollPane<>(sourcePane), htmlPane)); actions = MoeActions.getActions(this); menubar = createMenuBar(); actions.updateKeymap(); fxMenus.clear(); menubar.getMenus().forEach(fxMenus::add); ComboBox<String> interfaceSelector = createInterfaceSelector(); interfaceSelector.setDisable(!sourceIsCode); Region toolbar = createToolbar(interfaceSelector.heightProperty()); BorderPane topBar = new BorderPane(null, null, interfaceSelector, null, toolbar); topBar.getStyleClass().add("moe-top-bar"); setTop(topBar); sourcePane.setContextMenu(createPopupMenu()); } | Create the editor's menu bar. | private MenuBar createMenuBar() { return new MenuBar( createMenu("class", "save - print - close"), createMenu("edit", "undo redo - cut-to-clipboard copy-to-clipboard paste-from-clipboard - indent-block deindent-block comment-block uncomment-block autoindent - insert-method add-javadoc"), createMenu("tools", "find find-next find-next-backward replace go-to-line - compile toggle-breakpoint - toggle-interface-view"), createMenu("option", "increase-font decrease-font reset-font - key-bindings preferences") ); } | Create the pop up menu bar | private ContextMenu createPopupMenu() { ContextMenu popup = new ContextMenu(); String [] popupKeys="cut copy paste".split(" "); for (String popupKey : popupKeys) { String label = Config.getString("editor." + popupKey + LabelSuffix); String actionName = getResource(popupKey + ActionSuffix); MoeAbstractAction action = actions.getActionByName(actionName); if (action == null) { Debug.message("Moe: cannot find action " + popupKey); } else { MenuItem menuItem = action.makeContextMenuItem(); menuItem.setText(label); popup.getItems().add(menuItem); } } return popup; } | Create a single menu for the editor's menu bar. The key for the menu (as | defined in moe.properties) is supplied. | private Menu createMenu(String titleKey, String itemList) { MenuItem item; String label; Menu menu = new Menu(Config.getString("editor." + titleKey + LabelSuffix)); String[] itemKeys = itemList.split(" "); for (String itemKey : itemKeys) { if (itemKey.equals("-")) { menu.getItems().add(new SeparatorMenuItem()); } else { MoeAbstractAction action = actions.getActionByName(itemKey); if (action == null) { Debug.message("Moe: cannot find action " + itemKey); } else if ( !( Config.isMacOS() && titleKey.toLowerCase().equals("option") && itemKey.toLowerCase().equals("preferences") ) ) { item = action.makeMenuItem(); menu.getItems().add(item); label = Config.getString("editor." + itemKey + LabelSuffix); item.setText(label); } } } return menu; } | Create the toolbar. | | @return The toolbar component, ready made. | private Region createToolbar(DoubleExpression buttonHeight) { TilePane tilePane = new TilePane(Orientation.HORIZONTAL, createToolbarButton("compile", buttonHeight), createToolbarButton("undo", buttonHeight), createToolbarButton("cut", buttonHeight), createToolbarButton("copy", buttonHeight), createToolbarButton("paste", buttonHeight), createToolbarButton("find", buttonHeight), createToolbarButton("close", buttonHeight) ); tilePane.setPrefColumns(tilePane.getChildren().size()); return JavaFXUtil.withStyleClass(tilePane, "moe-top-bar-buttons"); } | Create a button on the toolbar. | @param key The internal key identifying the action and label | @param buttonHeight The height of the buttons | private ButtonBase createToolbarButton(String key, DoubleExpression buttonHeight) { final String label = Config.getString("editor." + key + LabelSuffix); ButtonBase button; String actionName = getResource(key + ActionSuffix); if (actionName == null) { actionName = key; } MoeAbstractAction action = actions.getActionByName(actionName); if (action != null) { button = action.makeButton(); button.setText(label); } else { button = new Button("Unknown"); } if (action == null) { button.setDisable(true); Debug.message("Moe: action not found for button " + label); } if (isNonReadmeAction(actionName) && !sourceIsCode){ action.setEnabled(false); } button.setFocusTraversable(false); button.setMaxWidth(Double.MAX_VALUE); button.prefHeightProperty().bind(buttonHeight); button.setMaxHeight(Double.MAX_VALUE); button.getStyleClass().add("toolbar-" + key + "-button"); return button; }
| Create a combo box for the toolbar | private ComboBox createInterfaceSelector() { String[] choiceStrings = {implementationString, interfaceString }; interfaceToggle = new ComboBox<String>(FXCollections.observableArrayList(choiceStrings)); interfaceToggle.setFocusTraversable(false); JavaFXUtil.addChangeListenerPlatform(interfaceToggle.valueProperty(), v -> { if (v.equals(interfaceString)) switchToInterfaceView(); else{ switchToSourceView(); } }); return interfaceToggle; }
| Sets the find panel to be visible and if there is a selection/or previous search | it starts a automatic find of what was selected in the text/or previous search. If | it is the source pane then the replace button is enabled; if it is the interface pane | then the replace button and replace panel are set to disabled and invisible | public void initFindPanel() { finder.displayFindPanel(sourcePane.getSelectedText()); }
| Sets the caret forward by the value indicated if this does not | exceed the document length; Else it sets it to the document length | public void setCaretPositionForward(int caretPos) { int docLength = sourcePane.getLength(); if (sourcePane.getCaretPosition() + caretPos <= docLength) { sourcePane.setCaretPosition(sourcePane.getCaretPosition() + caretPos); } else { sourcePane.setCaretPosition(docLength); } }
| Get the source pane. | public MoeEditorPane getSourcePane() { return sourcePane; } public WebView getHTMLPane() { return htmlPane; }
| Get the current pane. | public MoeEditorPane getCurrentTextPane() { return sourcePane; }
| Get the source document that this editor is editing. | @Override @OnThread(Tag.FXPlatform) public MoeSyntaxDocument getSourceDocument() { return sourceDocument; }
| Removes the selected highlights (in both the source/doc pane) | Note: the other highlights such as the brackets etc remain | public void removeSearchHighlights() { sourceDocument.removeSearchHighlights(); }
| Create and pop up the content assist (code completion) dialog. | protected void createContentAssist() { ParsedCUNode parser = sourceDocument.getParser(); ExpressionTypeInfo suggests = parser == null ? null : parser.getExpressionType(sourcePane.getCaretPosition(), sourceDocument); if (suggests != null) { LocatableToken suggestToken = suggests.getSuggestionToken();
| | |PopulateCompletionsWorker worker = new PopulateCompletionsWorker(suggests, suggestToken, xpos, ypos); | |worker.execute(); AssistContent[] possibleCompletions = ParseUtils.getPossibleCompletions(suggests, javadocResolver, null); Arrays.sort(possibleCompletions, AssistContent.getComparator()); List<SuggestionDetails> suggestionDetails = Arrays.stream(possibleCompletions) .map(AssistContentThreadSafe::new) .map(ac -> new SuggestionDetailsWithHTMLDoc(ac.getName(), ExpressionCompletionCalculator.getParamsCompletionDisplay(ac), ac.getType(), SuggestionShown.COMMON, ac.getDocHTML())) .collect(Collectors.toList()); int originalPosition = suggestToken == null ? sourcePane.getCaretPosition() : suggestToken.getPosition(); Bounds screenPos; if (suggestToken == null) { screenPos = sourcePane.getCaretBounds().orElse(null); } else { screenPos = sourcePane.getCharacterBoundsOnScreen(originalPosition, originalPosition + 1).orElse(null); if (screenPos == null) { screenPos = sourcePane.getCharacterBoundsOnScreen(originalPosition - 1, originalPosition).orElse(null);; screenPos = new BoundingBox(screenPos.getMaxX(), screenPos.getMinY(), 0, screenPos.getHeight()); } } if (screenPos == null) return; Bounds spLoc = sourcePane.screenToLocal(screenPos); StringExpression editorFontCSS = PrefMgr.getEditorFontCSS(true); SuggestionList suggestionList = new SuggestionList(new SuggestionListParent() { @Override @OnThread(Tag.FX) public StringExpression getFontCSS() { return editorFontCSS; } @Override public double getFontSize() { return PrefMgr.getEditorFontSize().get(); } @Override public void setupSuggestionWindow(Stage window) { sourcePane.setFakeCaret(true); } }, suggestionDetails, null, SuggestionShown.RARE, i -> { }, new SuggestionListListener() { @Override public @OnThread(Tag.FXPlatform) void suggestionListChoiceClicked(SuggestionList suggestionList, int highlighted) { if (highlighted != -1) { codeComplete(possibleCompletions[highlighted], originalPosition, sourcePane.getCaretPosition(), suggestionList); } } @Override public Response suggestionListKeyTyped(SuggestionList suggestionList, KeyEvent event, int highlighted) { if (event.getCharacter().equals("\b") || event.getCharacter().equals("\u007F")) { } else if (event.getCharacter().equals("\n")) { suggestionListChoiceClicked(suggestionList, highlighted); return Response.DISMISS; } else { sourcePane.insertText(sourcePane.getCaretPosition(), event.getCharacter()); } String prefix = sourcePane.getText(originalPosition, sourcePane.getCaretPosition()); suggestionList.calculateEligible(prefix, true, false); suggestionList.updateVisual(prefix); return Response.CONTINUE; } @Override public @OnThread(Tag.FXPlatform) SuggestionList.SuggestionListListener.Response suggestionListKeyPressed(SuggestionList suggestionList, KeyEvent event, int highlighted) { switch (event.getCode()) { case ESCAPE: return Response.DISMISS; case ENTER: case TAB: suggestionListChoiceClicked(suggestionList, highlighted); return Response.DISMISS; case BACK_SPACE: sourcePane.deletePreviousChar(); break; case DELETE: sourcePane.deleteNextChar(); break; } if (sourcePane.getCaretPosition() < originalPosition) { return Response.DISMISS; } else { return Response.CONTINUE; } } @Override public @OnThread(Tag.FXPlatform) void hidden() { sourcePane.setFakeCaret(false); } }); String prefix = sourcePane.getText(originalPosition, sourcePane.getCaretPosition()); suggestionList.calculateEligible(prefix, true, false); suggestionList.updateVisual(prefix); suggestionList.highlightFirstEligible(); suggestionList.show(sourcePane, spLoc); Position pos = sourcePane.offsetToPosition(originalPosition, Bias.Forward); watcher.recordCodeCompletionStarted(pos.getMajor() + 1, pos.getMinor() + 1, null, null, prefix, suggestionList.getRecordingId()); } else {
| | |//no completions found. no need to search. | |info.message ("No completions available."); CodeCompletionDisplay codeCompletionDlg = new CodeCompletionDisplay(this, watcher, null, new AssistContent[0], null); initialiseContentAssist(codeCompletionDlg, xpos, ypos); */ } } | |/** | codeComplete prints the selected text in the editor | private void codeComplete(AssistContent selected, int prefixBegin, int prefixEnd, SuggestionList suggestionList) { String start = selected.getName(); List<ParamInfo> params = selected.getParams(); if (params != null) start += "("; sourcePane.select(prefixBegin, prefixEnd); String prefix = sourcePane.getSelectedText(); insertText(start, false); String inserted = start; if (params != null) { int selLoc = sourcePane.getCaretPosition(); if (!params.isEmpty()) { final String joinedParams = params.stream().map(ParamInfo::getDummyName).collect(Collectors.joining(", ")); insertText(joinedParams, false); inserted += joinedParams; } insertText(")", false); inserted += ")"; if (params.size() > 0) sourcePane.select(selLoc, selLoc + params.get(0).getDummyName().length()); } Position prefixBeginPos = sourcePane.offsetToPosition(prefixBegin, Bias.Forward); watcher.recordCodeCompletionEnded(prefixBeginPos.getMajor() + 1, prefixBeginPos.getMinor() + 1, null, null, prefix, inserted, suggestionList.getRecordingId()); try { save(); } catch (IOException e) { Debug.reportError(e); } }
| Sets the find panel to be visible | public void setFindPanelVisible() { finder.setVisible(true); }; public void mouseOverText(MouseOverTextEvent e) { final int caretPos = e.getCharacterIndex(); if (caretPos != mouseCaretPos) { showErrorPopupForCaretPos(caretPos, true); } }
| Populates the find field and requests focus | public void setFindTextfield(String text) { finder.populateFindTextfield(text); }
| Determines whether the Naviview should initially be expanded or not. | protected boolean getNaviviewExpandedProperty() { if (watcher != null && watcher.getProperty(EditorWatcher.NAVIVIEW_EXPANDED_PROPERTY)!=null) { return Boolean.parseBoolean(watcher.getProperty(EditorWatcher.NAVIVIEW_EXPANDED_PROPERTY)); } return PrefMgr.getNaviviewExpanded(); }
| Returns whether the editor text represents source code, or something else | (such as the README.txt file). | | @return true if source code; | false if not | protected boolean containsSourceCode() { return sourceIsCode; }
| Notify the editor watcher of an edit (or save). | @param includeOneLineEdits - will be true if it is considered unlikely that further edits will | be localised to previous edit locations (line), or if the file has been saved. | private void recordEdit(boolean includeOneLineEdits) { if (watcher != null) { watcher.recordJavaEdit(sourceDocument.getText(0, sourceDocument.getLength()), includeOneLineEdits); } } public bluej.editor.TextEditor assumeText() { return this; } @Override public void insertAppendMethod(NormalMethodElement method, FXPlatformConsumer<Boolean> after) { NodeAndPosition<ParsedNode> classNode = findClassNode(); if (classNode != null) { NodeAndPosition<ParsedNode> existingMethodNode = findMethodNode(method.getName(), classNode); if (existingMethodNode != null) { String text = ""; for (CodeElement codeElement : method.getContents()) { text += codeElement.toJavaSource().toTemporaryJavaCodeString(); } appendTextToNode(existingMethodNode, text); after.accept(false); return; } appendTextToNode(classNode, method.toJavaSource().toTemporaryJavaCodeString()); after.accept(true); } after.accept(false); } @Override public void insertMethodCallInConstructor(String className, CallElement callElement, FXPlatformConsumer<Boolean> after) { NodeAndPosition<ParsedNode> classNode = findClassNode(); if (classNode != null) { NodeAndPosition<ParsedNode> constructor = findMethodNode(className, classNode); if (constructor == null) { addDefaultConstructor(className, callElement); } else { String methodName = callElement.toJavaSource().toTemporaryJavaCodeString(); methodName = methodName.substring(0, methodName.indexOf('(')); if (!hasMethodCall(methodName, constructor, true)) { appendTextToNode(constructor, callElement.toJavaSource().toTemporaryJavaCodeString()); after.accept(true); return; } } } after.accept(false); } private void addDefaultConstructor(String className, CallElement callElement) { NodeAndPosition<ParsedNode> classNode = findClassNode(); if (classNode != null) { appendTextToNode(classNode, "public " + className + "()\n{\n" + callElement.toJavaSource().toTemporaryJavaCodeString() + "}\n"); } }
| Appends text to a node that ends in a curly bracket | private void appendTextToNode(NodeAndPosition<ParsedNode> node, String text) { for (int pos = node.getEnd() - 1; pos >= 0; pos--) { if ("}".equals(getText(getLineColumnFromOffset(pos), getLineColumnFromOffset(pos+1)))) { int posFinal = pos; undoManager.compoundEdit(() -> { int originalLength = node.getSize(); setText(getLineColumnFromOffset(posFinal), getLineColumnFromOffset(posFinal), text); int oldPos = getSourcePane().getCaretPosition(); MoeIndent.calculateIndentsAndApply(sourceDocument, node.getPosition(), node.getPosition() + originalLength + text.length(), oldPos); }); setCaretLocation(getLineColumnFromOffset(pos)); return; } } Debug.message("Could not find end of node to append to: \"" + getText(getLineColumnFromOffset( node.getPosition()), getLineColumnFromOffset(node.getEnd())) + "\""); } private NodeAndPosition findClassNode() { NodeAndPosition<ParsedNode> root = new NodeAndPosition<>(sourceDocument.getParser(), 0, sourceDocument.getParser().getSize()); for (NodeAndPosition<ParsedNode> nap : iterable(root)) { if (nap.getNode().getNodeType() == ParsedNode.NODETYPE_TYPEDEF) return nap; } return null; } private NodeAndPosition findMethodNode(String methodName, NodeAndPosition<ParsedNode> start) { for (NodeAndPosition<ParsedNode> nap : iterable(start)) { if (nap.getNode().getNodeType() == ParsedNode.NODETYPE_NONE) { NodeAndPosition<ParsedNode> r = findMethodNode(methodName, nap); if (r != null) return r; } if (nap.getNode().getNodeType() == ParsedNode.NODETYPE_METHODDEF && nap.getNode().getName().equals(methodName)) { return nap; } } return null; } private boolean hasMethodCall(String methodName, NodeAndPosition<ParsedNode> methodNode, boolean root) { for (NodeAndPosition<ParsedNode> nap : iterable(methodNode)) { if (nap.getNode().getNodeType() == ParsedNode.NODETYPE_NONE && root) { return hasMethodCall(methodName, nap, false); } if (nap.getNode().getNodeType() == ParsedNode.NODETYPE_EXPRESSION && sourceDocument.getText( nap.getPosition(), nap.getSize()).startsWith(methodName)) { return true; } } return false; } private Iterable> iterable(final NodeAndPosition<ParsedNode> parent) { return () -> parent.getNode().getChildren(parent.getPosition()); } @Override @OnThread(Tag.FX) public FrameEditor assumeFrame() { return null; } @Override public boolean compileStarted(int compilationSequence) { compilationStarted = true; errorManager.removeAllErrorHighlights(); return false; } @Override @OnThread(Tag.FXPlatform) public boolean isOpen() { return fxTabbedEditor != null && fxTabbedEditor.isWindowVisible(); } public String getTitle() { return windowTitle; } public void compileOrShowNextError() { if (watcher != null) { if (saveState.isChanged() || !errorManager.hasErrorHighlights()) { if (! saveState.isChanged()) { if (PrefMgr.getFlag(PrefMgr.ACCESSIBILITY_SUPPORT)) { DialogManager.showTextWithCopyButtonFX(getWindow(), Config.getString("pkgmgr.accessibility.compileDone"), "BlueJ"); } } scheduleCompilation(CompileReason.USER, CompileType.EXPLICIT_USER_COMPILE); } else { ErrorDetails err = errorManager.getNextErrorPos(sourcePane.getCaretPosition()); if (err != null) { sourcePane.setCaretPosition(err.startPos); ensureCaretVisible(); if (PrefMgr.getFlag(PrefMgr.ACCESSIBILITY_SUPPORT)) { DialogManager.showTextWithCopyButtonFX(getWindow(), err.message, "BlueJ"); } } } } }
| Notify this editor that it has gained focus, either because its tab was selected or it is the | currently selected tab in a window that gained focus, or it has lost focus for the opposite | reasons. | | @param visible true if the editor has focus, false otherwise | public void notifyVisibleTab(boolean visible) { if (visible) { if (watcher != null) { watcher.recordSelected(); } checkForChangeOnDisk(); } else { showErrorOverlay(null, 0); } }
| Sets the parent SwingTabbedEditor reference. | | @param partOfMove True if this is part of a move to another window (and thus we shouldn't record | open or close) | @OnThread(Tag.FXPlatform) public void setParent(FXTabbedEditor parent, boolean partOfMove) { if (watcher != null) { if (!partOfMove && parent != null) { watcher.recordOpen(); } else if (!partOfMove && parent == null) { watcher.recordClose(); } if (parent == null && saveState.isChanged()) { scheduleCompilation(CompileReason.MODIFIED, CompileType.ERROR_CHECK_ONLY); } } this.fxTabbedEditor = parent; } p.public void updateHeaderHasErrors(boolean hasErrors) { fxTab.setErrorStatus(hasErrors); } @OnThread(Tag.FX) public List getFXMenu() { return fxMenus; } @OnThread(Tag.FXPlatform) public EditorWatcher getWatcher() { return watcher; } @OnThread(Tag.FXPlatform) public void requestEditorFocus() { sourcePane.requestFocus(); } @Override public void setExtendsClass(String className, ClassInfo info) { try { save(); if (info != null) { if (info.getSuperclass() == null) { Selection s1 = info.getExtendsInsertSelection(); setSelection(s1.getLine(), s1.getColumn(), s1.getEndLine(), s1.getEndColumn()); insertText(" extends " + className, false); } else { Selection s1 = info.getSuperReplaceSelection(); setSelection(s1.getLine(), s1.getColumn(), s1.getEndLine(), s1.getEndColumn()); insertText(className, false); } save(); } } catch (IOException ioe) { DialogManager.showMessageWithTextFX(getWindow(), "generic-file-save-error", ioe.getLocalizedMessage()); } } @Override public void removeExtendsClass(ClassInfo info) { try { save(); if (info != null) { Selection s1 = info.getExtendsReplaceSelection(); s1.combineWith(info.getSuperReplaceSelection()); if (s1 != null) { setSelection(s1.getLine(), s1.getColumn(), s1.getEndLine(), s1.getEndColumn()); insertText("", false); } save(); } } catch (IOException ioe) { DialogManager.showMessageWithTextFX(getWindow(), "generic-file-save-error", ioe.getLocalizedMessage()); } } @Override public void addImplements(String interfaceName, ClassInfo info) { try { save(); if (info != null) { Selection s1 = info.getImplementsInsertSelection(); setSelection(s1.getLine(), s1.getColumn(), s1.getEndLine(), s1.getEndColumn()); if (info.hasInterfaceSelections()) { List<String> exists = getInterfaceTexts(info.getInterfaceSelections()); if (!exists.contains(interfaceName)) insertText(", " + interfaceName, false); } else { insertText(" implements " + interfaceName, false); } save(); } } catch (IOException ioe) { DialogManager.showMessageWithTextFX(getWindow(), "generic-file-save-error", ioe.getLocalizedMessage()); } } @Override public void addExtendsInterface(String interfaceName, ClassInfo info) { try { save(); if (info != null) { Selection s1 = info.getExtendsInsertSelection(); setSelection(s1.getLine(), s1.getColumn(), s1.getEndLine(), s1.getEndColumn()); if (info.hasInterfaceSelections()) { List<String> exists = getInterfaceTexts(info.getInterfaceSelections()); if (!exists.contains(interfaceName)) insertText(", " + interfaceName, false); } else { insertText(" extends " + interfaceName, false); } save(); } } catch (IOException ioe) { DialogManager.showMessageWithTextFX(getWindow(), "generic-file-save-error", ioe.getLocalizedMessage()); } } @Override public void removeExtendsOrImplementsInterface(String interfaceName, ClassInfo info) { try { save(); if (info != null) { Selection s1 = null; List<Selection> vsels; List<String> vtexts; vsels = info.getInterfaceSelections(); vtexts = getInterfaceTexts(vsels); int where = vtexts.indexOf(interfaceName); if (where == 1 && vsels.size() > 2) where = 2; if (where > 0) { s1 = vsels.get(where - 1); s1.combineWith(vsels.get(where)); } if (s1 != null) { setSelection(s1.getLine(), s1.getColumn(), s1.getEndLine(), s1.getEndColumn()); insertText("", false); } save(); } } catch (IOException ioe) { DialogManager.showMessageWithTextFX(getWindow(), "generic-file-save-error", ioe.getLocalizedMessage()); } } @Override public void removeImports(List<String> importTargets) { List<ImportsCollection.LocatableImport> toRemove = new ArrayList<>(); for (String importTarget : importTargets) { ImportsCollection.LocatableImport details = getParsedNode().getImports().getImportInfo(importTarget); if (details != null) { toRemove.add(details); } } Collections.sort(toRemove, Comparator.<LocatableImport>comparingInt(t -> -t.getStart())); for (ImportsCollection.LocatableImport locatableImport : toRemove) { if (locatableImport.getStart() != -1) { getSourcePane().replaceText(locatableImport.getStart(), locatableImport.getStart() + locatableImport.getLength(), ""); } } }
| Using a list of selections, retrieve a list of text strings from the editor which | correspond to those selections. | TODO this is usually used to get the implemented interfaces, but it is a clumsy way | to do that. | private List getInterfaceTexts(List<Selection> selections) { List<String> r = new ArrayList<String>(selections.size()); for (Selection sel : selections) { String text = getText(new bluej.parser.SourceLocation(sel.getLine(), sel.getColumn()), new bluej.parser.SourceLocation(sel.getEndLine(), sel.getEndColumn())); int taIndex = text.indexOf('<'); if (taIndex != -1) text = text.substring(0, taIndex); text = text.trim(); r.add(text); } return r; }
| Set the header image (in the tab header) for this editor | @param image The image to use (any size). | @Override public void setHeaderImage(Image image) { fxTab.setHeaderImage(image); } @OnThread(Tag.FXPlatform) public javafx.stage.Window getWindow() { return fxTabbedEditor.getWindow(); } private static class ErrorDisplay { @OnThread(Tag.Swing) private final ErrorDetails details; private PopupControl popup; public ErrorDisplay(ErrorDetails details) { this.details = details; } @OnThread(Tag.FXPlatform) public void createPopup() { this.popup = new PopupControl(); Text text = new Text(ParserMessageHandler.getMessageForCode(details.message)); TextFlow flow = new TextFlow(text); flow.setMaxWidth(600.0); JavaFXUtil.addStyleClass(text, "java-error"); text.styleProperty().bind(PrefMgr.getEditorFontCSS(true)); Pane p = new BorderPane(flow); this.popup.setSkin(new Skin<Skinnable>() { @Override @OnThread(Tag.FX) public Skinnable getSkinnable() { return popup; } @Override @OnThread(Tag.FX) public Node getNode() { return p; } @Override @OnThread(Tag.FX) public void dispose() { } }); p.getStyleClass().add("java-error-popup"); Config.addPopupStylesheets(p); } } }

.   - MoeEditor
.   MoeEditor
.   findSubstring
.   isNonReadmeAction
.   getNonReadmeActions
.   checkForChangeOnDisk
.   reloadFile
.   clear
.   setEditorVisible
.   refresh
.   save
.   close
.   displayMessage
.   displayDiagnostic
.   setStepMark
.   getPosFromColumn
.   setSelection
.   setSelection
.   removeStepMark
.   changeName
.   scheduleCompilation
.   compileFinished
.   getCompileErrorLabel
.   removeBreakpoints
.   reInitBreakpoints
.   isModified
.   isReadOnly
.   setReadOnly
.   showInterface
.   isShowingInterface
.   getCaretLocation
.   setCaretLocation
.   getLineColumnFromOffset
.   getSelectionBegin
.   getSelectionEnd
.   getText
.   setText
.   setSelection
.   getOffsetFromLineColumn
.   getProperty
.   setProperty
.   getLineLength
.   getTextLength
.   numberOfLines
.   getParsedNode
.   scheduleReparseRunner
.   reparseRunnerFinished
.   blueJEvent
.   listenToChanges
.   documentContentChanged
.   clearMessage
.   writeMessage
.   writeWarningMessage
.   printTo
.   printPages
.   print
.   run
.   doClose
.   checkExpandTabs
.   showReplacePanel
.   findString
.   showPreferences
.   setLastModified

top, use, map, interface FindNavigator

.   highlightAll
.   selectNext
.   selectPrev
.   validProperty
.   replaceCurrent
.   replaceAll
.   doFind
.   highlightAll
.   replaceCurrent
.   replaceAll
.   selectNext
.   select
.   selectPrev
.   validProperty
.   goToLine
.   ensureCaretVisible
.   switchToSourceView
.   switchToInterfaceView
.   findHTMLNode
.   refreshHtmlDisplay
.   setupJavadocMangler
.   docUpToDate
.   resetMenuToolbar
.   toggleBreakpoint
.   clearAllBreakpoints
.   positionHasBreakpoint
.   lineHasBreakpoint
.   setUnsetBreakpoint
.   doRemoveBreakpoint
.   setStepMark
.   repaint
.   viewingCode
.   getSourceLine
.   getLineNumberAt
.   doReload
.   checkBracketStatus
.   setCompileStatus
.   setSaved
.   setChanged
.   caretMoved
.   showErrorPopupForCaretPos
.   cancelFreshState
.   focusMethod
.   focusMethod
.   paramsMatch
.   showErrorOverlay
.   getBracketMatch
.   doBracketMatch
.   removeBracketHighlight
.   setWindowTitle
.   getDocPath
.   getResource
.   initWindow
.   createMenuBar
.   createPopupMenu
.   createMenu
.   createToolbar
.   createToolbarButton
.   createInterfaceSelector
.   initFindPanel
.   setCaretPositionForward
.   getSourcePane
.   getHTMLPane
.   getCurrentTextPane
.   getSourceDocument
.   removeSearchHighlights
.   createContentAssist
.   getFontCSS
.   getFontSize
.   setupSuggestionWindow
.   suggestionListChoiceClicked
.   suggestionListKeyTyped
.   hidden
.   codeComplete
.   setFindPanelVisible
.   mouseOverText
.   setFindTextfield
.   getNaviviewExpandedProperty
.   containsSourceCode
.   recordEdit
.   assumeText
.   insertAppendMethod
.   insertMethodCallInConstructor
.   addDefaultConstructor
.   appendTextToNode
.   findClassNode
.   findMethodNode
.   hasMethodCall
.   iterable
.   assumeFrame
.   compileStarted
.   isOpen
.   getTitle
.   compileOrShowNextError
.   notifyVisibleTab
.   setParent
.   updateHeaderHasErrors
.   getFXMenu
.   getWatcher
.   requestEditorFocus
.   setExtendsClass
.   removeExtendsClass
.   addImplements
.   addExtendsInterface
.   removeExtendsOrImplementsInterface
.   removeImports
.   getInterfaceTexts
.   setHeaderImage
.   getWindow

top, use, map, class ErrorDisplay

.   ErrorDisplay
.   createPopup
.   getSkinnable
.   getNode
.   dispose




4202 neLoCode + 417 LoComm