diff options
Diffstat (limited to 'platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java')
-rw-r--r-- | platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java | 263 |
1 files changed, 194 insertions, 69 deletions
diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java index ac0bc8608368..44a56e7df517 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java @@ -175,10 +175,10 @@ public class LineStatusTracker { LOG.assertTrue(!myReleased, "Already released"); int first = - range.getOffset1() >= myDocument.getLineCount() ? myDocument.getTextLength() : myDocument.getLineStartOffset(range.getOffset1()); + range.getOffset1() >= getLineCount(myDocument) ? myDocument.getTextLength() : myDocument.getLineStartOffset(range.getOffset1()); int second = - range.getOffset2() >= myDocument.getLineCount() ? myDocument.getTextLength() : myDocument.getLineStartOffset(range.getOffset2()); + range.getOffset2() >= getLineCount(myDocument) ? myDocument.getTextLength() : myDocument.getLineStartOffset(range.getOffset2()); final RangeHighlighter highlighter = DocumentMarkupModel.forDocument(myDocument, myProject, true) .addRangeHighlighter(first, second, HighlighterLayer.FIRST - 1, null, HighlighterTargetArea.LINES_IN_RANGE); @@ -259,6 +259,7 @@ public class LineStatusTracker { if (range.getHighlighter() != null) { range.getHighlighter().dispose(); } + range.invalidate(); } myRanges.clear(); } @@ -316,10 +317,10 @@ public class LineStatusTracker { if (myBulkUpdate || myAnathemaThrown || BaseLoadState.LOADED != myBaseLoaded) return; try { myFirstChangedLine = myDocument.getLineNumber(e.getOffset()); - myLastChangedLine = myDocument.getLineNumber(e.getOffset() + e.getOldLength()); - myChangedLines = myLastChangedLine - myFirstChangedLine; + myLastChangedLine = e.getOldLength() == 0 ? myFirstChangedLine : myDocument.getLineNumber(e.getOffset() + e.getOldLength() - 1); if (StringUtil.endsWithChar(e.getOldFragment(), '\n')) myLastChangedLine++; - myTotalLines = e.getDocument().getLineCount(); + myChangedLines = myLastChangedLine - myFirstChangedLine; + myTotalLines = getLineCount(e.getDocument()); } catch (ProcessCanceledException ignore) { } @@ -334,9 +335,14 @@ public class LineStatusTracker { if (myReleased) return; if (myBulkUpdate || myAnathemaThrown || BaseLoadState.LOADED != myBaseLoaded) return; try { - int currentChangedLines = myDocument.getLineNumber(e.getOffset() + e.getNewLength()) - myDocument.getLineNumber(e.getOffset()); + int currentFirstChangedLine = myFirstChangedLine; + int currentLastChangedLine = + e.getNewLength() == 0 ? currentFirstChangedLine : myDocument.getLineNumber(e.getOffset() + e.getNewLength() - 1); + if (StringUtil.endsWithChar(e.getNewFragment(), '\n')) currentLastChangedLine++; + int currentChangedLines = currentLastChangedLine - currentFirstChangedLine; + int upToDateTotalLine = getLineCount(myUpToDateDocument); + int linesShift = currentChangedLines - myChangedLines; - int upToDateTotalLine = myUpToDateDocument.getLineCount(); List<Range> rangesBeforeChange = new ArrayList<Range>(); List<Range> rangesAfterChange = new ArrayList<Range>(); @@ -355,13 +361,14 @@ public class LineStatusTracker { myLastChangedLine = lastChangedRange.getOffset2() - 1; } - int currentFirstLine = myFirstChangedLine; - int currentLastLine = myLastChangedLine + linesShift; + currentFirstChangedLine = myFirstChangedLine; + currentLastChangedLine = myLastChangedLine + linesShift; int upToDateFirstLine = getUpToDateLine1(lastRangeBefore, myFirstChangedLine); int upToDateLastLine = getUpToDateLine2(firstRangeAfter, myLastChangedLine, myTotalLines, upToDateTotalLine); - List<Range> newChangedRanges = getNewChangedRanges(currentFirstLine, currentLastLine, upToDateFirstLine, upToDateLastLine); + List<Range> newChangedRanges = + getNewChangedRanges(currentFirstChangedLine, currentLastChangedLine, upToDateFirstLine, upToDateLastLine); shiftRanges(rangesAfterChange, linesShift); @@ -426,6 +433,7 @@ public class LineStatusTracker { range.getHighlighter().dispose(); } range.setHighlighter(null); + range.invalidate(); } for (Range range : newRangesInChange) { range.setHighlighter(createHighlighter(range)); @@ -531,98 +539,207 @@ public class LineStatusTracker { } } - public void rollbackChanges(final Range range) { + public void rollbackChanges(@NotNull Range range) { myApplication.assertWriteAccessAllowed(); synchronized (myLock) { - TextRange currentTextRange = getCurrentTextRangeWithMagic(range); + if (!range.isValid()) { + LOG.warn("Rollback of invalid range"); + return; + } + + if (range.getType() == Range.MODIFIED) { + TextRange currentTextRange = getCurrentTextRange(range); + int offset1 = currentTextRange.getStartOffset(); + int offset2 = currentTextRange.getEndOffset(); - int offset1 = currentTextRange.getStartOffset(); - int offset2 = Math.min(currentTextRange.getEndOffset() + 1, myDocument.getTextLength()); - if (range.getType() == Range.INSERTED) { - myDocument.replaceString(offset1, offset2, ""); + CharSequence upToDateContent = getUpToDateContent(range); + myDocument.replaceString(offset1, offset2, upToDateContent); + } + else if (range.getType() == Range.INSERTED) { + TextRange currentTextRange = getCurrentTextRange(range); + int offset1 = currentTextRange.getStartOffset(); + int offset2 = currentTextRange.getEndOffset(); + + if (offset1 > 0) { + offset1--; + } + else if (offset2 < myDocument.getTextLength()) { + offset2++; + } + myDocument.deleteString(offset1, offset2); } else if (range.getType() == Range.DELETED) { - String upToDateContent = getUpToDateContentWithMagic(range); - myDocument.insertString(offset1, upToDateContent); + CharSequence content = getUpToDateContent(range); + if (range.getOffset2() == getLineCount(myDocument)) { + myDocument.insertString(myDocument.getTextLength(), "\n" + content); + } + else { + myDocument.insertString(myDocument.getLineStartOffset(range.getOffset2()), content + "\n"); + } } else { - String upToDateContent = getUpToDateContentWithMagic(range); - myDocument.replaceString(offset1, offset2, upToDateContent); + throw new IllegalArgumentException("Unknown range type: " + range.getType()); } } } - public String getUpToDateContentWithMagic(Range range) { - synchronized (myLock) { - TextRange textRange = getUpToDateRangeWithMagic(range); - final int startOffset = textRange.getStartOffset(); - final int endOffset = Math.min(textRange.getEndOffset() + 1, myUpToDateDocument.getTextLength()); - return myUpToDateDocument.getCharsSequence().subSequence(startOffset, endOffset).toString(); - } - } + public void rollbackChanges(@NotNull SegmentTree lines) { + myApplication.assertWriteAccessAllowed(); - public String getUpToDateContent(Range range) { synchronized (myLock) { - TextRange textRange = getUpToDateRange(range); - final int startOffset = textRange.getStartOffset(); - final int endOffset = Math.min(textRange.getEndOffset() + 1, myUpToDateDocument.getTextLength()); - return myUpToDateDocument.getCharsSequence().subSequence(startOffset, endOffset).toString(); + List<Range> affectedRanges = new ArrayList<Range>(); + + boolean wasEnd = false; + boolean simple = true; + for (Range range : myRanges) { + if (!range.isValid()) { + LOG.warn("Rollback of invalid range"); + return; + } + + boolean check; + if (range.getOffset1() == range.getOffset2()) { + check = lines.check(range.getOffset1()); + } + else { + check = lines.check(range.getOffset1(), range.getOffset2()); + } + if (check) { + if (wasEnd) simple = false; + affectedRanges.add(range); + } + else { + if (!affectedRanges.isEmpty()) wasEnd = true; + } + } + + if (simple) { + rollbackChangesSimple(affectedRanges); + } + else { + rollbackChangesComplex(affectedRanges); + } } } - Project getProject() { - return myProject; + private void rollbackChangesSimple(@NotNull List<Range> ranges) { + if (ranges.isEmpty()) return; + + Range first = ranges.get(0); + Range last = ranges.get(ranges.size() - 1); + + byte type = first == last ? first.getType() : Range.MODIFIED; + final Range merged = new Range(first.getOffset1(), last.getOffset2(), first.getUOffset1(), last.getUOffset2(), type); + + // We don't expect complex Insertion/Deletion operation - they shouldn't exist + assert type != Range.MODIFIED || (first.getOffset1() != last.getOffset2() && first.getUOffset1() != last.getUOffset2()); + + rollbackChanges(merged); } - @NotNull - TextRange getCurrentTextRangeWithMagic(@NotNull Range range) { - return getRangeWithMagic(range.getType(), range.getOffset1(), range.getOffset2(), Range.DELETED, myDocument); + private void rollbackChangesComplex(@NotNull List<Range> ranges) { + // We can't relay on assumption, that revert of a single change will not affect any other. + // This, among the others, is because of 'magic' ranges for revert, that will affect nearby lines implicitly. + // So it's dangerous to apply ranges ony-by-one and we have to create single atomic modification. + // Usage of Bulk mode will lead to full rebuild of tracker, and therefore will be slow.. + + if (ranges.isEmpty()) return; + if (ranges.size() == 1) { + rollbackChanges(ranges.get(0)); + return; + } + + Range first = ranges.get(0); + Range last = ranges.get(ranges.size() - 1); + + // We don't expect complex Insertion/Deletion operation - they shouldn't exist. + assert first != last && first.getOffset1() != last.getOffset2() && first.getUOffset1() != last.getUOffset2(); + + final int start = getCurrentTextRange(first).getStartOffset(); + final int end = getCurrentTextRange(last).getEndOffset(); + + StringBuilder builder = new StringBuilder(); + + int lastOffset = start; + for (Range range : ranges) { + TextRange textRange = getCurrentTextRange(range); + + builder.append(myDocument.getText(new TextRange(lastOffset, textRange.getStartOffset()))); + lastOffset = textRange.getEndOffset(); + + if (range.getType() == Range.MODIFIED) { + builder.append(getUpToDateContent(range)); + } + else if (range.getType() == Range.INSERTED) { + if (builder.length() > 0) { + builder.deleteCharAt(builder.length() - 1); + } + else { + lastOffset++; + } + } + else if (range.getType() == Range.DELETED) { + CharSequence content = getUpToDateContent(range); + if (range.getOffset2() == getLineCount(myDocument)) { + builder.append('\n').append(content); + } + else { + builder.append(content).append('\n'); + } + } + else { + throw new IllegalArgumentException("Unknown range type: " + range.getType()); + } + } + builder.append(myDocument.getText(new TextRange(lastOffset, end))); + + final String s = builder.toString(); + + myDocument.replaceString(start, end, s); } - @NotNull - TextRange getUpToDateRangeWithMagic(@NotNull Range range) { - return getRangeWithMagic(range.getType(), range.getUOffset1(), range.getUOffset2(), Range.INSERTED, myUpToDateDocument); + public CharSequence getUpToDateContent(@NotNull Range range) { + synchronized (myLock) { + TextRange textRange = getUpToDateRange(range); + final int startOffset = textRange.getStartOffset(); + final int endOffset = textRange.getEndOffset(); + return myUpToDateDocument.getCharsSequence().subSequence(startOffset, endOffset); + } } @NotNull TextRange getCurrentTextRange(@NotNull Range range) { - return getRange(range.getType(), range.getOffset1(), range.getOffset2(), Range.DELETED, myDocument); - } + synchronized (myLock) { + if (!range.isValid()) { + LOG.warn("Current TextRange of invalid range"); + } - @NotNull - TextRange getUpToDateRange(@NotNull Range range) { - return getRange(range.getType(), range.getUOffset1(), range.getUOffset2(), Range.INSERTED, myUpToDateDocument); + return getRange(range.getOffset1(), range.getOffset2(), myDocument); + } } @NotNull - private static TextRange getRangeWithMagic(byte rangeType, int offset1, int offset2, byte emptyRangeCondition, Document document) { - if (rangeType == emptyRangeCondition) { - int lineStartOffset; - if (offset1 == 0) { - lineStartOffset = 0; - } - else { - lineStartOffset = document.getLineEndOffset(offset1 - 1); - } - //if (lineStartOffset > 0) lineStartOffset--; - return new TextRange(lineStartOffset, lineStartOffset); - } - else { - int startOffset = document.getLineStartOffset(offset1); - int endOffset = document.getLineEndOffset(offset2 - 1); - if (startOffset > 0) { - --startOffset; - --endOffset; + TextRange getUpToDateRange(@NotNull Range range) { + synchronized (myLock) { + if (!range.isValid()) { + LOG.warn("UpToDate TextRange of invalid range"); } - return new TextRange(startOffset, endOffset); + + return getRange(range.getUOffset1(), range.getUOffset2(), myUpToDateDocument); } } + /** + * Return affected range, without non-internal '\n' + * so if last line is not empty, the last symbol will be not '\n' + * <p/> + * So we consider '\n' not as a part of line, but a separator between lines + */ @NotNull - private static TextRange getRange(byte rangeType, int offset1, int offset2, byte emptyRangeCondition, Document document) { - if (rangeType == emptyRangeCondition) { - int lineStartOffset = offset1 < document.getLineCount() ? document.getLineStartOffset(offset1) : document.getTextLength(); + private static TextRange getRange(int offset1, int offset2, @NotNull Document document) { + if (offset1 == offset2) { + int lineStartOffset = offset1 < getLineCount(document) ? document.getLineStartOffset(offset1) : document.getTextLength(); return new TextRange(lineStartOffset, lineStartOffset); } else { @@ -643,7 +760,11 @@ public class LineStatusTracker { } } - public static enum BaseLoadState { + Project getProject() { + return myProject; + } + + public enum BaseLoadState { LOADING, FAILED, LOADED @@ -705,4 +826,8 @@ public class LineStatusTracker { myLabel.setText("Can not highlight changed lines. File is too big and there are too many changes."); } } + + private static int getLineCount(@NotNull Document document) { + return Math.max(document.getLineCount(), 1); + } } |