diff --git a/lib/jsyntaxpane-0.9.5.jar b/lib/jsyntaxpane-0.9.5.jar index 79d74f13d..2337f7427 100644 Binary files a/lib/jsyntaxpane-0.9.5.jar and b/lib/jsyntaxpane-0.9.5.jar differ diff --git a/libsrc/jsyntaxpane/jsyntaxpane/src/main/java/jsyntaxpane/CompoundUndoMan.java b/libsrc/jsyntaxpane/jsyntaxpane/src/main/java/jsyntaxpane/CompoundUndoMan.java index 3314b7bb5..0fc01cbf5 100644 --- a/libsrc/jsyntaxpane/jsyntaxpane/src/main/java/jsyntaxpane/CompoundUndoMan.java +++ b/libsrc/jsyntaxpane/jsyntaxpane/src/main/java/jsyntaxpane/CompoundUndoMan.java @@ -16,14 +16,16 @@ package jsyntaxpane; import javax.swing.event.UndoableEditEvent; import javax.swing.text.AbstractDocument; import javax.swing.text.AbstractDocument.DefaultDocumentEvent; +import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.swing.undo.CompoundEdit; import javax.swing.undo.UndoManager; import javax.swing.undo.UndoableEdit; /** - * A revised UndoManager that groups undos based on positions. If the change is relatively next to the - * previous change, like when continuous typing, then the undoes are grouped together. + * A revised UndoManager that groups undos based on positions. If the change is + * relatively next to the previous change, like when continuous typing, then the + * undoes are grouped together. * * This is cutomized from the * @@ -34,107 +36,164 @@ import javax.swing.undo.UndoableEdit; * http://tips4java.wordpress.com/2008/10/27/compound-undo-manager/ * * @author Ayman Al-Sairafi + * + * + * JPEXS - updated for java 9+ + * https://github.com/nordfalk/jsyntaxpane/blob/master/jsyntaxpane/src/main/java/jsyntaxpane/CompoundUndoManager.java + * */ public class CompoundUndoMan extends UndoManager { - private CompoundEdit compoundEdit; - // This allows us to start combining operations. - // it will be reset after the first change. - private boolean startCombine = false; - // This holds the start of the last line edited, if edits are on multiple - // lines, then they will not be combined. - private int lastLine = -1; + private final SyntaxDocument doc; - public CompoundUndoMan(SyntaxDocument doc) { - doc.addUndoableEditListener(this); - lastLine = doc.getStartPosition().getOffset(); - } + private CompoundEdit compoundEdit; + // This allows us to start combining operations. + // it will be reset after the first change. + private boolean startCombine = false; + // This holds the start of the last line edited, if edits are on multiple + // lines, then they will not be combined. + private int lastLine = -1; - /** - * Whenever an UndoableEdit happens the edit will either be absorbed - * by the current compound edit or a new compound edit will be started - */ - @Override - public void undoableEditHappened(UndoableEditEvent e) { - // Start a new compound edit + public CompoundUndoMan(SyntaxDocument doc) { + this.doc = doc; + doc.addUndoableEditListener(this); + lastLine = doc.getStartPosition().getOffset(); + } - AbstractDocument.DefaultDocumentEvent docEvt = (DefaultDocumentEvent) e.getEdit(); + /** + * Whenever an UndoableEdit happens the edit will either be absorbed by the + * current compound edit or a new compound edit will be started + */ + @Override + public void undoableEditHappened(UndoableEditEvent e) { + // Start a new compound edit - if (compoundEdit == null) { - compoundEdit = startCompoundEdit(e.getEdit()); - startCombine = false; - return; - } + if (compoundEdit == null) { + compoundEdit = startCompoundEdit(e.getEdit()); + startCombine = false; + updateDirty(); + return; + } + if (e.getEdit() instanceof DefaultDocumentEvent) { + // Java 6 to 8 + AbstractDocument.DefaultDocumentEvent docEvt = (DefaultDocumentEvent) e.getEdit(); - int editLine = ((SyntaxDocument)docEvt.getDocument()).getLineNumberAt(docEvt.getOffset()); + int editLine = doc.getLineNumberAt(docEvt.getOffset()); - // Check for an incremental edit or backspace. - // The Change in Caret position and Document length should both be - // either 1 or -1. - if ((startCombine || Math.abs(docEvt.getLength()) == 1) && editLine == lastLine) { - compoundEdit.addEdit(e.getEdit()); - startCombine = false; - return; - } + // Check for an incremental edit or backspace. + // The Change in Caret position and Document length should both be + // either 1 or -1. + if ((startCombine || Math.abs(docEvt.getLength()) == 1) && editLine == lastLine) { + compoundEdit.addEdit(e.getEdit()); + startCombine = false; + updateDirty(); + return; + } - // Not incremental edit, end previous edit and start a new one - lastLine = editLine; + // Not incremental edit, end previous edit and start a new one + lastLine = editLine; - compoundEdit.end(); - compoundEdit = startCompoundEdit(e.getEdit()); - } + } else // Java 9: It seems that all the edits are wrapped and we cannot get line number! + // See https://github.com/netroby/jdk9-dev/blob/master/jdk/src/java.desktop/share/classes/javax/swing/text/AbstractDocument.java#L279 + // AbstractDocument.DefaultDocumentEventUndoableWrapper docEvt = e.getEdit(); + { + if (startCombine && !e.getEdit().isSignificant()) { + compoundEdit.addEdit(e.getEdit()); + startCombine = false; + updateDirty(); + return; + } + } - /* - ** Each CompoundEdit will store a group of related incremental edits - ** (ie. each character typed or backspaced is an incremental edit) - */ - private CompoundEdit startCompoundEdit(UndoableEdit anEdit) { - // Track Caret and Document information of this compound edit - AbstractDocument.DefaultDocumentEvent docEvt = (DefaultDocumentEvent) anEdit; + compoundEdit.end(); + compoundEdit = startCompoundEdit(e.getEdit()); - // The compound edit is used to store incremental edits + updateDirty(); + } - compoundEdit = new MyCompoundEdit(); - compoundEdit.addEdit(anEdit); + private void updateDirty() { + doc.setCanUndo(canUndo()); + doc.setCanRedo(canRedo()); + } - // The compound edit is added to the UndoManager. All incremental - // edits stored in the compound edit will be undone/redone at once + @Override + protected void undoTo(UndoableEdit edit) throws CannotUndoException { + super.undoTo(edit); + updateDirty(); + } - addEdit(compoundEdit); + @Override + public synchronized void undo() throws CannotUndoException { + super.undo(); + updateDirty(); + } - return compoundEdit; - } + @Override + protected void redoTo(UndoableEdit edit) throws CannotRedoException { + super.redoTo(edit); + updateDirty(); + } - class MyCompoundEdit extends CompoundEdit { + @Override + public synchronized void redo() throws CannotRedoException { + super.redo(); + updateDirty(); + } - @Override - public boolean isInProgress() { - // in order for the canUndo() and canRedo() methods to work - // assume that the compound edit is never in progress - return false; - } + @Override + public synchronized void discardAllEdits() { + super.discardAllEdits(); + updateDirty(); + } - @Override - public void undo() throws CannotUndoException { - // End the edit so future edits don't get absorbed by this edit + /* + ** Each CompoundEdit will store a group of related incremental edits + ** (ie. each character typed or backspaced is an incremental edit) + */ + private CompoundEdit startCompoundEdit(UndoableEdit anEdit) { + // Track Caret and Document information of this compound edit + // AbstractDocument.DefaultDocumentEvent docEvt = (DefaultDocumentEvent) anEdit; - if (compoundEdit != null) { - compoundEdit.end(); - } + // The compound edit is used to store incremental edits + compoundEdit = new MyCompoundEdit(); + compoundEdit.addEdit(anEdit); - super.undo(); + // The compound edit is added to the UndoManager. All incremental + // edits stored in the compound edit will be undone/redone at once + addEdit(compoundEdit); - // Always start a new compound edit after an undo + return compoundEdit; + } - compoundEdit = null; - } - } + class MyCompoundEdit extends CompoundEdit { - /** - * Start to combine the next operations together. Only the next operation is combined. - * The flag is then automatically reset. - */ - public void startCombine() { - startCombine = true; - } + @Override + public boolean isInProgress() { + // in order for the canUndo() and canRedo() methods to work + // assume that the compound edit is never in progress + return false; + } + + @Override + public void undo() throws CannotUndoException { + // End the edit so future edits don't get absorbed by this edit + + if (compoundEdit != null) { + compoundEdit.end(); + } + + super.undo(); + + // Always start a new compound edit after an undo + compoundEdit = null; + } + } + + /** + * Start to combine the next operations together. Only the next operation is + * combined. The flag is then automatically reset. + */ + public void startCombine() { + startCombine = true; + } } diff --git a/libsrc/jsyntaxpane/jsyntaxpane/src/main/java/jsyntaxpane/SyntaxDocument.java b/libsrc/jsyntaxpane/jsyntaxpane/src/main/java/jsyntaxpane/SyntaxDocument.java index fcc5bed2b..efd06df06 100644 --- a/libsrc/jsyntaxpane/jsyntaxpane/src/main/java/jsyntaxpane/SyntaxDocument.java +++ b/libsrc/jsyntaxpane/jsyntaxpane/src/main/java/jsyntaxpane/SyntaxDocument.java @@ -13,6 +13,7 @@ */ package jsyntaxpane; +import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -30,577 +31,616 @@ import javax.swing.text.PlainDocument; import javax.swing.text.Segment; /** - * A document that supports being highlighted. The document maintains an - * internal List of all the Tokens. The Tokens are updated using - * a Lexer, passed to it during construction. - * + * A document that supports being highlighted. The document maintains an + * internal List of all the Tokens. The Tokens are updated using a Lexer, passed + * to it during construction. + * * @author Ayman Al-Sairafi */ public class SyntaxDocument extends PlainDocument { - Lexer lexer; - List tokens; - CompoundUndoMan undo; - boolean ignoreUpdate; + public static final String CAN_UNDO = "can-undo"; + public static final String CAN_REDO = "can-redo"; - public SyntaxDocument(Lexer lexer) { - super(); - putProperty(PlainDocument.tabSizeAttribute, 4); - this.lexer = lexer; - // Listen for undo and redo events - undo = new CompoundUndoMan(this); - } + Lexer lexer; + List tokens; + CompoundUndoMan undo; - /** - * Parse the entire document and return list of tokens that do not already - * exist in the tokens list. There may be overlaps, and replacements, - * which we will cleanup later. - * @return list of tokens that do not exist in the tokens field - */ - private void parse() { - // if we have no lexer, then we must have no tokens... - if (lexer == null) { - tokens = null; - return; - } - int len = getLength(); - List toks = new ArrayList(len / 10); - long ts = System.nanoTime(); - try { - Segment seg = new Segment(); - getText(0, len, seg); - lexer.parse(seg, 0, toks); - } catch (BadLocationException ex) { - log.log(Level.SEVERE, null, ex); - } finally { - if (log.isLoggable(Level.FINEST)) { - log.finest(String.format("Parsed %d in %d ms, giving %d tokens\n", - len, (System.nanoTime() - ts) / 1000000, toks.size())); - } - tokens = toks; - } - } + boolean ignoreUpdate; + private final PropertyChangeSupport propSupport; + private boolean canUndoState = false; + private boolean canRedoState = false; - @Override - protected void fireChangedUpdate(DocumentEvent e) { - parse(); - super.fireChangedUpdate(e); - } + public SyntaxDocument(Lexer lexer) { + super(); + putProperty(PlainDocument.tabSizeAttribute, 4); + this.lexer = lexer; + // Listen for undo and redo events + undo = new CompoundUndoMan(this); + propSupport = new PropertyChangeSupport(this); + } - @Override - protected void fireInsertUpdate(DocumentEvent e) { - if (ignoreUpdate) { - return; - } - parse(); - super.fireInsertUpdate(e); - } + /** + * Parse the entire document and return list of tokens that do not already + * exist in the tokens list. There may be overlaps, and replacements, which + * we will cleanup later. + * + * @return list of tokens that do not exist in the tokens field + */ + private void parse() { + // if we have no lexer, then we must have no tokens... + if (lexer == null) { + tokens = null; + return; + } + int len = getLength(); + List toks = new ArrayList(len / 10); + long ts = System.nanoTime(); + try { + Segment seg = new Segment(); + getText(0, len, seg); + lexer.parse(seg, 0, toks); + } catch (BadLocationException ex) { + log.log(Level.SEVERE, null, ex); + } finally { + if (log.isLoggable(Level.FINEST)) { + log.finest(String.format("Parsed %d in %d ms, giving %d tokens\n", + len, (System.nanoTime() - ts) / 1000000, toks.size())); + } + tokens = toks; + } + } - @Override - protected void fireRemoveUpdate(DocumentEvent e) { - parse(); - super.fireRemoveUpdate(e); - } + @Override + protected void fireChangedUpdate(DocumentEvent e) { + parse(); + super.fireChangedUpdate(e); + } - public void setIgnoreUpdate(boolean value) { - ignoreUpdate = value; - if (!ignoreUpdate) { - parse(); - } - } + @Override + protected void fireInsertUpdate(DocumentEvent e) { + if (ignoreUpdate) { + return; + } + parse(); + super.fireInsertUpdate(e); + } - /** - * Replace the token with the replacement string - * @param token - * @param replacement - */ - public void replaceToken(Token token, String replacement) { - try { - replace(token.start, token.length, replacement, null); - } catch (BadLocationException ex) { - log.log(Level.WARNING, "unable to replace token: " + token, ex); - } - } + @Override + protected void fireRemoveUpdate(DocumentEvent e) { + parse(); + super.fireRemoveUpdate(e); + } - /** - * This class is used to iterate over tokens between two positions - * - */ - class TokenIterator implements ListIterator { + public void setIgnoreUpdate(boolean value) { + ignoreUpdate = value; + if (!ignoreUpdate) { + parse(); + } + } - int start; - int end; - int ndx = 0; + /** + * Replace the token with the replacement string + * + * @param token + * @param replacement + */ + public void replaceToken(Token token, String replacement) { + try { + replace(token.start, token.length, replacement, null); + } catch (BadLocationException ex) { + log.log(Level.WARNING, "unable to replace token: " + token, ex); + } + } - @SuppressWarnings("unchecked") - private TokenIterator(int start, int end) { - this.start = start; - this.end = end; - if (tokens != null && !tokens.isEmpty()) { - Token token = new Token(TokenType.COMMENT, start, end - start); - ndx = Collections.binarySearch((List) tokens, token); - // we will probably not find the exact token... - if (ndx < 0) { - // so, start from one before the token where we should be... - // -1 to get the location, and another -1 to go back.. - ndx = (-ndx - 1 - 1 < 0) ? 0 : (-ndx - 1 - 1); - Token t = tokens.get(ndx); - // if the prev token does not overlap, then advance one - if (t.end() <= start) { - ndx++; - } + /** + * This class is used to iterate over tokens between two positions + * + */ + class TokenIterator implements ListIterator { - } - } - } + int start; + int end; + int ndx = 0; - @Override - public boolean hasNext() { - if (tokens == null) { - return false; - } - if (ndx >= tokens.size()) { - return false; - } - Token t = tokens.get(ndx); - if (t.start >= end) { - return false; - } - return true; - } + @SuppressWarnings("unchecked") + private TokenIterator(int start, int end) { + this.start = start; + this.end = end; + if (tokens != null && !tokens.isEmpty()) { + Token token = new Token(TokenType.COMMENT, start, end - start); + ndx = Collections.binarySearch((List) tokens, token); + // we will probably not find the exact token... + if (ndx < 0) { + // so, start from one before the token where we should be... + // -1 to get the location, and another -1 to go back.. + ndx = (-ndx - 1 - 1 < 0) ? 0 : (-ndx - 1 - 1); + Token t = tokens.get(ndx); + // if the prev token does not overlap, then advance one + if (t.end() <= start) { + ndx++; + } - @Override - public Token next() { - return tokens.get(ndx++); - } + } + } + } - @Override - public void remove() { - throw new UnsupportedOperationException(); - } + @Override + public boolean hasNext() { + if (tokens == null) { + return false; + } + if (ndx >= tokens.size()) { + return false; + } + Token t = tokens.get(ndx); + if (t.start >= end) { + return false; + } + return true; + } - @Override - public boolean hasPrevious() { - if (tokens == null) { - return false; - } - if (ndx <= 0) { - return false; - } - Token t = tokens.get(ndx); - if (t.end() <= start) { - return false; - } - return true; - } + @Override + public Token next() { + return tokens.get(ndx++); + } - @Override - public Token previous() { - return tokens.get(ndx--); - } + @Override + public void remove() { + throw new UnsupportedOperationException(); + } - @Override - public int nextIndex() { - return ndx + 1; - } + @Override + public boolean hasPrevious() { + if (tokens == null) { + return false; + } + if (ndx <= 0) { + return false; + } + Token t = tokens.get(ndx); + if (t.end() <= start) { + return false; + } + return true; + } - @Override - public int previousIndex() { - return ndx - 1; - } + @Override + public Token previous() { + return tokens.get(ndx--); + } - @Override - public void set(Token e) { - throw new UnsupportedOperationException(); - } + @Override + public int nextIndex() { + return ndx + 1; + } - @Override - public void add(Token e) { - throw new UnsupportedOperationException(); - } - } + @Override + public int previousIndex() { + return ndx - 1; + } - /** - * Return an iterator of tokens between p0 and p1. - * @param start start position for getting tokens - * @param end position for last token - * @return Iterator for tokens that overal with range from start to end - */ - public Iterator getTokens(int start, int end) { - return new TokenIterator(start, end); - } + @Override + public void set(Token e) { + throw new UnsupportedOperationException(); + } - /** - * Find the token at a given position. May return null if no token is - * found (whitespace skipped) or if the position is out of range: - * @param pos - * @return - */ - public Token getTokenAt(int pos) { - if (tokens == null || tokens.isEmpty() || pos > getLength()) { - return null; - } - Token tok = null; - Token tKey = new Token(TokenType.DEFAULT, pos, 1); - @SuppressWarnings("unchecked") - int ndx = Collections.binarySearch((List) tokens, tKey); - if (ndx < 0) { - // so, start from one before the token where we should be... - // -1 to get the location, and another -1 to go back.. - ndx = (-ndx - 1 - 1 < 0) ? 0 : (-ndx - 1 - 1); - Token t = tokens.get(ndx); - if ((t.start <= pos) && (pos <= t.end())) { - tok = t; - } - } else { - tok = tokens.get(ndx); - } - return tok; - } + @Override + public void add(Token e) { + throw new UnsupportedOperationException(); + } + } - public Token getWordAt(int offs, Pattern p) { - Token word = null; - try { - Element line = getParagraphElement(offs); - if (line == null) { - return word; - } - int lineStart = line.getStartOffset(); - int lineEnd = Math.min(line.getEndOffset(), getLength()); - Segment seg = new Segment(); - getText(lineStart, lineEnd - lineStart, seg); - if (seg.count > 0) { - // we need to get the word using the words pattern p - Matcher m = p.matcher(seg); - int o = offs - lineStart; - while (m.find()) { - if (m.start() <= o && o <= m.end()) { - word = new Token(TokenType.DEFAULT, m.start() + lineStart, m.end() - m.start()); - break; - } - } - } - } catch (BadLocationException ex) { - Logger.getLogger(SyntaxDocument.class.getName()).log(Level.SEVERE, null, ex); - } finally { - return word; - } - } + /** + * Return an iterator of tokens between p0 and p1. + * + * @param start start position for getting tokens + * @param end position for last token + * @return Iterator for tokens that overal with range from start to end + */ + public Iterator getTokens(int start, int end) { + return new TokenIterator(start, end); + } - /** - * Return the token following the current token, or null - * This is an expensive operation, so do not use it to update the gui - * @param tok - * @return - */ - public Token getNextToken(Token tok) { - int n = tokens.indexOf(tok); - if ((n >= 0) && (n < (tokens.size() - 1))) { - return tokens.get(n + 1); - } else { - return null; - } - } + /** + * Find the token at a given position. May return null if no token is found + * (whitespace skipped) or if the position is out of range: + * + * @param pos + * @return + */ + public Token getTokenAt(int pos) { + if (tokens == null || tokens.isEmpty() || pos > getLength()) { + return null; + } + Token tok = null; + Token tKey = new Token(TokenType.DEFAULT, pos, 1); + @SuppressWarnings("unchecked") + int ndx = Collections.binarySearch((List) tokens, tKey); + if (ndx < 0) { + // so, start from one before the token where we should be... + // -1 to get the location, and another -1 to go back.. + ndx = (-ndx - 1 - 1 < 0) ? 0 : (-ndx - 1 - 1); + Token t = tokens.get(ndx); + if ((t.start <= pos) && (pos <= t.end())) { + tok = t; + } + } else { + tok = tokens.get(ndx); + } + return tok; + } - /** - * Return the token prior to the given token, or null - * This is an expensive operation, so do not use it to update the gui - * @param tok - * @return - */ - public Token getPrevToken(Token tok) { - int n = tokens.indexOf(tok); - if ((n > 0) && (!tokens.isEmpty())) { - return tokens.get(n - 1); - } else { - return null; - } - } + public Token getWordAt(int offs, Pattern p) { + Token word = null; + try { + Element line = getParagraphElement(offs); + if (line == null) { + return word; + } + int lineStart = line.getStartOffset(); + int lineEnd = Math.min(line.getEndOffset(), getLength()); + Segment seg = new Segment(); + getText(lineStart, lineEnd - lineStart, seg); + if (seg.count > 0) { + // we need to get the word using the words pattern p + Matcher m = p.matcher(seg); + int o = offs - lineStart; + while (m.find()) { + if (m.start() <= o && o <= m.end()) { + word = new Token(TokenType.DEFAULT, m.start() + lineStart, m.end() - m.start()); + break; + } + } + } + } catch (BadLocationException ex) { + Logger.getLogger(SyntaxDocument.class.getName()).log(Level.SEVERE, null, ex); + } finally { + return word; + } + } - /** - * This is used to return the other part of a paired token in the document. - * A paired part has token.pairValue <> 0, and the paired token will - * have the negative of t.pairValue. - * This method properly handles nestings of same pairValues, but overlaps - * are not checked. - * if The document does not contain a paired token, then null is returned. - * @param t - * @return the other pair's token, or null if nothing is found. - */ - public Token getPairFor(Token t) { - if (t == null || t.pairValue == 0) { - return null; - } - Token p = null; - int ndx = tokens.indexOf(t); - // w will be similar to a stack. The openners weght is added to it - // and the closers are subtracted from it (closers are already negative) - int w = t.pairValue; - int direction = (t.pairValue > 0) ? 1 : -1; - boolean done = false; - int v = Math.abs(t.pairValue); - while (!done) { - ndx += direction; - if (ndx < 0 || ndx >= tokens.size()) { - break; - } - Token current = tokens.get(ndx); - if (Math.abs(current.pairValue) == v) { - w += current.pairValue; - if (w == 0) { - p = current; - done = true; - } - } - } + /** + * Return the token following the current token, or null + * This is an expensive operation, so do not use it to update the gui + * + * @param tok + * @return + */ + public Token getNextToken(Token tok) { + int n = tokens.indexOf(tok); + if ((n >= 0) && (n < (tokens.size() - 1))) { + return tokens.get(n + 1); + } else { + return null; + } + } - return p; - } + /** + * Return the token prior to the given token, or null + * This is an expensive operation, so do not use it to update the gui + * + * @param tok + * @return + */ + public Token getPrevToken(Token tok) { + int n = tokens.indexOf(tok); + if ((n > 0) && (!tokens.isEmpty())) { + return tokens.get(n - 1); + } else { + return null; + } + } - /** - * Perform an undo action, if possible - */ - public void doUndo() { - if (undo.canUndo()) { - undo.undo(); - parse(); - } - } + /** + * This is used to return the other part of a paired token in the document. + * A paired part has token.pairValue <> 0, and the paired token will have + * the negative of t.pairValue. This method properly handles nestings of + * same pairValues, but overlaps are not checked. if The document does not + * contain a paired token, then null is returned. + * + * @param t + * @return the other pair's token, or null if nothing is found. + */ + public Token getPairFor(Token t) { + if (t == null || t.pairValue == 0) { + return null; + } + Token p = null; + int ndx = tokens.indexOf(t); + // w will be similar to a stack. The openners weght is added to it + // and the closers are subtracted from it (closers are already negative) + int w = t.pairValue; + int direction = (t.pairValue > 0) ? 1 : -1; + boolean done = false; + int v = Math.abs(t.pairValue); + while (!done) { + ndx += direction; + if (ndx < 0 || ndx >= tokens.size()) { + break; + } + Token current = tokens.get(ndx); + if (Math.abs(current.pairValue) == v) { + w += current.pairValue; + if (w == 0) { + p = current; + done = true; + } + } + } - /** - * Perform a redo action, if possible. - */ - public void doRedo() { - if (undo.canRedo()) { - undo.redo(); - parse(); - } - } + return p; + } - /** - * Return a matcher that matches the given pattern on the entire document - * @param pattern - * @return matcher object - */ - public Matcher getMatcher(Pattern pattern) { - return getMatcher(pattern, 0, getLength()); - } + /** + * Perform an undo action, if possible + */ + public void doUndo() { + if (undo.canUndo()) { + undo.undo(); + parse(); + } + } - /** - * Return a matcher that matches the given pattern in the part of the - * document starting at offset start. Note that the matcher will have - * offset starting from start - * - * @param pattern - * @param start - * @return matcher that MUST be offset by start to get the proper - * location within the document - */ - public Matcher getMatcher(Pattern pattern, int start) { - return getMatcher(pattern, start, getLength() - start); - } + /** + * Perform a redo action, if possible. + */ + public void doRedo() { + if (undo.canRedo()) { + undo.redo(); + parse(); + } + } - /** - * Return a matcher that matches the given pattern in the part of the - * document starting at offset start and ending at start + length. - * Note that the matcher will have - * offset starting from start - * - * @param pattern - * @param start - * @param length - * @return matcher that MUST be offset by start to get the proper - * location within the document - */ - public Matcher getMatcher(Pattern pattern, int start, int length) { - Matcher matcher = null; - if (getLength() == 0) { - return null; - } - if (start >= getLength()) { - return null; - } - try { - if (start < 0) { - start = 0; - } - if (start + length > getLength()) { - length = getLength() - start; - } - Segment seg = new Segment(); - getText(start, length, seg); - matcher = pattern.matcher(seg); - } catch (BadLocationException ex) { - log.log(Level.SEVERE, "Requested offset: " + ex.offsetRequested(), ex); - } - return matcher; - } + /** + * Return a matcher that matches the given pattern on the entire document + * + * @param pattern + * @return matcher object + */ + public Matcher getMatcher(Pattern pattern) { + return getMatcher(pattern, 0, getLength()); + } - /** - * This will discard all undoable edits - */ - public void clearUndos() { - undo.discardAllEdits(); - } + /** + * Return a matcher that matches the given pattern in the part of the + * document starting at offset start. Note that the matcher will have offset + * starting from start + * + * @param pattern + * @param start + * @return matcher that MUST be offset by start to get the proper + * location within the document + */ + public Matcher getMatcher(Pattern pattern, int start) { + return getMatcher(pattern, start, getLength() - start); + } - /** - * Gets the line at given position. The line returned will NOT include - * the line terminator '\n' - * @param pos Position (usually from text.getCaretPosition() - * @return the STring of text at given position - * @throws BadLocationException - */ - public String getLineAt(int pos) throws BadLocationException { - Element e = getParagraphElement(pos); - Segment seg = new Segment(); - getText(e.getStartOffset(), e.getEndOffset() - e.getStartOffset(), seg); - char last = seg.last(); - if (last == '\n' || last == '\r') { - seg.count--; - } - return seg.toString(); - } + /** + * Return a matcher that matches the given pattern in the part of the + * document starting at offset start and ending at start + length. Note that + * the matcher will have offset starting from start + * + * @param pattern + * @param start + * @param length + * @return matcher that MUST be offset by start to get the proper + * location within the document + */ + public Matcher getMatcher(Pattern pattern, int start, int length) { + Matcher matcher = null; + if (getLength() == 0) { + return null; + } + if (start >= getLength()) { + return null; + } + try { + if (start < 0) { + start = 0; + } + if (start + length > getLength()) { + length = getLength() - start; + } + Segment seg = new Segment(); + getText(start, length, seg); + matcher = pattern.matcher(seg); + } catch (BadLocationException ex) { + log.log(Level.SEVERE, "Requested offset: " + ex.offsetRequested(), ex); + } + return matcher; + } - /** - * Deletes the line at given position - * @param pos - * @throws javax.swing.text.BadLocationException - */ - public void removeLineAt(int pos) - throws BadLocationException { - Element e = getParagraphElement(pos); - remove(e.getStartOffset(), getElementLength(e)); - } + /** + * This will discard all undoable edits + */ + public void clearUndos() { + undo.discardAllEdits(); + } - /** - * Replace the line at given position with the given string, which can span - * multiple lines - * @param pos - * @param newLines - * @throws javax.swing.text.BadLocationException - */ - public void replaceLineAt(int pos, String newLines) - throws BadLocationException { - Element e = getParagraphElement(pos); - replace(e.getStartOffset(), getElementLength(e), newLines, null); - } + /** + * Gets the line at given position. The line returned will NOT include the + * line terminator '\n' + * + * @param pos Position (usually from text.getCaretPosition() + * @return the STring of text at given position + * @throws BadLocationException + */ + public String getLineAt(int pos) throws BadLocationException { + Element e = getParagraphElement(pos); + Segment seg = new Segment(); + getText(e.getStartOffset(), e.getEndOffset() - e.getStartOffset(), seg); + char last = seg.last(); + if (last == '\n' || last == '\r') { + seg.count--; + } + return seg.toString(); + } - /** - * Helper method to get the length of an element and avoid getting - * a too long element at the end of the document - * @param e - * @return - */ - private int getElementLength(Element e) { - int end = e.getEndOffset(); - if (end >= (getLength() - 1)) { - end--; - } - return end - e.getStartOffset(); - } + /** + * Deletes the line at given position + * + * @param pos + * @throws javax.swing.text.BadLocationException + */ + public void removeLineAt(int pos) + throws BadLocationException { + Element e = getParagraphElement(pos); + remove(e.getStartOffset(), getElementLength(e)); + } - /** - * Gets the text without the comments. For example for the string - * { // it's a comment this method will return "{ ". - * @param aStart start of the text. - * @param anEnd end of the text. - * @return String for the line without comments (if exists). - */ - public synchronized String getUncommentedText(int aStart, int anEnd) { - readLock(); - StringBuilder result = new StringBuilder(); - Iterator iter = getTokens(aStart, anEnd); - while (iter.hasNext()) { - Token t = iter.next(); - if (!TokenType.isComment(t)) { - result.append(t.getText(this)); - } - } - readUnlock(); - return result.toString(); - } + /** + * Replace the line at given position with the given string, which can span + * multiple lines + * + * @param pos + * @param newLines + * @throws javax.swing.text.BadLocationException + */ + public void replaceLineAt(int pos, String newLines) + throws BadLocationException { + Element e = getParagraphElement(pos); + replace(e.getStartOffset(), getElementLength(e), newLines, null); + } - /** - * Returns the starting position of the line at pos - * @param pos - * @return starting position of the line - */ - public int getLineStartOffset(int pos) { - return getParagraphElement(pos).getStartOffset(); - } + /** + * Helper method to get the length of an element and avoid getting a too + * long element at the end of the document + * + * @param e + * @return + */ + private int getElementLength(Element e) { + int end = e.getEndOffset(); + if (end >= (getLength() - 1)) { + end--; + } + return end - e.getStartOffset(); + } - /** - * Returns the end position of the line at pos. - * Does a bounds check to ensure the returned value does not exceed - * document length - * @param pos - * @return - */ - public int getLineEndOffset(int pos) { - int end = 0; - end = getParagraphElement(pos).getEndOffset(); - if (end >= getLength()) { - end = getLength(); - } - return end; - } + /** + * Gets the text without the comments. For example for the string + * { // it's a comment this method will return "{ ". + * + * @param aStart start of the text. + * @param anEnd end of the text. + * @return String for the line without comments (if exists). + */ + public synchronized String getUncommentedText(int aStart, int anEnd) { + readLock(); + StringBuilder result = new StringBuilder(); + Iterator iter = getTokens(aStart, anEnd); + while (iter.hasNext()) { + Token t = iter.next(); + if (!TokenType.isComment(t)) { + result.append(t.getText(this)); + } + } + readUnlock(); + return result.toString(); + } - /** - * Return the number of lines in this document - * @return - */ - public int getLineCount() { - Element e = getDefaultRootElement(); - int cnt = e.getElementCount(); - return cnt; - } + /** + * Returns the starting position of the line at pos + * + * @param pos + * @return starting position of the line + */ + public int getLineStartOffset(int pos) { + return getParagraphElement(pos).getStartOffset(); + } - /** - * Return the line number at given position. The line numbers are zero based - * @param pos - * @return - */ - public int getLineNumberAt(int pos) { - int lineNr = getDefaultRootElement().getElementIndex(pos); - return lineNr; - } + /** + * Returns the end position of the line at pos. Does a bounds check to + * ensure the returned value does not exceed document length + * + * @param pos + * @return + */ + public int getLineEndOffset(int pos) { + int end = getParagraphElement(pos).getEndOffset(); + if (end >= getLength()) { + end = getLength(); + } + return end; + } - @Override - public String toString() { - return "SyntaxDocument(" + lexer + ", " + ((tokens == null) ? 0 : tokens.size()) + " tokens)@" + - hashCode(); - } + /** + * Return the number of lines in this document + * + * @return + */ + public int getLineCount() { + Element e = getDefaultRootElement(); + int cnt = e.getElementCount(); + return cnt; + } - /** - * We override this here so that the replace is treated as one operation - * by the undomanager - * @param offset - * @param length - * @param text - * @param attrs - * @throws BadLocationException - */ - @Override - public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException { - remove(offset, length); - undo.startCombine(); - insertString(offset, text, attrs); - } + /** + * Return the line number at given position. The line numbers are zero based + * + * @param pos + * @return + */ + public int getLineNumberAt(int pos) { + int lineNr = getDefaultRootElement().getElementIndex(pos); + return lineNr; + } - /** - * Append the given string to the text of this document. - * @param str - * @return this document - */ - public SyntaxDocument append(String str) { - try { - insertString(getLength(), str, null); - } catch (BadLocationException ex) { - log.log(Level.WARNING, "Error appending str", ex); - } - return this; - } + @Override + public String toString() { + return "SyntaxDocument(" + lexer + ", " + ((tokens == null) ? 0 : tokens.size()) + " tokens)@" + + hashCode(); + } + + /** + * We override this here so that the replace is treated as one operation by + * the undomanager + * + * @param offset + * @param length + * @param text + * @param attrs + * @throws BadLocationException + */ + @Override + public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException { + remove(offset, length); + undo.startCombine(); + insertString(offset, text, attrs); + } + + /** + * Append the given string to the text of this document. + * + * @param str + * @return this document + */ + public SyntaxDocument append(String str) { + try { + insertString(getLength(), str, null); + } catch (BadLocationException ex) { + log.log(Level.WARNING, "Error appending str", ex); + } + return this; + } + + public void setCanUndo(boolean value) { + if (canUndoState != value) { + // System.out.println("canUndo = " + value); + canUndoState = value; + propSupport.firePropertyChange(CAN_UNDO, !value, value); + } + } + + public void setCanRedo(boolean value) { + if (canRedoState != value) { + // System.out.println("canRedo = " + value); + canRedoState = value; + propSupport.firePropertyChange(CAN_REDO, !value, value); + } + } // our logger instance... - private static final Logger log = Logger.getLogger(SyntaxDocument.class.getName()); + private static final Logger log = Logger.getLogger(SyntaxDocument.class.getName()); }