summaryrefslogtreecommitdiff
path: root/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java
diff options
context:
space:
mode:
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.java263
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);
+ }
}