package bluej.editor.moe;

import java.util.*;
import java.util.Map.Entry;

import javax.swing.text.Segment;

import bluej.editor.moe.BlueJSyntaxView.ParagraphAttribute;
import bluej.editor.moe.BlueJSyntaxView.ScopeInfo;
import bluej.editor.moe.Token.TokenType;
import bluej.utility.Utility;
import bluej.utility.javafx.JavaFXUtil;
import com.google.common.collect.ImmutableSet;
import javafx.beans.binding.BooleanExpression;
import org.fxmisc.richtext.model.*;
import org.fxmisc.richtext.model.TwoDimensional.Bias;
import org.reactfx.Subscription;
import org.reactfx.collection.LiveList;
import threadchecker.OnThread;
import threadchecker.Tag;
import bluej.Config;
import bluej.parser.entity.EntityResolver;
import bluej.parser.nodes.NodeTree;
import bluej.parser.nodes.NodeTree.NodeAndPosition;
import bluej.parser.nodes.ParsedCUNode;
import bluej.parser.nodes.ParsedNode;
import bluej.utility.Debug;


| An implementation of PlainDocument, with an optional added parser to provide | syntax highlighting, scope highlighting, and other advanced functionality. | | @author Bruce Quig | @author Jo Wood (Modified to allow user-defined colours, March 2001) | @OnThread(Tag.FXPlatform) public class MoeSyntaxDocument { public static final String MOE_FIND_RESULT = "moe-find-result"; public static final String MOE_BRACKET_HIGHLIGHT = "moe-bracket-highlight";
| A RichTextFX document can have paragraph styles, and text-segment styles. | | We use RichTextFX such that one RichTextFX paragraph = one line of Java source code. | Our paragraph styles are thus line styles, and we use this to store information | about the scope boxes that need painting on the background for scope highlighting. | | Our text-segment styles are a set of style-classes to apply to a text-segment. | This is usually a single token-style (our tokens never overlap), and possibly | the error state class and/or search highlight class. | private final SimpleEditableStyledDocument<ScopeInfo, ImmutableSet<String>> document;
| Maximum amount of document to reparse in one hit (advisory) | private final static int MAX_PARSE_PIECE = 8000; private final int tabSize; private ParsedCUNode parsedNode; private EntityResolver parentResolver; private NodeTree<ReparseRecord> reparseRecordTree;
| We want to avoid repaint flicker by letting the user see partly-updated | backgrounds when processing the reparse queue. So we store new backgrounds | in this map until we're ready to show all new backgrounds, which typically | happens when the reparse queue is empty (and is done by the applyPendingScopeBackgrounds() | method) | private final Map<Integer, ScopeInfo> pendingScopeBackgrounds = new HashMap<>(); private final Set<Integer> pendingStyleUpdates = new HashSet<>(); private boolean applyingScopeBackgrounds = false; private final BlueJSyntaxView syntaxView; private boolean hasFindHighlights = false; private String cachedContent = null; private int[] lineStarts = null; boolean notYetShown = true;
| Is this pane an off-screen item just for printing? | private boolean thisDocIsForPrinting = false;
| Create an empty MoeSyntaxDocument. | @OnThread(Tag.FXPlatform) public MoeSyntaxDocument(ScopeColors scopeColors) { tabSize = Config.getPropInteger("bluej.editor.tabsize", 4); document = new SimpleEditableStyledDocument<>(null, ImmutableSet.of()); if (scopeColors != null) { syntaxView = new BlueJSyntaxView(this, scopeColors); } else { syntaxView = null; } document.plainChanges().subscribe(c -> { invalidateCache(); if (!c.getRemoved().isEmpty()) { fireRemoveUpdate(c.getPosition(), c.getRemovalEnd() - c.getPosition()); } if (!c.getInserted().isEmpty()) { fireInsertUpdate(c.getPosition(), c.getInsertionEnd() - c.getPosition()); } if (!thisDocIsForPrinting) { JavaFXUtil.runAfterCurrent(() -> { invalidateCache(); applyPendingScopeBackgrounds(); }); } }); }
| Create an empty MoeSyntaxDocument, which uses the given entity resolver | to resolve symbols. | @OnThread(Tag.FXPlatform) public MoeSyntaxDocument(EntityResolver parentResolver, ScopeColors scopeColors) { this(scopeColors); this.parentResolver = parentResolver; if (parentResolver != null) { reparseRecordTree = new NodeTree<ReparseRecord>(); } }
| Create an empty MoeSyntaxDocument, which uses the given entity resolver | to resolve symbols. | @OnThread(Tag.FXPlatform) public MoeSyntaxDocument(EntityResolver parentResolver) { this((ScopeColors) null); this.parentResolver = parentResolver; if (parentResolver != null) { reparseRecordTree = new NodeTree<ReparseRecord>(); } } public Position createPosition(int initialPos) { return new Position(initialPos); } public void markFindResult(int start, int end) { hasFindHighlights = true; document.setStyleSpans(start, document.getStyleSpans(start, end).mapStyles(ss -> { return Utility.setAdd(ss, MOE_FIND_RESULT); })); } public void removeSearchHighlights() { if (hasFindHighlights) { removeStyleThroughout(MOE_FIND_RESULT); hasFindHighlights = false; } } public void removeStyleThroughout(String spanStyle) { LiveList<Paragraph<ScopeInfo, String, ImmutableSet<String>>> paragraphs = document.getParagraphs(); for (int i = 0; i < paragraphs.size(); i++) { Paragraph<ScopeInfo, String, ImmutableSet<String>> para = paragraphs.get(i); StyleSpans<ImmutableSet<String>> styleSpans = para.getStyleSpans(); boolean present = styleSpans.stream().anyMatch(s -> s.getStyle().contains(spanStyle)); if (present) { document.setStyleSpans(i, 0, styleSpans.mapStyles(ss -> Utility.setMinus(ss, spanStyle))); } } } public void addStyle(int start, int end, String style) { document.setStyleSpans(start, document.getStyleSpans(start, end).mapStyles(ss -> Utility.setAdd(ss, style))); }
| | We'll keep track of recent events, to aid in hunting down bugs in the event | that we get an unexpected exception. | private static int EDIT_INSERT = 0; private static int EDIT_DELETE = 1; public void copyFrom(MoeSyntaxDocument from) { document.replace(0, document.getLength(), from.document); } public TwoDimensional.Position offsetToPosition(int startOffset) { if (lineStarts == null) { return document.offsetToPosition(startOffset, Bias.Forward); } else { int line = 1; for (; line < lineStarts.length; line++) { if (startOffset < lineStarts[line]) break; } line -= 1; int lineFinal = line; int column = startOffset - lineStarts[lineFinal]; return new TwoDimensional.Position() { @Override public TwoDimensional getTargetObject() { return document; } @Override public int getMajor() { return lineFinal; } @Override public int getMinor() { return column; } @Override public boolean sameAs(TwoDimensional.Position other) { return getTargetObject() == other.getTargetObject() && getMajor() == other.getMajor() && getMinor() == other.getMinor(); } @Override public TwoDimensional.Position clamp() { return this; } @Override public TwoDimensional.Position offsetBy(int offset, Bias bias) { return document.offsetToPosition(startOffset + offset, Bias.Forward); } @Override public int toOffset() { return startOffset; } }; } } private int getAbsolutePosition(int lineIndex, int columnIndex) { if (lineStarts == null || lineIndex >= lineStarts.length) return document.getAbsolutePosition(lineIndex, columnIndex); } else { return lineStarts[lineIndex] + columnIndex; } }
| Marks this document as an off-screen copy for printing. Note: you must call this before | altering the content of the document, or else the run-later we are suppressing with this flag | will already have been run. | public void markAsForPrinting() { thisDocIsForPrinting = true; } public boolean isPrinting() { return thisDocIsForPrinting; } @OnThread(Tag.Any) private static class EditEvent { int type; int offset; int length; } private List<EditEvent> recentEdits = new LinkedList<EditEvent>(); private void recordEvent(MoeSyntaxEvent event) { int type; if (event.isInsert()) { type = EDIT_INSERT; } else if (event.isRemove()) { type = EDIT_DELETE; } else { return; } EditEvent eevent = new EditEvent(); eevent.type = type; eevent.offset = event.getOffset(); eevent.length = event.getLength(); recentEdits.add(eevent); if (recentEdits.size() > 10) { recentEdits.remove(0); } } private void invalidateCache() { cachedContent = null; lineStarts = null; } private void cacheContent() { if (cachedContent == null) cachedContent = document.getText(); if (lineStarts == null) { lineStarts = new int[document.getParagraphs().size()]; lineStarts[0] = 0; int curLine = 0; for (int character = 0; character < cachedContent.length(); character++) { if (cachedContent.charAt(character) == '\n') { curLine += 1; lineStarts[curLine] = character + 1; } } } }
| Access the parsed node structure of this document. | public ParsedCUNode getParser() { flushReparseQueue(); return parsedNode; }
| Get the current parsed node structure of the document, without processing any | pending re-parse operations first. | public ParsedCUNode getParsedNode() { return parsedNode; }
| Enable the parser. This should be called after loading a document. | @param force whether to force-enable the parser. If false, the parser will only | be enabled if an entity resolver is available. | @OnThread(Tag.FXPlatform) public void enableParser(boolean force) { if (parentResolver != null || force) { parsedNode = new ParsedCUNode(this); parsedNode.setParentResolver(parentResolver); reparseRecordTree = new NodeTree<ReparseRecord>(); parsedNode.textInserted(this, 0, 0, getLength(), new MoeSyntaxEvent(this, 0, getLength(), true, false)); } }
| Run an item from the re-parse queue, if there are any. Return true if | a queued re-parse was processed or false if the queue was empty. | public boolean pollReparseQueue() { return pollReparseQueue(MAX_PARSE_PIECE); }
| Run an item from the re-parse queue, if there are any, and attempt to | parse the specified amount of document (approximately). Return true if | a queued re-parse was processed or false if the queue was empty. | @OnThread(Tag.FXPlatform) private boolean pollReparseQueue(int maxParse) { try { if (reparseRecordTree == null) { return false; } NodeAndPosition<ReparseRecord> nap = reparseRecordTree.findNodeAtOrAfter(0); if (nap != null) { int pos = nap.getPosition(); ParsedNode pn = parsedNode; int ppos = 0; if (pn != null) { NodeAndPosition<ParsedNode> cn = pn.findNodeAt(pos, ppos); while (cn != null && cn.getEnd() == pos){ cn = cn.nextSibling(); } while (cn != null && cn.getPosition() <= pos){ ppos = cn.getPosition(); pn = cn.getNode(); cn = pn.findNodeAt(nap.getPosition(), ppos); while (cn != null && cn.getEnd() == pos){ cn = cn.nextSibling(); } } MoeSyntaxEvent mse = new MoeSyntaxEvent(this, -1, -1, false, false); pn.reparse(this, ppos, pos, maxParse, mse); fireChangedUpdate(mse); return true; } } return false; } catch (RuntimeException e) { Debug.message("Exception during incremental parsing. Recent edits:"); for (EditEvent event : recentEdits) { String eventStr = event.type == EDIT_INSERT ? "insert " : "delete "; eventStr += "offset=" + event.offset + " length=" + event.length; Debug.message(eventStr); } Debug.message("--- Source code ---"); Debug.message(getText(0, getLength())); Debug.message("--- Source ends ---"); throw e; } } private void dumpTree(Iterator<NodeAndPosition<ParsedNode>> iterator, String indent) { for (NodeAndPosition<ParsedNode> nap2 : (Iterable<NodeAndPosition<ParsedNode>>)(() -> iterator)) { Debug.message(indent + "Node: " + nap2.getPosition() + " -> " + nap2.getEnd()); dumpTree(nap2.getNode().getChildren(nap2.getPosition()), indent + " "); } }
| Sets the step-line (paused line in debugger) indicator on that line, | and clears it from all other lines. | | Line number starts at one. | public void showStepLine(int lineNumber) { for (int i = 0; i < document.getParagraphs().size(); i++) { setParagraphAttributesForLineNumber(i + 1, Collections.singletonMap(ParagraphAttribute.STEP_MARK, i + 1 == lineNumber)); } }
| Issue a change update to listeners. | | @param mse the event with the details of the change, or null if the the change is a resize | of the viewport (which requires re-drawing scope backgrounds to match). | public void fireChangedUpdate(MoeSyntaxEvent mse) { if (syntaxView != null) { syntaxView.updateDamage(mse); } if (mse == null) { applyPendingScopeBackgrounds(); } }
| Re-calculate scope position/colour for all lines. | public void recalculateAllScopes() { if (notYetShown) return; syntaxView.resetColors(); recalculateScopesForLinesInRange(0, document.getParagraphs().size() - 1); applyPendingScopeBackgrounds(); }
| Recalculate (and schedule for re-drawing) scope margins for lines in the given range. | | @param firstLineIncl the first line in the range, inclusive; 0-based. | @param lastLineIncl the last line in the range, inclusive; 0-based. | public void recalculateScopesForLinesInRange(int firstLineIncl, int lastLineIncl) { if (syntaxView == null) { return; } cacheContent(); syntaxView.recalculateScopes(pendingScopeBackgrounds, firstLineIncl, lastLineIncl); }
| Apply pending scope background updates. Must not be called from a document update | event. | public void applyPendingScopeBackgrounds() { if (parsedNode == null) { return; } if (applyingScopeBackgrounds) { return; } MoeEditorPane editorPane = (syntaxView != null) ? syntaxView.getEditorPane() : null; if (editorPane == null) { return; } applyingScopeBackgrounds = true; double scrollY = editorPane.getEstimatedScrollY(); Set<Entry<Integer, ScopeInfo>> pendingBackgrounds = new HashMap<>(pendingScopeBackgrounds).entrySet(); pendingScopeBackgrounds.clear(); for (Entry<Integer, ScopeInfo> pending : pendingBackgrounds) { if (pending.getKey() >= document.getParagraphs().size()) { continue; } ScopeInfo newStyle = pending.getValue(); setParagraphStyle(pending.getKey(), newStyle); } for (Integer pending : pendingStyleUpdates) { StyleSpans<ImmutableSet<String>> styleSpans = syntaxView.getTokenStylesFor(pending, this); if (styleSpans != null && !styleSpans.equals(document.getStyleSpans(pending))) { document.setStyleSpans(pending, 0, document.getStyleSpans(pending) .overlay(styleSpans, MoeSyntaxDocument::setTokenStyles)); } } pendingStyleUpdates.clear(); editorPane.layout(); editorPane.estimatedScrollYProperty().setValue(scrollY); editorPane.layout(); applyingScopeBackgrounds = false; }
| Removes any existing token styles from allStyles, then adds them in from newTokenStyle. | private static ImmutableSet setTokenStyles(ImmutableSet<String> allStyles, ImmutableSet<String> newTokenStyle) { return Utility.setUnion(Utility.setMinus(allStyles, TokenType.allCSSClasses()), newTokenStyle); } private void setParagraphStyle(int i, ScopeInfo newStyle) { document.setParagraphStyle(i, newStyle); }
| Process all of the re-parse queue. | public void flushReparseQueue() { while (pollReparseQueue(getLength())) {; applyPendingScopeBackgrounds(); } }
| Schedule a reparse at a certain point within the document. | @param pos The position to reparse at | @param size The reparse size. This is a minimum, rather than a maximum; that is, | the reparse when it occurs must parse at least this much. | public void scheduleReparse(int pos, int size) { NodeAndPosition<ReparseRecord> existing = reparseRecordTree.findNodeAtOrAfter(pos); if (existing != null) { if (existing.getPosition() > pos && existing.getPosition() <= (pos + size)) { existing.getNode().slideStart(pos - existing.getPosition()); return; } else if (existing.getPosition() <= pos) { int nsize = (pos + size) - existing.getPosition(); if (nsize > existing.getSize()) { NodeAndPosition<ReparseRecord> next = existing.nextSibling(); while (next != null && next.getPosition() <= pos + size){ nsize = Math.max(nsize, next.getEnd() - pos); NodeAndPosition<ReparseRecord> nnext = next.nextSibling(); next.getNode().remove(); next = nnext; } existing.getNode().setSize(nsize); } return; } } ReparseRecord rr = new ReparseRecord(); reparseRecordTree.insertNode(rr, pos, size); }
| Mark a portion of the document as having been parsed. This removes any | scheduled re-parses as appropriate and repaints the appropriate area. | public void markSectionParsed(int pos, int size) { repaintLines(pos, size, true); NodeAndPosition<ReparseRecord> existing = reparseRecordTree.findNodeAtOrAfter(pos); while (existing != null && existing.getPosition() <= pos){ NodeAndPosition<ReparseRecord> next = existing.nextSibling(); int rsize = existing.getEnd() - pos; rsize = Math.min(rsize, size); if (rsize == existing.getSize()) { existing.getNode().remove(); } else if (existing.getPosition() == pos) { existing.slideStart(rsize); existing = next; break; } else { int existingEnd = existing.getEnd(); existing.setSize(pos - existing.getPosition()); if (existingEnd > pos + size) { scheduleReparse(pos + size, existingEnd - (pos + size)); return; } } existing = next; } while (existing != null && existing.getPosition() < pos + size){ int rsize = pos + size - existing.getPosition(); if (rsize < existing.getSize()) { existing.slideStart(rsize); return; } NodeAndPosition<ReparseRecord> next = existing.nextSibling(); existing.getNode().remove(); existing = next; } }
| Inform any listeners that a parse error has occurred. | | @param position The position of the parse error | @param size The size of the erroneous portion | @param message The error message | public void parseError(int position, int size, String message) { }
| | @param lineNumber Line number (starts at 1) | @param alterAttr The attributes to alter (mapped to true means add, mapped to false means remove, not present means don't alter) | public void setParagraphAttributesForLineNumber(int lineNumber, Map<ParagraphAttribute, Boolean> alterAttr) { if (syntaxView == null) return; Map<Integer, EnumSet<ParagraphAttribute>> changedLines = syntaxView.setParagraphAttributes(lineNumber, alterAttr); updateScopesAfterParaAttrChange(changedLines); } private void updateScopesAfterParaAttrChange(Map<Integer, EnumSet<ParagraphAttribute>> changedLines) { for (Entry<Integer, EnumSet<ParagraphAttribute>> changedLine : changedLines.entrySet()) { if (changedLine.getKey() - 1 < document.getParagraphs().size()) { ScopeInfo prevStyle = document.getParagraphStyle(changedLine.getKey() - 1); if (prevStyle != null) { setParagraphStyle(changedLine.getKey() - 1, prevStyle.withAttributes(changedLine.getValue())); } } } }
| Sets attributes for all paragraphs. | | @param alterAttr the attributes to set the value for (other attributes will be unaffected) | public void setParagraphAttributes(Map<ParagraphAttribute, Boolean> alterAttr) { if (syntaxView == null) return; Map<Integer, EnumSet<ParagraphAttribute>> changedLines = syntaxView.setParagraphAttributes(alterAttr); updateScopesAfterParaAttrChange(changedLines); }
| Identify the token types and positions in a line. This is used for syntax colouring. | @param line The line number (0 based). | public Token getTokensForLine(int line) { Element lineEl = getDefaultRootElement().getElement(line); int pos = lineEl.getStartOffset(); int length = lineEl.getEndOffset() - pos - 1; return parsedNode.getMarkTokensFor(pos, length, 0, this); }
| | If text was inserted, the reparse-record tree needs to be updated. | protected void fireInsertUpdate(int offset, int length) { if (syntaxView != null) { syntaxView.setDuringUpdate(true); } if (reparseRecordTree != null) { NodeAndPosition<ReparseRecord> napRr = reparseRecordTree.findNodeAtOrAfter(offset); if (napRr != null) { if (napRr.getPosition() <= offset) { napRr.getNode().resize(napRr.getSize() + length); } else { napRr.getNode().slide(length); } } } int firstLine = offsetToPosition(offset).getMajor(); int lastLine = offsetToPosition(offset + length).getMajor(); for (int i = firstLine; i <= lastLine; i++) { pendingStyleUpdates.add(i); } MoeSyntaxEvent mse = new MoeSyntaxEvent(this, offset, length, true, false); if (parsedNode != null) { parsedNode.textInserted(this, 0, offset, length, mse); } fireChangedUpdate(mse); recordEvent(mse); if (syntaxView != null) { syntaxView.setDuringUpdate(false); } }
| | If part of the document was removed, the reparse-record tree needs to be updated. | protected void fireRemoveUpdate(int offset, int length) { if (syntaxView != null) { syntaxView.setDuringUpdate(true); } NodeAndPosition<ReparseRecord> napRr = (reparseRecordTree != null) ? reparseRecordTree.findNodeAtOrAfter(offset) : null; int rpos = offset; int rlen = length; if (napRr != null && napRr.getEnd() == rpos) { napRr = napRr.nextSibling(); } while (napRr != null && rlen > 0){ if (napRr.getPosition() < rpos) { if (napRr.getEnd() >= rpos + rlen) { napRr.getNode().resize(napRr.getSize() - rlen); break; } else { int reduction = napRr.getEnd() - rpos; napRr.getNode().resize(napRr.getSize() - reduction); rlen -= reduction; napRr = napRr.nextSibling(); continue; } } else if (napRr.getPosition() == rpos) { if (napRr.getEnd() > rpos + rlen) { napRr.getNode().resize(napRr.getSize() - rlen); break; } else { napRr.getNode().remove(); napRr = reparseRecordTree.findNodeAtOrAfter(offset); continue; } } else { if (napRr.getPosition() >= (rpos + rlen)) { napRr.slide(-rlen); break; } else if (napRr.getEnd() <= (rpos + rlen)) { NodeAndPosition<ReparseRecord> nextRr = napRr.nextSibling(); napRr.getNode().remove(); napRr = nextRr; continue; } else { int ramount = (rpos + rlen) - napRr.getPosition(); napRr.slideStart(ramount); napRr.slide(-rlen); break; } } } pendingStyleUpdates.add(offsetToPosition(offset).getMajor()); MoeSyntaxEvent mse = new MoeSyntaxEvent(this, offset, length, false, true); if (parsedNode != null) { parsedNode.textRemoved(this, 0, offset, length, mse); } fireChangedUpdate(mse); recordEvent(mse); if (syntaxView != null) { syntaxView.setDuringUpdate(false); } }
| Gets the RichTextFX document wrapped by this class. | The styles are deliberately wildcards as the actual types are | an implementation detail private to this class. | @return | public SimpleEditableStyledDocument getDocument() { return document; }
| Placed in this class so we don't have to expose document's | inner types. | @return Creates a new MoeEditorPane for this document | @OnThread(Tag.FXPlatform) public MoeEditorPane makeEditorPane(MoeEditor editor, BooleanExpression compiledStatus) { return new MoeEditorPane(editor, document, syntaxView, compiledStatus); }
| Notify that a certain area of the document needs repainting. | public void repaintLines(int offset, int length) { repaintLines(offset, length, false); }
| Notify that a certain area of the document needs repainting (re-drawing of scope background | and optionally syntax). | | @param offset the offset of the region to be repainted | @param length the length of the region to be repainted | @param reStyle true if the syntax highlighting should be adjusted | public void repaintLines(int offset, int length, boolean reStyle) { int startLine = offsetToPosition(offset).getMajor(); int endLine = offsetToPosition(offset + length).getMajor(); recalculateScopesForLinesInRange(startLine, endLine); restyleLines(startLine, endLine); }
| Mark all lines between start and end (inclusive) as needing to be re-styled. | public void restyleLines(int start, int end) { for (int i = start; i <= end; i++) { pendingStyleUpdates.add(i); } } public int getLength() { return document.getLength(); } public String getText(int start, int length) { if (cachedContent == null) { return document.getText(start, start + length); } else { return cachedContent.substring(start, start + length); } } public void getText(int startOffset, int length, Segment segment) { String s = getText(startOffset, length); segment.array = s.toCharArray(); segment.offset = 0; segment.count = s.length(); } public void insertString(int start, String text) { replace(start, 0, text); } public void replace(int start, int length, String text) { document.replace(start, start + length, ReadOnlyStyledDocument.fromString(text, null, ImmutableSet.of(), SegmentOps.styledTextOps())); } public void remove(int start, int length) { if (length != 0) document.replace(start, start + length, new SimpleEditableStyledDocument<>(null, ImmutableSet.of())); }
| First line is one | public EnumSet getParagraphAttributes(int lineNo) { if (syntaxView != null) return syntaxView.getParagraphAttributes(lineNo); else{ return EnumSet.noneOf(ParagraphAttribute.class); } } @OnThread(Tag.FXPlatform) public static interface Element { public Element getElement(int index); public int getStartOffset(); public int getEndOffset(); public int getElementIndex(int offset); public int getElementCount(); } public Element getDefaultRootElement() { return new Element() { @Override public Element getElement(int index) { if (index >= (lineStarts != null ? lineStarts.length : document.getParagraphs().size())) return null; boolean lastPara = lineStarts != null ? (index == lineStarts.length - 1) : (index == document.getParagraphs().size() - 1); int paraLength; if (lineStarts == null) { paraLength = document.getParagraph(index).length() + (lastPara ? 0 : 1 | newline | ; } else { paraLength = lastPara ? (document.getLength() - lineStarts[index]) : lineStarts[index + 1] - lineStarts[index]; } int pos = getAbsolutePosition(index, 0); return new Element() { @Override public Element getElement(int index) { return null; } @Override public int getStartOffset() { return pos; } @Override public int getEndOffset() { return pos + paraLength; } @Override public int getElementIndex(int offset) { return -1; } @Override public int getElementCount() { return 0; } }; } @Override public int getStartOffset() { return 0; } @Override public int getEndOffset() { return document.getLength(); } @Override public int getElementIndex(int offset) { return offsetToPosition(offset).getMajor(); } @Override public int getElementCount() { if (lineStarts != null) return lineStarts.length; else{ return document.getParagraphs().size(); } } }; } @OnThread(Tag.Any) public class Position { private final Subscription subscription; private int position; public Position(int initial) { this.position = initial; subscription = document.plainChanges().subscribe(this::changed); } private void changed(PlainTextChange c) { if (c.getPosition() <= position) { position -= Math.min(c.getRemovalEnd() - c.getPosition(), position - c.getPosition()); position += (c.getInsertionEnd() - c.getPosition()); } } public void dispose() { subscription.unsubscribe(); } public int getOffset() { return position; } } }
top, use, map, class MoeSyntaxDocument

.   MoeSyntaxDocument
.   MoeSyntaxDocument
.   MoeSyntaxDocument
.   createPosition
.   markFindResult
.   removeSearchHighlights
.   removeStyleThroughout
.   addStyle
.   copyFrom
.   offsetToPosition
.   getTargetObject
.   getMajor
.   getMinor
.   sameAs
.   clamp
.   offsetBy
.   toOffset
.   getAbsolutePosition
.   markAsForPrinting
.   isPrinting

top, use, map, class EditEvent

.   recordEvent
.   invalidateCache
.   cacheContent
.   getParser
.   getParsedNode
.   enableParser
.   pollReparseQueue
.   pollReparseQueue
.   dumpTree
.   showStepLine
.   fireChangedUpdate
.   recalculateAllScopes
.   recalculateScopesForLinesInRange
.   applyPendingScopeBackgrounds
.   setTokenStyles
.   setParagraphStyle
.   flushReparseQueue
.   scheduleReparse
.   markSectionParsed
.   parseError
.   setParagraphAttributesForLineNumber
.   updateScopesAfterParaAttrChange
.   setParagraphAttributes
.   getTokensForLine
.   fireInsertUpdate
.   fireRemoveUpdate
.   getDocument
.   makeEditorPane
.   repaintLines
.   repaintLines
.   restyleLines
.   getLength
.   getText
.   getText
.   insertString
.   replace
.   remove
.   getParagraphAttributes

top, use, map, interface Element

.   getElement
.   getStartOffset
.   getEndOffset
.   getElementIndex
.   getElementCount
.   getDefaultRootElement
.   getElement
.   getElement
.   getStartOffset
.   getEndOffset
.   getElementIndex
.   getElementCount
.   getStartOffset
.   getEndOffset
.   getElementIndex
.   getElementCount

top, use, map, class Position

.   Position
.   changed
.   dispose
.   getOffset




1362 neLoCode + 90 LoComm