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;
This commit is contained in:
Denis.Zhdanov
2013-02-26 13:55:48 +04:00
parent d8274020f0
commit de7131e6a8
3 changed files with 183 additions and 134 deletions
@@ -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])
@@ -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<IndentGuideDescriptor> 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<IndentGuideDescriptor> descriptors = new ArrayList<IndentGuideDescriptor>();
@@ -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
* <p/>
* Example:
* <pre>
* 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<Language, TokenSet> 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:
* <pre>
* void test() {
* if (true) {
* int i;
* // int j;
* }
* }
* </pre>
* 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;
}
}
}
@@ -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 <code>[startOffset; endOffset)</code> interval such that a char from the given buffer is
* not contained at the given 'chars' string.
* <p/>
* 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 <code>[startOffset; endOffset)</code> which points to a symbol at the given buffer such
* as that that symbol is not contained at the given 'chars';
* <code>endOffset</code> 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) {