diff options
author | Anton Leherbauer | 2008-02-08 10:17:46 +0000 |
---|---|---|
committer | Anton Leherbauer | 2008-02-08 10:17:46 +0000 |
commit | e192d34edc2b0788bb50e857169056330825e18c (patch) | |
tree | ff1c2bff1859f60d291d6fb4a2393bec8e3df69f | |
parent | ba72e09e05d685197c6d86f806de6ac152e98f0d (diff) | |
download | org.eclipse.cdt-e192d34edc2b0788bb50e857169056330825e18c.tar.gz org.eclipse.cdt-e192d34edc2b0788bb50e857169056330825e18c.tar.xz org.eclipse.cdt-e192d34edc2b0788bb50e857169056330825e18c.zip |
Fix for 110222: Improve pair matcher for angle brackets
3 files changed, 298 insertions, 8 deletions
diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/PairMatcherTest.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/PairMatcherTest.java index 332b2511271..b7bc2434610 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/PairMatcherTest.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/PairMatcherTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2006 IBM Corporation and others. + * Copyright (c) 2000, 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -51,7 +51,7 @@ public class PairMatcherTest extends TestCase { document.setDocumentPartitioner(ICPartitions.C_PARTITIONING, partitioner); fDocument= document; - fPairMatcher= new CPairMatcher(new char[] { '(', ')' }); + fPairMatcher= new CPairMatcher(new char[] { '(', ')', '<', '>' }); } public static Test suite() { @@ -146,12 +146,92 @@ public class PairMatcherTest extends TestCase { assertNotNull(match); assertTrue(match.getOffset() == 3 && match.getLength() == 12); } - } + } public void testAfterClosingMatchWithNLAndSingleLineComment() { fDocument.set("x\nx(y\nx //(x\ny)x"); IRegion match= fPairMatcher.match(fDocument, 15); assertNotNull(match); assertTrue(match.getOffset() == 3 && match.getLength() == 12); + } + + public void testAngleBracketsAsOperators() { + fDocument.set("void f(){ \n\tif (x<y);\n\twhile(x>y)\n\t\tx << 2; y >> 1;\n}"); + int idx= fDocument.get().indexOf('<', 0); + while (idx >= 0) { + IRegion match= fPairMatcher.match(fDocument, idx + 1); + assertNull(match); + idx= fDocument.get().indexOf('<', idx + 1); + } + idx= fDocument.get().indexOf('>', 0); + while (idx >= 0) { + IRegion match= fPairMatcher.match(fDocument, idx + 1); + assertNull(match); + idx= fDocument.get().indexOf('>', idx + 1); + } + } + + public void testAngleBracketsAsPairs() { + fDocument.set("template < class X > class A {};}"); + int idx= fDocument.get().indexOf('<', 0); + IRegion match= fPairMatcher.match(fDocument, idx + 1); + assertNotNull(match); + int otherIdx= fDocument.get().indexOf('>'); + assertEquals(otherIdx, match.getOffset() + match.getLength() - 1); + + match= fPairMatcher.match(fDocument, otherIdx + 1); + assertNotNull(match); + assertEquals(idx, match.getOffset()); + } + + public void testAngleBracketsAsPairs2() { + fDocument.set("ConstTemplate c<5>;"); + int idx= fDocument.get().indexOf('<', 0); + IRegion match= fPairMatcher.match(fDocument, idx + 1); + assertNotNull(match); + int otherIdx= fDocument.get().indexOf('>'); + assertEquals(otherIdx, match.getOffset() + match.getLength() - 1); + + match= fPairMatcher.match(fDocument, otherIdx + 1); + assertNotNull(match); + assertEquals(idx, match.getOffset()); + } + + public void testAngleBracketsAsPairsNested() { + fDocument.set("OtherTemplate nested<map<int,int>,Y>;"); + int idx= fDocument.get().indexOf('<', 0); + IRegion match= fPairMatcher.match(fDocument, idx + 1); + assertNotNull(match); + int otherIdx= fDocument.get().lastIndexOf('>'); + assertEquals(otherIdx, match.getOffset() + match.getLength() - 1); + + match= fPairMatcher.match(fDocument, otherIdx + 1); + assertNotNull(match); + assertEquals(idx, match.getOffset()); + + idx= fDocument.get().indexOf('<', idx+1); + match= fPairMatcher.match(fDocument, idx + 1); + assertNotNull(match); + otherIdx= fDocument.get().indexOf('>', idx + 1); + assertEquals(otherIdx, match.getOffset() + match.getLength() - 1); + } + + public void testAngleBracketsAsPairsMultiline() { + fDocument.set("OtherTemplate nested<\n\tmap<int,int>,Y\n>;"); + int idx= fDocument.get().indexOf('<', 0); + IRegion match= fPairMatcher.match(fDocument, idx + 1); + assertNotNull(match); + int otherIdx= fDocument.get().lastIndexOf('>'); + assertEquals(otherIdx, match.getOffset() + match.getLength() - 1); + + match= fPairMatcher.match(fDocument, otherIdx + 1); + assertNotNull(match); + assertEquals(idx, match.getOffset()); + + idx= fDocument.get().indexOf('<', idx+1); + match= fPairMatcher.match(fDocument, idx + 1); + assertNotNull(match); + otherIdx= fDocument.get().indexOf('>', idx + 1); + assertEquals(otherIdx, match.getOffset() + match.getLength() - 1); } } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/CHeuristicScanner.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/CHeuristicScanner.java index 8114fea5b2a..c7a7a22a1a5 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/CHeuristicScanner.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/CHeuristicScanner.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2007 IBM Corporation and others. + * Copyright (c) 2000, 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -634,13 +634,30 @@ public final class CHeuristicScanner implements Symbols { * @return the matching peer character position, or <code>NOT_FOUND</code> */ public int findClosingPeer(int start, final char openingPeer, final char closingPeer) { + return findClosingPeer(start, UNBOUND, openingPeer, closingPeer); + } + + /** + * Returns the position of the closing peer character (forward search). Any scopes introduced by opening peers + * are skipped. All peers accounted for must reside in the default partition. + * + * <p>Note that <code>start</code> must not point to the opening peer, but to the first + * character being searched.</p> + * + * @param start the start position + * @param bound the bound + * @param openingPeer the opening peer character (e.g. '{') + * @param closingPeer the closing peer character (e.g. '}') + * @return the matching peer character position, or <code>NOT_FOUND</code> + */ + public int findClosingPeer(int start, int bound, final char openingPeer, final char closingPeer) { Assert.isLegal(start >= 0); try { int depth= 1; start -= 1; while (true) { - start= scanForward(start + 1, UNBOUND, new CharacterMatch(new char[] {openingPeer, closingPeer})); + start= scanForward(start + 1, bound, new CharacterMatch(new char[] {openingPeer, closingPeer})); if (start == NOT_FOUND) return NOT_FOUND; @@ -726,7 +743,7 @@ public final class CHeuristicScanner implements Symbols { return null; int begin= findOpeningPeer(offset - 1, CHeuristicScanner.UNBOUND, LBRACE, RBRACE); - int end= findClosingPeer(offset, LBRACE, RBRACE); + int end= findClosingPeer(offset, UNBOUND, LBRACE, RBRACE); if (begin == NOT_FOUND || end == NOT_FOUND) return null; return new Region(begin, end + 1 - begin); diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/CPairMatcher.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/CPairMatcher.java index 4444f1d69b1..82732ab9a1b 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/CPairMatcher.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/text/CPairMatcher.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2007 IBM Corporation and others. + * Copyright (c) 2000, 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -12,17 +12,210 @@ *******************************************************************************/ package org.eclipse.cdt.internal.ui.text; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.IRegion; +import org.eclipse.jface.text.Region; +import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.text.source.DefaultCharacterPairMatcher; +import org.eclipse.jface.text.source.ICharacterPairMatcher; import org.eclipse.cdt.ui.text.ICPartitions; /** - * Helper class for match pairs of characters. + * Helper class to match pairs of characters. */ public class CPairMatcher extends DefaultCharacterPairMatcher { + private static final int ANGLE_BRACKETS_SEARCH_BOUND = 200; + + private boolean fMatchAngularBrackets= true; + private int fAnchor= -1; + public CPairMatcher(char[] pairs) { super(pairs, ICPartitions.C_PARTITIONING); } + /* @see ICharacterPairMatcher#match(IDocument, int) */ + public IRegion match(IDocument document, int offset) { + try { + return performMatch(document, offset); + } catch (BadLocationException ble) { + return null; + } + } + + /* + * @see org.eclipse.jface.text.source.DefaultCharacterPairMatcher#getAnchor() + */ + public int getAnchor() { + if (fAnchor < 0) { + return super.getAnchor(); + } + return fAnchor; + } + + /* + * Performs the actual work of matching for #match(IDocument, int). + */ + private IRegion performMatch(IDocument document, int offset) throws BadLocationException { + if (offset < 0 || document == null) return null; + final char prevChar= document.getChar(Math.max(offset - 1, 0)); + if ((prevChar == '<' || prevChar == '>') && !fMatchAngularBrackets) + return null; + final IRegion region; + if (prevChar == '<') { + region= findClosingAngleBracket(document, offset - 1); + fAnchor= ICharacterPairMatcher.LEFT; + } else if (prevChar == '>') { + region= findOpeningAngleBracket(document, offset - 1); + fAnchor= ICharacterPairMatcher.RIGHT; + } else { + region= super.match(document, offset); + fAnchor= -1; + } + if (region != null) { + if (prevChar == '>') { + final int peer= region.getOffset(); + if (isLessThanOperator(document, peer)) + return null; + } else if (prevChar == '<') { + final int peer= region.getOffset() + region.getLength() - 1; + if (isGreaterThanOperator(document, peer)) + return null; + } + } + return region; + } + + /** + * Returns the region enclosing the matching angle brackets. + * + * @param document a document + * @param offset an offset within the document pointing after the closing angle bracket + * @return the matching region or {@link NullPointerException} if no match could be found + * @throws BadLocationException + */ + private IRegion findOpeningAngleBracket(IDocument document, int offset) throws BadLocationException { + if (offset < 0) return null; + final String contentType= TextUtilities.getContentType(document, ICPartitions.C_PARTITIONING, offset, false); + CHeuristicScanner scanner= new CHeuristicScanner(document, ICPartitions.C_PARTITIONING, contentType); + if (isTemplateParameterCloseBracket(offset, document, scanner)) { + int pos= scanner.findOpeningPeer(offset - 1, Math.max(0, offset - ANGLE_BRACKETS_SEARCH_BOUND), '<', '>'); + if (pos != CHeuristicScanner.NOT_FOUND) { + return new Region(pos, offset - pos); + } + } + return null; + } + + /** + * Returns the region enclosing the matching angle brackets. + * + * @param document a document + * @param offset an offset within the document pointing after the opening angle bracket + * @return the matching region or {@link NullPointerException} if no match could be found + * @throws BadLocationException + */ + private IRegion findClosingAngleBracket(IDocument document, int offset) throws BadLocationException { + if (offset < 0) return null; + final String contentType= TextUtilities.getContentType(document, ICPartitions.C_PARTITIONING, offset, false); + CHeuristicScanner scanner= new CHeuristicScanner(document, ICPartitions.C_PARTITIONING, contentType); + if (isTemplateParameterOpenBracket(offset, document, scanner)) { + int pos= scanner.findClosingPeer(offset + 1, Math.min(document.getLength(), offset + ANGLE_BRACKETS_SEARCH_BOUND), '<', '>'); + if (pos != CHeuristicScanner.NOT_FOUND) { + return new Region(offset, pos - offset + 1); + } + } + return null; + } + + /** + * Returns true if the character at the specified offset is a + * less-than sign, rather than an template parameter list open + * angle bracket. + * + * @param document a document + * @param offset an offset within the document + * @return true if the character at the specified offset is not + * a template parameter start bracket + * @throws BadLocationException + */ + private boolean isLessThanOperator(IDocument document, int offset) throws BadLocationException { + if (offset < 0) return false; + final String contentType= TextUtilities.getContentType(document, ICPartitions.C_PARTITIONING, offset, false); + CHeuristicScanner scanner= new CHeuristicScanner(document, ICPartitions.C_PARTITIONING, contentType); + return !isTemplateParameterOpenBracket(offset, document, scanner); + } + + /** + * Returns true if the character at the specified offset is a + * greater-than sign, rather than an template parameter list close + * angle bracket. + * + * @param document a document + * @param offset an offset within the document + * @return true if the character at the specified offset is not + * a template parameter end bracket + * @throws BadLocationException + */ + private boolean isGreaterThanOperator(IDocument document, int offset) throws BadLocationException { + if (offset < 0) return false; + final String contentType= TextUtilities.getContentType(document, ICPartitions.C_PARTITIONING, offset, false); + CHeuristicScanner scanner= new CHeuristicScanner(document, ICPartitions.C_PARTITIONING, contentType); + return !isTemplateParameterCloseBracket(offset, document, scanner); + } + + /** + * Checks if the angular bracket at <code>offset</code> is a template + * parameter bracket. + * + * @param offset the offset of the opening bracket + * @param document the document + * @param scanner a heuristic scanner on <code>document</code> + * @return <code>true</code> if the bracket is part of a template parameter, + * <code>false</code> otherwise + */ + private boolean isTemplateParameterOpenBracket(int offset, IDocument document, CHeuristicScanner scanner) { + int prevToken= scanner.previousToken(offset - 1, Math.max(0, offset - ANGLE_BRACKETS_SEARCH_BOUND)); + if (prevToken == Symbols.TokenIDENT) { + return true; + } + + return false; + } + + /** + * Checks if the angular bracket at <code>offset</code> is a template + * parameter bracket. + * + * @param offset the offset of the closing bracket + * @param document the document + * @param scanner a heuristic scanner on <code>document</code> + * @return <code>true</code> if the bracket is part of a template parameter, + * <code>false</code> otherwise + */ + private boolean isTemplateParameterCloseBracket(int offset, IDocument document, CHeuristicScanner scanner) { + if (offset >= document.getLength() - 1) + return true; + int thisToken= scanner.previousToken(offset, Math.max(0, offset - ANGLE_BRACKETS_SEARCH_BOUND)); + if (thisToken != Symbols.TokenGREATERTHAN) + return false; + int prevToken= scanner.previousToken(scanner.getPosition(), Math.max(0, offset - ANGLE_BRACKETS_SEARCH_BOUND)); + if (prevToken == Symbols.TokenGREATERTHAN) + return true; + int nextToken= scanner.nextToken(offset + 1, Math.min(document.getLength(), offset + ANGLE_BRACKETS_SEARCH_BOUND)); + + switch (nextToken) { + case Symbols.TokenGREATERTHAN: + case Symbols.TokenCOMMA: + case Symbols.TokenSEMICOLON: + case Symbols.TokenCLASS: + case Symbols.TokenSTRUCT: + case Symbols.TokenUNION: + return true; + } + return false; + } + } |