From de7131e6a81ca66067ef3ab211fbbd521b13977a Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Tue, 26 Feb 2013 13:55:24 +0400 Subject: [PATCH] CR-IC-41 Vertical line block match disapear view line in java code (IDEA-99572) 1. Limit 'shiftForward()' by line end offset; 2. Minor refactoring; 3. Don't show indent guide for commented lines which would remove indent on un-commenting; --- .../daemon/indentGuide/IndentGuideTest.groovy | 12 +- .../codeInsight/daemon/impl/IndentsPass.java | 274 ++++++++++-------- .../com/intellij/util/text/CharArrayUtil.java | 31 +- 3 files changed, 183 insertions(+), 134 deletions(-) diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/indentGuide/IndentGuideTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/indentGuide/IndentGuideTest.groovy index 3fe81493e751..88af209beebf 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/indentGuide/IndentGuideTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/indentGuide/IndentGuideTest.groovy @@ -43,8 +43,8 @@ class Test { | //test(); | if (true) { | | int i = 1; - | | | -//| | | comment; + | | +//| | int k; | | int j = 1; | } } @@ -141,8 +141,12 @@ class Test { myFixture.configureByText("${getTestName(false)}.java", testData.documentText) CodeInsightTestFixtureImpl.instantiateAndRun(myFixture.file, myFixture.editor, ArrayUtilRt.EMPTY_INT_ARRAY, false) IndentsModelImpl model = myFixture.editor.indentsModel as IndentsModelImpl - assertEquals("expected to find ${testData.guides.size()} indent guide but got ${model.indents.size()}", - testData.guides.size(), model.indents.size()) + assertEquals( + "expected to find ${testData.guides.size()} indent guides (" + + "${testData.guides.collect { startLine, endLine, level -> "$level ($startLine-$endLine)"}}) " + + "but got ${model.indents.size()} (${model.indents})", + testData.guides.size(), model.indents.size() + ) testData.guides.each { def descriptor = model.getDescriptor(it[0], it[1]) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java index d70e7223649d..66421a2e4176 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/IndentsPass.java @@ -26,7 +26,6 @@ import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.ex.EditorEx; -import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.editor.highlighter.HighlighterIterator; import com.intellij.openapi.editor.markup.CustomHighlighterRenderer; import com.intellij.openapi.editor.markup.HighlighterTargetArea; @@ -44,8 +43,10 @@ import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import com.intellij.util.DocumentUtil; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.ContainerUtilRt; import com.intellij.util.containers.IntStack; import com.intellij.util.text.CharArrayUtil; +import gnu.trove.TIntIntHashMap; import org.jetbrains.annotations.NotNull; import java.awt.*; @@ -93,8 +94,9 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware final CharSequence chars = doc.getCharsSequence(); do { - int pos = doc.getLineStartOffset(startLine); - off = CharArrayUtil.shiftForward(chars, pos, " \t"); + int start = doc.getLineStartOffset(startLine); + int end = doc.getLineEndOffset(startLine); + off = CharArrayUtil.shiftForward(chars, start, end, " \t"); startLine--; } while (startLine > 1 && off < doc.getTextLength() && chars.charAt(off) == '\n'); @@ -296,7 +298,10 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware private List buildDescriptors() { if (!myEditor.getSettings().isIndentGuidesShown()) return Collections.emptyList(); - int[] lineIndents = calcIndents(); + IndentsCalculator calculator = new IndentsCalculator(); + calculator.calculate(); + int[] lineIndents = calculator.lineIndents; + TIntIntHashMap effectiveCommentColumns = calculator.indentAfterUncomment; List descriptors = new ArrayList(); @@ -316,7 +321,18 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware final int level = indents.pop(); int startLine = lines.pop(); if (level > 0) { - descriptors.add(createDescriptor(level, startLine, line, chars)); + boolean addDescriptor = effectiveCommentColumns.contains(startLine); // Indent started at comment + if (!addDescriptor) { + for (int i = startLine; i < line; i++) { + if (level != lineIndents[i] && level != effectiveCommentColumns.get(i)) { + addDescriptor = true; + break; + } + } + } + if (addDescriptor) { + descriptors.add(createDescriptor(level, startLine, line, chars)); + } } } @@ -332,8 +348,8 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware while (!indents.empty()) { ProgressManager.checkCanceled(); final int level = indents.pop(); + int startLine = lines.pop(); if (level > 0) { - int startLine = lines.pop(); descriptors.add(createDescriptor(level, startLine, myDocument.getLineCount(), chars)); } } @@ -351,109 +367,8 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware return true; } int startOffset = document.getLineStartOffset(line); - return CharArrayUtil.shiftForward(chars, startOffset, " \t") >= myDocument.getLineEndOffset(line); - } - - /** - * Calculates line indents for the {@link #myDocument target document}. - * - * @return line indents array which uses lines as indices - */ - @NotNull - private int[] calcIndents() { - Context context = new Context(myDocument, myFile.getLanguage(), myEditor.getHighlighter()); - int[] lineIndents = context.lineIndents; - final FileType fileType = myFile.getFileType(); - int prevLineIndent = -1; - - for (int line = 0; line < lineIndents.length; line++) { - ProgressManager.checkCanceled(); - int lineStart = myDocument.getLineStartOffset(line); - int lineEnd = myDocument.getLineEndOffset(line); - final int nonWhitespaceOffset = CharArrayUtil.shiftForward(context.chars, lineStart, " \t"); - final int column; - if (nonWhitespaceOffset == lineEnd) { - lineIndents[line] = -1; // Blank line marker - } - else if (nonWhitespaceOffset > lineEnd) { - lineIndents[line] = calcIndent(context, line, lineStart, lineEnd, 0); - prevLineIndent = lineIndents[line]; - } - else { - column = myEditor.calcColumnNumber(nonWhitespaceOffset, line); - if (prevLineIndent > 0 && prevLineIndent > column) { - lineIndents[line] = calcIndent(context, line, nonWhitespaceOffset, lineEnd, column); - } - else { - lineIndents[line] = column; - } - prevLineIndent = lineIndents[line]; - } - } - - int topIndent = 0; - for (int line = 0; line < lineIndents.length; line++) { - ProgressManager.checkCanceled(); - if (lineIndents[line] >= 0) { - topIndent = lineIndents[line]; - } - else { - int startLine = line; - while (line < lineIndents.length && lineIndents[line] < 0) { - //noinspection AssignmentToForLoopParameter - line++; - } - - int bottomIndent = line < lineIndents.length ? lineIndents[line] : topIndent; - - int indent = Math.min(topIndent, bottomIndent); - if (bottomIndent < topIndent) { - int nonWhitespaceOffset = CharArrayUtil.shiftForward(context.chars, myDocument.getLineStartOffset(line), " \t"); - HighlighterIterator iterator = context.highlighter.createIterator(nonWhitespaceOffset); - if (BraceMatchingUtil.isRBraceToken(iterator, context.chars, fileType)) { - indent = topIndent; - } - } - - for (int blankLine = startLine; blankLine < line; blankLine++) { - assert lineIndents[blankLine] == -1; - lineIndents[blankLine] = Math.min(topIndent, indent); - } - - //noinspection AssignmentToForLoopParameter - line--; // will be incremented back at the end of the loop; - } - } - - return lineIndents; - } - - /** - * Tries to calculate given line's indent column assuming that there might be a comment at the given indent offset - * (see {@link #parseComments(IElementType)}). - * - * @param context current calculation context - * @param line target line - * @param indentOffset start indent offset to use for the given line - * @param lineEndOffset given line's end offset - * @param fallbackColumn column to return if it's not possible to apply comment-specific indent calculation rules - * @return given line's indent column to use - */ - private int calcIndent(@NotNull Context context, int line, int indentOffset, int lineEndOffset, int fallbackColumn) { - final HighlighterIterator it = context.highlighter.createIterator(indentOffset); - if (context.comments.contains(it.getTokenType()) && indentOffset == it.getStart()) { - String prefix = COMMENT_PREFIXES.get(it.getTokenType()); - if (prefix == null) { - prefix = parseComments(it.getTokenType()); - } - if (!NO_COMMENT_INFO_MARKER.equals(prefix)) { - final int indentInsideCommentOffset = CharArrayUtil.shiftForward(context.chars, indentOffset + prefix.length(), " \t"); - if (indentInsideCommentOffset < lineEndOffset) { - return myEditor.calcColumnNumber(indentInsideCommentOffset, line); - } - } - } - return fallbackColumn; + int endOffset = document.getLineEndOffset(line); + return CharArrayUtil.shiftForward(chars, startOffset, endOffset, " \t") >= myDocument.getLineEndOffset(line); } /** @@ -461,7 +376,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware *

* Example: *

-   *   it (true) {
+   *   if (true) {
    *     int i1;
    * //    int i2;
    *     int i3;
@@ -478,7 +393,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware
    *                     {@link #NO_COMMENT_INFO_MARKER} otherwise
    */
   @NotNull
-  private static String parseComments(@NotNull IElementType commentType) {
+  private static String getCommentPrefix(@NotNull IElementType commentType) {
     Commenter c = LanguageCommenters.INSTANCE.forLanguage(commentType.getLanguage());
     if (!(c instanceof CodeDocumentationAwareCommenter)) {
       COMMENT_PREFIXES.put(commentType, NO_COMMENT_INFO_MARKER);
@@ -521,21 +436,132 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware
     return answer != 0 ? answer : r.getEndOffset() - h.getEndOffset();
   }
 
-  /**
-   * 'Parameter object' for {@link #calcIndent(Context, int, int, int, int)}.
-   */
-  private static class Context {
+  private class IndentsCalculator {
 
-    @NotNull public final int[]             lineIndents;
-    @NotNull public final CharSequence      chars;
-    @NotNull public final TokenSet          comments;
-    @NotNull public final EditorHighlighter highlighter;
+    @NotNull public final Map myComments = ContainerUtilRt.newHashMap();
 
-    Context(@NotNull Document document, @NotNull Language language, @NotNull EditorHighlighter highlighter) {
-      lineIndents = new int[document.getLineCount()];
-      chars = document.getCharsSequence();
-      comments = LanguageParserDefinitions.INSTANCE.forLanguage(language).getCommentTokens();
-      this.highlighter = highlighter;
+    /**
+     * We need to treat specially commented lines. Consider a situation like below:
+     * 
+     *   void test() {
+     *     if (true) {
+     *       int i;
+     *  //     int j;
+     *     }
+     *   }
+     * 
+ * We don't want to show indent guide after 'int i;' line because un-commented line below ('int j;') would have the same indent + * level. That's why we remember 'indents after un-comment' at this collection. + */ + @NotNull public final TIntIntHashMap/* line -> indent column after un-comment */ indentAfterUncomment = new TIntIntHashMap(); + + @NotNull public final int[] lineIndents; + @NotNull public final CharSequence myChars; + + IndentsCalculator() { + assert myDocument != null; + lineIndents = new int[myDocument.getLineCount()]; + myChars = myDocument.getCharsSequence(); + } + + /** + * Calculates line indents for the {@link #myDocument target document}. + */ + void calculate() { + final FileType fileType = myFile.getFileType(); + int prevLineIndent = -1; + + for (int line = 0; line < lineIndents.length; line++) { + ProgressManager.checkCanceled(); + int lineStart = myDocument.getLineStartOffset(line); + int lineEnd = myDocument.getLineEndOffset(line); + final int nonWhitespaceOffset = CharArrayUtil.shiftForward(myChars, lineStart, lineEnd, " \t"); + final int column; + if (nonWhitespaceOffset == lineEnd) { + lineIndents[line] = -1; // Blank line marker + } + else { + column = myEditor.calcColumnNumber(nonWhitespaceOffset, line); + if (prevLineIndent > 0 && prevLineIndent > column) { + lineIndents[line] = calcIndent(line, nonWhitespaceOffset, lineEnd, column); + } + else { + lineIndents[line] = column; + } + prevLineIndent = lineIndents[line]; + } + } + + int topIndent = 0; + for (int line = 0; line < lineIndents.length; line++) { + ProgressManager.checkCanceled(); + if (lineIndents[line] >= 0) { + topIndent = lineIndents[line]; + } + else { + int startLine = line; + while (line < lineIndents.length && lineIndents[line] < 0) { + //noinspection AssignmentToForLoopParameter + line++; + } + + int bottomIndent = line < lineIndents.length ? lineIndents[line] : topIndent; + + int indent = Math.min(topIndent, bottomIndent); + if (bottomIndent < topIndent) { + int lineStart = myDocument.getLineStartOffset(line); + int lineEnd = myDocument.getLineEndOffset(line); + int nonWhitespaceOffset = CharArrayUtil.shiftForward(myChars, lineStart, lineEnd, " \t"); + HighlighterIterator iterator = myEditor.getHighlighter().createIterator(nonWhitespaceOffset); + if (BraceMatchingUtil.isRBraceToken(iterator, myChars, fileType)) { + indent = topIndent; + } + } + + for (int blankLine = startLine; blankLine < line; blankLine++) { + assert lineIndents[blankLine] == -1; + lineIndents[blankLine] = Math.min(topIndent, indent); + } + + //noinspection AssignmentToForLoopParameter + line--; // will be incremented back at the end of the loop; + } + } + } + + /** + * Tries to calculate given line's indent column assuming that there might be a comment at the given indent offset + * (see {@link #getCommentPrefix(IElementType)}). + * + * @param line target line + * @param indentOffset start indent offset to use for the given line + * @param lineEndOffset given line's end offset + * @param fallbackColumn column to return if it's not possible to apply comment-specific indent calculation rules + * @return given line's indent column to use + */ + private int calcIndent(int line, int indentOffset, int lineEndOffset, int fallbackColumn) { + final HighlighterIterator it = myEditor.getHighlighter().createIterator(indentOffset); + IElementType tokenType = it.getTokenType(); + Language language = tokenType.getLanguage(); + TokenSet comments = myComments.get(language); + if (comments == null) { + myComments.put(language, comments = LanguageParserDefinitions.INSTANCE.forLanguage(language).getCommentTokens()); + } + if (comments.contains(tokenType) && indentOffset == it.getStart()) { + String prefix = COMMENT_PREFIXES.get(tokenType); + if (prefix == null) { + prefix = getCommentPrefix(tokenType); + } + if (!NO_COMMENT_INFO_MARKER.equals(prefix)) { + final int indentInsideCommentOffset = CharArrayUtil.shiftForward(myChars, indentOffset + prefix.length(), lineEndOffset, " \t"); + if (indentInsideCommentOffset < lineEndOffset) { + int indent = myEditor.calcColumnNumber(indentInsideCommentOffset, line); + indentAfterUncomment.put(line, indent - prefix.length()); + return indent; + } + } + } + return fallbackColumn; } } } diff --git a/platform/util/src/com/intellij/util/text/CharArrayUtil.java b/platform/util/src/com/intellij/util/text/CharArrayUtil.java index 5e4dc45a4823..f62a1645d607 100644 --- a/platform/util/src/com/intellij/util/text/CharArrayUtil.java +++ b/platform/util/src/com/intellij/util/text/CharArrayUtil.java @@ -194,17 +194,36 @@ public class CharArrayUtil { } public static int shiftForward(@NotNull CharSequence buffer, int offset, @NotNull String chars) { - while (true) { - if (offset >= buffer.length()) break; + return shiftForward(buffer, offset, buffer.length(), chars); + } + + /** + * Tries to find an offset from the [startOffset; endOffset) interval such that a char from the given buffer is + * not contained at the given 'chars' string. + *

+ * Example: + * {@code buffer="abc", startOffset=0, endOffset = 3, chars="ab". Result: 2} + * + * @param buffer target buffer which symbols should be checked + * @param startOffset start offset to use within the given buffer (inclusive) + * @param endOffset end offset to use within the given buffer (exclusive) + * @param chars pass-through symbols + * @return offset from the [startOffset; endOffset) which points to a symbol at the given buffer such + * as that that symbol is not contained at the given 'chars'; + * endOffset otherwise + */ + public static int shiftForward(@NotNull CharSequence buffer, final int startOffset, final int endOffset, @NotNull String chars) { + for (int offset = startOffset, limit = Math.min(endOffset, buffer.length()); offset < limit; offset++) { char c = buffer.charAt(offset); - int i; + int i; for (i = 0; i < chars.length(); i++) { if (c == chars.charAt(i)) break; } - if (i == chars.length()) break; - offset++; + if (i >= chars.length()) { + return offset; + } } - return offset; + return endOffset; } public static int shiftForwardCarefully(@NotNull CharSequence buffer, int offset, @NotNull String chars) {