diff options
author | Deepak Azad | 2012-03-11 22:48:38 +0000 |
---|---|---|
committer | Markus Keller | 2012-03-11 22:48:38 +0000 |
commit | ab31f1a2507f1497a0f78a48fe2faaed037040c4 (patch) | |
tree | bd315d541244904f780a253efddebf0e3828fadb | |
parent | 72d64a944d28d118c91a21fbd05c9b7a3febe8c5 (diff) | |
download | eclipse.platform.text-ab31f1a2507f1497a0f78a48fe2faaed037040c4.tar.gz eclipse.platform.text-ab31f1a2507f1497a0f78a48fe2faaed037040c4.tar.xz eclipse.platform.text-ab31f1a2507f1497a0f78a48fe2faaed037040c4.zip |
Bug 372128: [syntax highlighting] "Highlight enclosing brackets" hasv20120311-2248
massive performance problems
3 files changed, 424 insertions, 62 deletions
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/DefaultCharacterPairMatcher.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/DefaultCharacterPairMatcher.java index 4983562119f..318fa547ae7 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/DefaultCharacterPairMatcher.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/DefaultCharacterPairMatcher.java @@ -9,9 +9,6 @@ * Christian Plesner Hansen (plesner@quenta.org) - initial API and implementation *******************************************************************************/ package org.eclipse.jface.text.source; -import java.util.HashSet; -import java.util.Set; - import org.eclipse.core.runtime.Assert; import org.eclipse.jface.text.BadLocationException; @@ -132,38 +129,94 @@ public class DefaultCharacterPairMatcher implements ICharacterPairMatcher, IChar if (document == null || offset < 0 || offset > document.getLength()) return null; - int start; - int end; - if (length >= 0) { - start= offset; - end= offset + length; - } else { - end= offset; - start= offset + length; + //maybe a bracket is selected + IRegion region= match(document, offset, length); + fAnchor= ICharacterPairMatcher.LEFT; //always set the anchor to LEFT + if (region != null) { + return region; } - int sourceCaretOffset= offset + length; - int adjustment= getOffsetAdjustment(document, sourceCaretOffset, length); - sourceCaretOffset+= adjustment; + //bracket is not selected + try { + final String partition1= TextUtilities.getContentType(document, fPartitioning, offset, false); + final DocumentPartitionAccessor partDoc= new DocumentPartitionAccessor(document, fPartitioning, partition1); + return findEnclosingPeers(document, partDoc, offset, length, 0, document.getLength()); + } catch (BadLocationException ble) { + fAnchor= -1; + return null; + } + } + + /** + * @see org.eclipse.jface.text.source.ICharacterPairMatcherExtension#isMatchedChar(char) + * @since 3.8 + */ + public boolean isMatchedChar(char ch) { + return fPairs.contains(ch); + } + + /** + * @see org.eclipse.jface.text.source.ICharacterPairMatcherExtension#isMatchedChar(char, + * org.eclipse.jface.text.IDocument, int) + * @since 3.8 + */ + public boolean isMatchedChar(char ch, IDocument document, int offset) { + return isMatchedChar(ch); + } + + /** + * @see org.eclipse.jface.text.source.ICharacterPairMatcherExtension#isRecomputationOfEnclosingPairRequired(org.eclipse.jface.text.IDocument, + * org.eclipse.jface.text.IRegion, org.eclipse.jface.text.IRegion) + * @since 3.8 + */ + public boolean isRecomputationOfEnclosingPairRequired(IDocument document, IRegion currentSelection, IRegion previousSelection) { + int previousStartOffset= previousSelection.getOffset(); + int currentStartOffset= currentSelection.getOffset(); + int previousEndOffset= previousStartOffset + previousSelection.getLength(); + int currentEndOffset= currentStartOffset + currentSelection.getLength(); try { - for (int offset1= sourceCaretOffset; offset1 >= 0; offset1--) { - char prevChar= document.getChar(Math.max(offset1 - 1, 0)); - if (fPairs.contains(prevChar) && fPairs.isStartCharacter(prevChar)) { - IRegion match= performMatch(document, offset1); - if (match != null) { - int matchOffset= match.getOffset(); - int matchLength= match.getLength(); - if ((matchOffset <= start) && (matchOffset + matchLength > start) && (matchOffset < end) && (matchOffset + matchLength >= end)) { - return match; - } - } + String prevEndContentType= TextUtilities.getContentType(document, fPartitioning, previousEndOffset, false); + String currEndContentType= TextUtilities.getContentType(document, fPartitioning, currentEndOffset, false); + if (!prevEndContentType.equals(currEndContentType)) + return true; + + String prevStartContentType= TextUtilities.getContentType(document, fPartitioning, previousEndOffset, false); + String currStartContentType= TextUtilities.getContentType(document, fPartitioning, currentEndOffset, false); + if (!prevStartContentType.equals(currStartContentType)) + return true; + + int start; + int end; + if (currentEndOffset > previousEndOffset) { + start= previousEndOffset; + end= currentEndOffset; + } else { + start= currentEndOffset; + end= previousEndOffset; + } + for (int i= start; i < end; i++) { + if (isMatchedChar(document.getChar(i))) { + return true; } } - } catch (BadLocationException ble) { - return null; + + if (currentStartOffset > previousStartOffset) { + start= previousStartOffset; + end= currentStartOffset; + } else { + start= currentStartOffset; + end= previousStartOffset; + } + for (int i= start; i < end; i++) { + if (isMatchedChar(document.getChar(i))) { + return true; + } + } + } catch (BadLocationException e) { + //do nothing } - return null; + return false; } /** @@ -261,6 +314,106 @@ public class DefaultCharacterPairMatcher implements ICharacterPairMatcher, IChar return -1; } + /* + * Performs the actual work of finding enclosing peer characters for #findEnclosingPeerCharacters(IDocument, int, int). + */ + private IRegion findEnclosingPeers(IDocument document, DocumentPartitionAccessor doc, int offset, int length, int lowerBoundary, int upperBoundary) throws BadLocationException { + char[] pairs= fPairs.fPairs; + + int start; + int end; + if (length >= 0) { + start= offset; + end= offset + length; + } else { + end= offset; + start= offset + length; + } + + boolean lowerFound= false; + boolean upperFound= false; + int[][] counts= new int[pairs.length][2]; + int pos1= doc.getNextPosition(start, false); + int pos2= start; + + while ((pos1 >= lowerBoundary && !lowerFound) || (pos2 < upperBoundary && !upperFound)) { + for (int i= 0; i < counts.length; i++) { + counts[i][0]= counts[i][1]= 0; + } + + outer1: while (pos1 >= lowerBoundary && !lowerFound) { + final char c= doc.getChar(pos1); + int i= getCharacterIndex(c, document, pos1); + if (i != -1 && doc.inPartition(pos1)) { + if (i % 2 == 0) { + counts[i / 2][0]--; //start + } else { + counts[i / 2][0]++; //end + } + for (int j= 0; j < counts.length; j++) { + if (counts[j][0] == -1) { + lowerFound= true; + break outer1; + } + } + } + pos1= doc.getNextPosition(pos1, false); + } + + outer2: while (pos2 < upperBoundary && !upperFound) { + final char c= doc.getChar(pos2); + int i= getCharacterIndex(c, document, pos2); + if (i != -1 && doc.inPartition(pos2)) { + if (i % 2 == 0) { + counts[i / 2][1]++; //start + } else { + counts[i / 2][1]--; //end + } + for (int j= 0; j < counts.length; j++) { + if (counts[j][1] == -1 && counts[j][0] == -1) { + upperFound= true; + break outer2; + } + } + } + pos2= doc.getNextPosition(pos2, true); + } + + if (pos1 > start || pos2 < end) { + //match inside selection => discard + pos1= doc.getNextPosition(pos1, false); + pos2= doc.getNextPosition(pos2, true); + lowerFound= false; + upperFound= false; + } + } + pos2++; + if (pos1 < lowerBoundary || pos2 > upperBoundary) + return null; + return new Region(pos1, pos2 - pos1); + } + + /** + * Determines the index of the character in the char array passed to the constructor of the pair + * matcher. + * + * @param ch the character + * @param document the document + * @param offset the offset in document + * @return the index of the character in the char array passed to the constructor of the pair + * matcher, and -1 if the character is not one of the matched characters + * @since 3.8 + */ + private int getCharacterIndex(char ch, IDocument document, int offset) { + char[] pairs= fPairs.fPairs; + for (int i= 0; i < pairs.length; i++) { + if (pairs[i] == ch && isMatchedChar(ch, document, offset)) { + return i; + } + } + return -1; + } + /* @see ICharacterPairMatcher#getAnchor() */ public int getAnchor() { return fAnchor; @@ -285,6 +438,7 @@ public class DefaultCharacterPairMatcher implements ICharacterPairMatcher, IChar private final IDocument fDocument; private final String fPartitioning, fPartition; private ITypedRegion fCachedPartition; + private int fLength; /** * Creates a new partitioned document for the specified document. @@ -298,6 +452,7 @@ public class DefaultCharacterPairMatcher implements ICharacterPairMatcher, IChar fDocument= doc; fPartitioning= partitioning; fPartition= partition; + fLength= doc.getLength(); } /** @@ -338,7 +493,7 @@ public class DefaultCharacterPairMatcher implements ICharacterPairMatcher, IChar } /** - * Returns the next position to query in the search. The position + * Returns the next position to query in the search. The position * is not guaranteed to be in this document's partition. * * @param pos an offset within the document @@ -347,8 +502,7 @@ public class DefaultCharacterPairMatcher implements ICharacterPairMatcher, IChar */ public int getNextPosition(int pos, boolean searchForward) { final ITypedRegion partition= getPartition(pos); - if (partition == null) return simpleIncrement(pos, searchForward); - if (fPartition.equals(partition.getType())) + if (partition == null || fPartition.equals(partition.getType())) return simpleIncrement(pos, searchForward); if (searchForward) { int end= partition.getOffset() + partition.getLength(); @@ -376,7 +530,7 @@ public class DefaultCharacterPairMatcher implements ICharacterPairMatcher, IChar */ private ITypedRegion getPartition(int pos) { if (fCachedPartition == null || !contains(fCachedPartition, pos)) { - Assert.isTrue(pos >= 0 && pos <= fDocument.getLength()); + Assert.isTrue(pos >= 0 && pos <= fLength); try { fCachedPartition= TextUtilities.getPartition(fDocument, fPartitioning, pos, false); } catch (BadLocationException e) { @@ -405,28 +559,18 @@ public class DefaultCharacterPairMatcher implements ICharacterPairMatcher, IChar } /** - * Returns true if the specified character pair occurs in one - * of the character pairs. - * + * Returns true if the specified character occurs in one of the character pairs. + * * @param c a character * @return true exactly if the character occurs in one of the pairs */ public boolean contains(char c) { - return getAllCharacters().contains(new Character(c)); - } - - private Set/*<Character>*/ fCharsCache= null; - /** - * @return A set containing all characters occurring in character pairs. - */ - private Set/*<Character>*/ getAllCharacters() { - if (fCharsCache == null) { - Set/*<Character>*/ set= new HashSet/*<Character>*/(); - for (int i= 0; i < fPairs.length; i++) - set.add(new Character(fPairs[i])); - fCharsCache= set; + char[] pairs= fPairs; + for (int i= 0, n= pairs.length; i < n; i++) { + if (c == pairs[i]) + return true; } - return fCharsCache; + return false; } /** diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/ICharacterPairMatcherExtension.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/ICharacterPairMatcherExtension.java index ef87038a5b1..fb64f422f34 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/ICharacterPairMatcherExtension.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/ICharacterPairMatcherExtension.java @@ -49,4 +49,48 @@ public interface ICharacterPairMatcherExtension { * enclosing pair */ IRegion findEnclosingPeerCharacters(IDocument document, int offset, int length); + + /** + * Checks whether the character is one of the characters matched by the pair matcher. + * + * @param ch the character + * @return <code>true</code> if the the character is one of the characters matched by the pair + * matcher, and <code>false</code> otherwise + */ + boolean isMatchedChar(char ch); + + /** + * Checks whether the character is one of the characters matched by the pair matcher. + * + * <p> + * Clients can use this method to handle characters which may have special meaning in some + * situations. E.g. in Java, '<' is used as an angular bracket and as well as less-than operator. + * </p> + * + * @param ch the character + * @param document the document + * @param offset the offset in document + * @return <code>true</code> if the the character is one of the characters matched by the pair + * matcher, and <code>false</code> otherwise + */ + boolean isMatchedChar(char ch, IDocument document, int offset); + + /** + * Computes whether a client needs to recompute the enclosing pair after a selection change in + * the document. + * + * <p> + * This is intended to be a quick test to determine whether a re-computation of the enclosing pair is + * required, as the re-computation after each selection change via a + * {@link #findEnclosingPeerCharacters(IDocument, int, int)} call can be expensive for some + * clients. + * </p> + * + * @param document the document to work on + * @param currentSelection the current selection in the document + * @param previousSelection the previous selection in the document + * @return <code>true</code> if the enclosing pair needs to be recomputed, <code>false</code> + * otherwise + */ + boolean isRecomputationOfEnclosingPairRequired(IDocument document, IRegion currentSelection, IRegion previousSelection); } diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/MatchingCharacterPainter.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/MatchingCharacterPainter.java index f32f1a0b7fc..4b416d2b863 100644 --- a/org.eclipse.jface.text/src/org/eclipse/jface/text/source/MatchingCharacterPainter.java +++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/source/MatchingCharacterPainter.java @@ -20,21 +20,27 @@ import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IPaintPositionManager; import org.eclipse.jface.text.IPainter; import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.ITextInputListener; +import org.eclipse.jface.text.ITextListener; import org.eclipse.jface.text.ITextViewerExtension5; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextEvent; /** - * Highlights the peer character matching the character near the caret position. - * This painter can be configured with an - * {@link org.eclipse.jface.text.source.ICharacterPairMatcher}. + * Highlights the peer character matching the character near the caret position, or a pair of peer + * characters enclosing the caret position. This painter can be configured with an + * {@link org.eclipse.jface.text.source.ICharacterPairMatcher} or an + * {@link org.eclipse.jface.text.source.ICharacterPairMatcherExtension}. * <p> - * Clients instantiate and configure object of this class.</p> - * + * Clients instantiate and configure an object of this class. + * </p> + * * @since 2.1 */ public final class MatchingCharacterPainter implements IPainter, PaintListener { @@ -55,13 +61,62 @@ public final class MatchingCharacterPainter implements IPainter, PaintListener { private Position fPairPosition= new Position(0, 0); /** The anchor indicating whether the character is left or right of the caret */ private int fAnchor; - /** Whether to highlight enclosing peer characters or not. */ - private boolean fHighlightEnclosingPeerCharcters; - /** Whether to highlight the character at caret location or not. */ + + /** + * Whether to highlight enclosing peer characters or not. + * + * @since 3.8 + */ + private boolean fHighlightEnclosingPeerCharacters; + + /** + * Whether to highlight the character at caret location or not. + * + * @since 3.8 + */ private boolean fHighlightCharacterAtCaretLocation; - /** Whether a character is present at caret location or not. */ + + /** + * Whether a character is present at caret location or not. + * + * @since 3.8 + */ private boolean fCharacterPresentAtCaretLocation; + /** + * The document this painter is associated with. + * + * @since 3.8 + */ + private IDocument fDocument; + + /** + * The previous selection, used to determine the need for computing enclosing brackets. + * + * @since 3.8 + */ + private IRegion fPreviousSelection; + + /** + * Previous length of the document this painter is associated with. + * + * @since 3.8 + */ + private int fPreviousLengthOfDocument; + + /** + * Whether the input document has been replaced or not. + * + * @since 3.8 + */ + private boolean fDocumentChanged; + + /** + * The text viewer change listener. + * + * @since 3.8 + */ + private TextListener fTextListener; /** * Creates a new MatchingCharacterPainter for the given source viewer using the given character @@ -75,6 +130,7 @@ public final class MatchingCharacterPainter implements IPainter, PaintListener { fSourceViewer= sourceViewer; fMatcher= matcher; fTextWidget= sourceViewer.getTextWidget(); + fDocument= fSourceViewer.getDocument(); } /** @@ -95,7 +151,8 @@ public final class MatchingCharacterPainter implements IPainter, PaintListener { * @since 3.8 */ public void setHighlightEnclosingPeerCharacters(boolean highlightEnclosingPeerCharcters) { - fHighlightEnclosingPeerCharcters= highlightEnclosingPeerCharcters; + fHighlightEnclosingPeerCharacters= highlightEnclosingPeerCharcters; + installUninstallTextListener(highlightEnclosingPeerCharcters); } /** @@ -112,6 +169,10 @@ public final class MatchingCharacterPainter implements IPainter, PaintListener { */ public void dispose() { if (fMatcher != null) { + if (fMatcher instanceof ICharacterPairMatcherExtension && fTextListener != null) { + installUninstallTextListener(false); + } + fMatcher.clear(); fMatcher= null; } @@ -185,7 +246,7 @@ public final class MatchingCharacterPainter implements IPainter, PaintListener { offset -= region.getOffset(); } - if (fHighlightCharacterAtCaretLocation || (fHighlightEnclosingPeerCharcters && !fCharacterPresentAtCaretLocation)) { + if (fHighlightCharacterAtCaretLocation || (fHighlightEnclosingPeerCharacters && !fCharacterPresentAtCaretLocation)) { draw(gc, offset, 1); draw(gc, offset + length - 1, 1); } else { @@ -262,7 +323,7 @@ public final class MatchingCharacterPainter implements IPainter, PaintListener { */ public void paint(int reason) { - IDocument document= fSourceViewer.getDocument(); + IDocument document= fDocument; if (document == null) { deactivate(false); return; @@ -276,7 +337,28 @@ public final class MatchingCharacterPainter implements IPainter, PaintListener { ICharacterPairMatcherExtension matcher= (ICharacterPairMatcherExtension)fMatcher; pair= matcher.match(document, selection.getOffset(), selection.getLength()); characterPresentAtCaretLocation= (pair != null); - if (pair == null && fHighlightEnclosingPeerCharcters) { + if (pair == null && fHighlightEnclosingPeerCharacters) { + + int length= document.getLength(); + boolean lengthChanged= length != fPreviousLengthOfDocument; + fPreviousLengthOfDocument= length; + + if (reason != IPainter.CONFIGURATION && !fDocumentChanged && !lengthChanged && selection.equals(fPreviousSelection)) { + return; + } + + if (reason == IPainter.TEXT_CHANGE) { + fPreviousSelection= selection; + return; + } + + fDocumentChanged= false; + if (reason != IPainter.CONFIGURATION && !lengthChanged && fPreviousSelection != null && reason != IPainter.INTERNAL) { + if (!matcher.isRecomputationOfEnclosingPairRequired(document, selection, fPreviousSelection)) { + fPreviousSelection= selection; + return; + } + } pair= matcher.findEnclosingPeerCharacters(document, selection.getOffset(), selection.getLength()); } } else { @@ -288,6 +370,7 @@ public final class MatchingCharacterPainter implements IPainter, PaintListener { characterPresentAtCaretLocation= (pair != null); } + fPreviousSelection= selection; if (pair == null) { deactivate(true); return; @@ -340,4 +423,95 @@ public final class MatchingCharacterPainter implements IPainter, PaintListener { public void setPositionManager(IPaintPositionManager manager) { fPaintPositionManager= manager; } + + /** + * Installs or uninstalls the text listener depending on the boolean parameter. + * + * @param install <code>true</code> to install the text listener, <code>false</code> to uninstall + * + * @since 3.8 + */ + private void installUninstallTextListener(boolean install) { + if (!(fMatcher instanceof ICharacterPairMatcherExtension)) + return; + + if (install) { + fTextListener= new TextListener(); + fSourceViewer.addTextInputListener(fTextListener); + fSourceViewer.addTextListener(fTextListener); + } else { + if (fTextListener != null) { + fSourceViewer.removeTextInputListener(fTextListener); + fSourceViewer.removeTextListener(fTextListener); + fTextListener= null; + } + } + } + + /** + * Listens to document changes and if required by those document changes causes a re-computation + * of matching characters. + * + * @since 3.8 + */ + private class TextListener implements ITextListener, ITextInputListener { + + /** + * @see org.eclipse.jface.text.ITextInputListener#inputDocumentAboutToBeChanged(org.eclipse.jface.text.IDocument, + * org.eclipse.jface.text.IDocument) + */ + public void inputDocumentAboutToBeChanged(IDocument oldInput, IDocument newInput) { + //do nothing + } + + /** + * @see org.eclipse.jface.text.ITextInputListener#inputDocumentChanged(org.eclipse.jface.text.IDocument, + * org.eclipse.jface.text.IDocument) + */ + public void inputDocumentChanged(IDocument oldInput, IDocument newInput) { + fDocument= newInput; + fDocumentChanged= true; + } + + /** + * @see org.eclipse.jface.text.ITextListener#textChanged(org.eclipse.jface.text.TextEvent) + */ + public void textChanged(TextEvent event) { + if (!fHighlightEnclosingPeerCharacters || !(fMatcher instanceof ICharacterPairMatcherExtension)) + return; + + String text= event.getText(); + String replacedText= event.getReplacedText(); + + boolean viewerRedrawState= event.getViewerRedrawState(); + DocumentEvent documentEvent= event.getDocumentEvent(); + if (documentEvent == null && !viewerRedrawState) + return; + + ICharacterPairMatcherExtension matcher= (ICharacterPairMatcherExtension)fMatcher; + boolean found= searchForCharacters(text, matcher) || searchForCharacters(replacedText, matcher); + + if (found || (documentEvent == null && viewerRedrawState)) { + paint(IPainter.INTERNAL); + } + } + + /** + * Searches for matched characters in the given string. + * + * @param text the string to search + * @param matcher the pair matcher + * @return <code>true</code> if a matched character is found, <code>false</code> otherwise + */ + private boolean searchForCharacters(String text, ICharacterPairMatcherExtension matcher) { + if (text == null) + return false; + for (int i= 0; i < text.length(); i++) { + if (matcher.isMatchedChar(text.charAt(i))) { + return true; + } + } + return false; + } + } } |