IDEA-165197 NullPointerException in IndentsPass may break plugins

This commit is contained in:
Alexey Kudravtsev
2016-12-14 12:51:43 +03:00
parent bf741343ce
commit eb330fdea5
4 changed files with 44 additions and 57 deletions
@@ -81,15 +81,7 @@ public interface RangeMarker extends UserDataHolder, Segment {
*/
void setGreedyToRight(boolean greedy);
Comparator<RangeMarker> BY_START_OFFSET = new Comparator<RangeMarker>() {
@Override
public int compare(RangeMarker r1, RangeMarker r2) {
int result = r1.getStartOffset() - r2.getStartOffset();
if (result == 0) result = r1.getEndOffset() - r2.getEndOffset();
return result;
}
};
Comparator<? super RangeMarker> BY_START_OFFSET = BY_START_OFFSET_THEN_END_OFFSET;
boolean isGreedyToRight();
boolean isGreedyToLeft();
@@ -40,6 +40,7 @@ import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
@@ -56,17 +57,13 @@ import java.util.List;
public class IndentsPass extends TextEditorHighlightingPass implements DumbAware {
private static final Key<List<RangeHighlighter>> INDENT_HIGHLIGHTERS_IN_EDITOR_KEY = Key.create("INDENT_HIGHLIGHTERS_IN_EDITOR_KEY");
private static final Key<Long> LAST_TIME_INDENTS_BUILT = Key.create("LAST_TIME_INDENTS_BUILT");
private static final Key<Long> LAST_TIME_INDENTS_BUILT = Key.create("LAST_TIME_INDENTS_BUILT");
private final EditorEx myEditor;
private final PsiFile myFile;
public static final Comparator<TextRange> RANGE_COMPARATOR = (o1, o2) -> {
if (o1.getStartOffset() == o2.getStartOffset()) {
return o1.getEndOffset() - o2.getEndOffset();
}
return o1.getStartOffset() - o2.getStartOffset();
};
private volatile List<TextRange> myRanges = Collections.emptyList();
private volatile List<IndentGuideDescriptor> myDescriptors = Collections.emptyList();
private static final CustomHighlighterRenderer RENDERER = (editor, highlighter, g) -> {
int startOffset = highlighter.getStartOffset();
@@ -92,7 +89,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware
final VisualPosition startPosition = editor.offsetToVisualPosition(off);
int indentColumn = startPosition.column;
// It's considered that indent guide can cross not only white space but comments, javadocs etc. Hence, there is a possible
// It's considered that indent guide can cross not only white space but comments, javadoc etc. Hence, there is a possible
// case that the first indent guide line is, say, single-line comment where comment symbols ('//') are located at the first
// visual column. We need to calculate correct indent guide column then.
int lineShift = 1;
@@ -139,7 +136,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware
}
final EditorColorsScheme scheme = editor.getColorsScheme();
g.setColor(selected ? scheme.getColor(EditorColors.SELECTED_INDENT_GUIDE_COLOR) : scheme.getColor(EditorColors.INDENT_GUIDE_COLOR));
g.setColor(scheme.getColor(selected ? EditorColors.SELECTED_INDENT_GUIDE_COLOR : EditorColors.INDENT_GUIDE_COLOR));
// There is a possible case that indent line intersects soft wrap-introduced text. Example:
// this is a long line <soft-wrap>
@@ -191,10 +188,8 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware
}
}
};
private volatile List<TextRange> myRanges;
private volatile List<IndentGuideDescriptor> myDescriptors;
public IndentsPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
IndentsPass(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
super(project, editor.getDocument(), false);
myEditor = (EditorEx)editor;
myFile = file;
@@ -216,7 +211,7 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware
ranges.add(new TextRange(myDocument.getLineStartOffset(descriptor.startLine), endOffset));
}
Collections.sort(ranges, RANGE_COMPARATOR);
Collections.sort(ranges, Segment.BY_START_OFFSET_THEN_END_OFFSET);
myRanges = ranges;
}
@@ -350,11 +345,9 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware
}
private class IndentsCalculator {
@NotNull public final Map<Language, TokenSet> myComments = ContainerUtilRt.newHashMap();
@NotNull final int[] lineIndents; // negative value means the line is empty (or contains a comment) and indent
// (denoted by absolute value) was deduced from enclosing non-empty lines
@NotNull final Map<Language, TokenSet> myComments = ContainerUtilRt.newHashMap();
@NotNull final int[] lineIndents; // negative value means the line is empty (or contains a comment) and indent
// (denoted by absolute value) was deduced from enclosing non-empty lines
@NotNull final CharSequence myChars;
IndentsCalculator() {
@@ -391,12 +384,9 @@ public class IndentsPass extends TextEditorHighlightingPass implements DumbAware
}
offset++;
}
if (offset == lineEnd || isComment(offset)) { // treating commented lines in the same way as empty lines
lineIndents[line] = -1; // Blank line marker
}
else {
lineIndents[line] = column;
}
// treating commented lines in the same way as empty lines
// Blank line marker
lineIndents[line] = offset == lineEnd || isComment(offset) ? -1 : column;
}
int topIndent = 0;
@@ -15,6 +15,8 @@
*/
package com.intellij.openapi.util;
import java.util.Comparator;
/**
* User: cdr
*/
@@ -22,4 +24,13 @@ public interface Segment {
Segment[] EMPTY_ARRAY = new Segment[0];
int getStartOffset();
int getEndOffset();
Comparator<Segment> BY_START_OFFSET_THEN_END_OFFSET = new Comparator<Segment>() {
@Override
public int compare(Segment r1, Segment r2) {
int result = r1.getStartOffset() - r2.getStartOffset();
if (result == 0) result = r1.getEndOffset() - r2.getEndOffset();
return result;
}
};
}
@@ -17,26 +17,24 @@ package org.jetbrains.idea.devkit.actions;
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInsight.daemon.impl.IndentsPass;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.command.CommandProcessorEx;
import com.intellij.openapi.command.UndoConfirmationPolicy;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Segment;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -57,7 +55,7 @@ public class ToggleHighlightingMarkupAction extends AnAction {
PsiFile file = CommonDataKeys.PSI_FILE.getData(e.getDataContext());
if (editor == null || file == null) return;
final Project project = file.getProject();
CommandProcessorEx commandProcessor = (CommandProcessorEx)CommandProcessorEx.getInstance();
CommandProcessorEx commandProcessor = (CommandProcessorEx)CommandProcessor.getInstance();
Object commandToken = commandProcessor.startCommand(project, e.getPresentation().getText(), e.getPresentation().getText(), UndoConfirmationPolicy.DEFAULT);
try {
WriteAction.run(() -> {
@@ -101,7 +99,7 @@ public class ToggleHighlightingMarkupAction extends AnAction {
pos = Math.max(matcher.end(1), matcher.end(2));
}
while (matcher.find(pos));
Collections.sort(ranges, IndentsPass.RANGE_COMPARATOR);
Collections.sort(ranges, Segment.BY_START_OFFSET_THEN_END_OFFSET);
}
if (!ranges.isEmpty()) {
int pos = 0;
@@ -112,7 +110,7 @@ public class ToggleHighlightingMarkupAction extends AnAction {
sb.append(sequence, pos, sequence.length());
}
else {
final int[] offset = new int[] {0};
final int[] offset = {0};
final ArrayList<HighlightInfo> infos = new ArrayList<>();
DaemonCodeAnalyzerEx.processHighlights(
document, project, HighlightSeverity.WARNING, 0, sequence.length(),
@@ -120,11 +118,11 @@ public class ToggleHighlightingMarkupAction extends AnAction {
if (info.getSeverity() != HighlightSeverity.WARNING && info.getSeverity() != HighlightSeverity.ERROR) return true;
if (info.getStartOffset() >= endOffset) return false;
if (info.getEndOffset() > startOffset) {
offset[0] = appendInfo(info, sb, sequence, offset[0], infos, false);
offset[0] = appendInfo(info, sb, sequence, offset[0], infos);
}
return true;
});
offset[0] = appendInfo(null, sb, sequence, offset[0], infos, false);
offset[0] = appendInfo(null, sb, sequence, offset[0], infos);
sb.append(sequence.subSequence(offset[0], sequence.length()));
}
document.setText(sb);
@@ -134,27 +132,27 @@ public class ToggleHighlightingMarkupAction extends AnAction {
StringBuilder sb,
CharSequence sequence,
int offset,
ArrayList<HighlightInfo> infos, final boolean compact) {
ArrayList<HighlightInfo> infos) {
if (info == null || !infos.isEmpty() && getMaxEnd(infos) < info.getStartOffset()) {
if (infos.size() == 1) {
HighlightInfo cur = infos.remove(0);
sb.append(sequence.subSequence(offset, cur.getStartOffset()));
appendTag(sb, cur, true, compact);
appendTag(sb, cur, true);
sb.append(sequence.subSequence(cur.getStartOffset(), cur.getEndOffset()));
appendTag(sb, cur, false, compact);
appendTag(sb, cur, false);
offset = cur.getEndOffset();
}
else {
// process overlapped
LinkedList<HighlightInfo> stack = new LinkedList<>();
for (HighlightInfo cur : infos) {
offset = processStack(stack, sb, sequence, offset, cur.getStartOffset(), compact);
offset = processStack(stack, sb, sequence, offset, cur.getStartOffset());
sb.append(sequence.subSequence(offset, cur.getStartOffset()));
offset = cur.getStartOffset();
appendTag(sb, cur, true, compact);
appendTag(sb, cur, true);
stack.addLast(cur);
}
offset = processStack(stack, sb, sequence, offset, sequence.length(), compact);
offset = processStack(stack, sb, sequence, offset, sequence.length());
infos.clear();
}
}
@@ -185,24 +183,20 @@ public class ToggleHighlightingMarkupAction extends AnAction {
StringBuilder sb,
CharSequence sequence,
int offset,
final int endOffset,
final boolean compact) {
final int endOffset) {
if (stack.isEmpty()) return offset;
for (HighlightInfo cur = stack.peekLast(); cur != null && cur.getEndOffset() <= endOffset; cur = stack.peekLast()) {
stack.removeLast();
if (offset <= cur.getEndOffset()) {
sb.append(sequence.subSequence(offset, cur.getEndOffset()));
}
else {
//System.out.println("Incorrect overlapping infos: " + offset + " > " + cur.getEndOffset());
}
offset = cur.getEndOffset();
appendTag(sb, cur, false, compact);
appendTag(sb, cur, false);
}
return offset;
}
private static void appendTag(StringBuilder sb, HighlightInfo cur, boolean opening, final boolean compact) {
private static void appendTag(StringBuilder sb, HighlightInfo cur, boolean opening) {
sb.append("<");
if (!opening) sb.append("/");
if (cur.isAfterEndOfLine()) {
@@ -211,7 +205,7 @@ public class ToggleHighlightingMarkupAction extends AnAction {
else {
sb.append(cur.getSeverity() == HighlightSeverity.WARNING ? "warning" : "error");
}
if (opening && !compact) {
if (opening) {
sb.append(" descr=\"").append(cur.getDescription()).append("\"");
}