diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties
index 1722b46d655a..1c05af27102c 100644
--- a/platform/platform-resources-en/src/messages/ActionsBundle.properties
+++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties
@@ -1014,6 +1014,8 @@ action.Vcs.ShowDiffAction.text=Show Changes
action.Vcs.ShowDiffAction.description=Show changes
action.Vcs.RollbackChanges.text=Rollback Changes
action.Vcs.RollbackChanges.description=Rollback changes
+action.RollbackLineStatusChanges.text=Rollback
+action.RollbackLineStatusChanges.description=Rollback selected local changes
action.Vcs.EditSourceAction.text=Edit Source
action.Vcs.EditSourceAction.description=Edit source
action.Vcs.ExcludeAction.text=Exclude from Commit
diff --git a/platform/platform-resources/src/idea/VcsActions.xml b/platform/platform-resources/src/idea/VcsActions.xml
index 935bcc7b4df8..dc15f19282bf 100644
--- a/platform/platform-resources/src/idea/VcsActions.xml
+++ b/platform/platform-resources/src/idea/VcsActions.xml
@@ -265,6 +265,9 @@
+
+
+
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 1bf0f6f7390c..c6f0dac39d0f 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
@@ -578,6 +578,116 @@ public class LineStatusTracker {
}
}
+ public void rollbackChanges(@NotNull SegmentTree lines) {
+ myApplication.assertWriteAccessAllowed();
+
+ synchronized (myLock) {
+ List affectedRanges = new ArrayList();
+
+ boolean wasEnd = false;
+ boolean simple = true;
+ for (Range range : myRanges) {
+ 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);
+ }
+ }
+ }
+
+ private void rollbackChangesSimple(@NotNull List 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);
+ }
+
+ private void rollbackChangesComplex(@NotNull List 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);
+ }
+
public CharSequence getUpToDateContent(@NotNull Range range) {
synchronized (myLock) {
TextRange textRange = getUpToDateRange(range);
@@ -589,17 +699,23 @@ public class LineStatusTracker {
@NotNull
TextRange getCurrentTextRange(@NotNull Range range) {
- return getRange(range.getType(), range.getOffset1(), range.getOffset2(), Range.DELETED, myDocument);
+ return getRange(range.getOffset1(), range.getOffset2(), myDocument);
}
@NotNull
TextRange getUpToDateRange(@NotNull Range range) {
- return getRange(range.getType(), range.getUOffset1(), range.getUOffset2(), Range.INSERTED, myUpToDateDocument);
+ 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'
+ *
+ * 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) {
+ 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);
}
diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusAction.java
new file mode 100644
index 000000000000..59697f1222dd
--- /dev/null
+++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusAction.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2000-2010 JetBrains s.r.o.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.intellij.openapi.vcs.ex;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonDataKeys;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.editor.Caret;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsBundle;
+import com.intellij.openapi.vcs.impl.LineStatusTrackerManager;
+import com.intellij.openapi.vfs.ReadonlyStatusHandler;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+public class RollbackLineStatusAction extends DumbAwareAction {
+ public RollbackLineStatusAction() {
+ super("Rollback", "Rollback selected changes", AllIcons.Actions.Reset);
+ }
+
+ @Override
+ public void update(AnActionEvent e) {
+ Project project = e.getProject();
+ if (project == null) {
+ e.getPresentation().setEnabled(false);
+ return;
+ }
+ Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
+ if (editor == null) {
+ e.getPresentation().setEnabled(false);
+ return;
+ }
+ LineStatusTracker tracker = LineStatusTrackerManager.getInstance(project).getLineStatusTracker(editor.getDocument());
+ if (tracker == null) {
+ e.getPresentation().setEnabled(false);
+ return;
+ }
+ e.getPresentation().setEnabled(true);
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ Project project = e.getProject();
+ Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
+ LineStatusTracker tracker = LineStatusTrackerManager.getInstance(project).getLineStatusTracker(editor.getDocument());
+ if (tracker == null) return;
+
+ rollback(tracker, editor, null);
+ }
+
+ protected static void rollback(@NotNull LineStatusTracker tracker, @Nullable Editor editor, @Nullable Range range) {
+ if (range != null) {
+ doRollback(tracker, range);
+ return;
+ }
+
+ if (editor == null) return;
+ Document document = editor.getDocument();
+ int totalLines = getLineCount(document);
+
+ SegmentTree lines = new SegmentTree(totalLines + 1);
+
+ List carets = editor.getCaretModel().getAllCarets();
+ for (Caret caret : carets) {
+ if (caret.hasSelection()) {
+ int line1 = editor.offsetToLogicalPosition(caret.getSelectionStart()).line;
+ int line2 = editor.offsetToLogicalPosition(caret.getSelectionEnd()).line;
+ lines.mark(line1, line2 + 1);
+ if (caret.getSelectionEnd() == document.getTextLength()) lines.mark(totalLines);
+ }
+ else {
+ lines.mark(caret.getLogicalPosition().line);
+ if (caret.getOffset() == document.getTextLength()) lines.mark(totalLines);
+ }
+ }
+
+ doRollback(tracker, lines);
+ }
+
+ private static void doRollback(@NotNull final LineStatusTracker tracker, @NotNull final Range range) {
+ execute(tracker, new Runnable() {
+ @Override
+ public void run() {
+ tracker.rollbackChanges(range);
+ }
+ });
+ }
+
+ private static void doRollback(@NotNull final LineStatusTracker tracker, @NotNull final SegmentTree lines) {
+ execute(tracker, new Runnable() {
+ @Override
+ public void run() {
+ tracker.rollbackChanges(lines);
+ }
+ });
+ }
+
+ private static void execute(@NotNull final LineStatusTracker tracker, @NotNull final Runnable task) {
+ // TODO: is there possible data races?
+ CommandProcessor.getInstance().executeCommand(tracker.getProject(), new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ if (!tracker.getDocument().isWritable()) {
+ final ReadonlyStatusHandler.OperationStatus operationStatus = ReadonlyStatusHandler
+ .getInstance(tracker.getProject()).ensureFilesWritable(tracker.getVirtualFile());
+ if (operationStatus.hasReadonlyFiles()) return;
+ }
+ task.run();
+ }
+ });
+ }
+ }, VcsBundle.message("command.name.rollback.change"), null);
+ }
+
+ private static int getLineCount(@NotNull Document document) {
+ return Math.max(document.getLineCount(), 1);
+ }
+}
diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusRangeAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusRangeAction.java
index ed5846d20ade..f1795b5efc3c 100644
--- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusRangeAction.java
+++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/RollbackLineStatusRangeAction.java
@@ -12,41 +12,28 @@
*/
package com.intellij.openapi.vcs.ex;
-import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.vcs.VcsBundle;
-import com.intellij.openapi.vfs.ReadonlyStatusHandler;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
-/**
-* @author irengrig
-*/
-public class RollbackLineStatusRangeAction extends BaseLineStatusRangeAction {
- public RollbackLineStatusRangeAction(final LineStatusTracker lineStatusTracker, final Range range, final Editor editor) {
- super(VcsBundle.message("action.name.rollback"), AllIcons.Actions.Reset, lineStatusTracker, range);
+public class RollbackLineStatusRangeAction extends RollbackLineStatusAction {
+ @NotNull private final LineStatusTracker myTracker;
+ @Nullable private final Editor myEditor;
+ @NotNull private final Range myRange;
+
+ public RollbackLineStatusRangeAction(@NotNull LineStatusTracker tracker, @NotNull Range range, @Nullable Editor editor) {
+ myTracker = tracker;
+ myEditor = editor;
+ myRange = range;
}
- public boolean isEnabled() {
- return true;
+ @Override
+ public void update(AnActionEvent e) {
+ e.getPresentation().setEnabled(true);
}
public void actionPerformed(final AnActionEvent e) {
- CommandProcessor.getInstance().executeCommand(myLineStatusTracker.getProject(), new Runnable() {
- public void run() {
- ApplicationManager.getApplication().runWriteAction(new Runnable() {
- public void run() {
- if (!myLineStatusTracker.getDocument().isWritable()) {
- final ReadonlyStatusHandler.OperationStatus operationStatus = ReadonlyStatusHandler
- .getInstance(myLineStatusTracker.getProject()).ensureFilesWritable(myLineStatusTracker.getVirtualFile());
- if (operationStatus.hasReadonlyFiles()) return;
- }
- myLineStatusTracker.rollbackChanges(myRange);
- }
- });
- }
- }, VcsBundle.message("command.name.rollback.change"), null);
-
+ rollback(myTracker, myEditor, myRange);
}
}
diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/SegmentTree.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/SegmentTree.java
new file mode 100644
index 000000000000..68307129c13e
--- /dev/null
+++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/SegmentTree.java
@@ -0,0 +1,116 @@
+package com.intellij.openapi.vcs.ex;
+
+import org.jetbrains.annotations.Nullable;
+
+public class SegmentTree {
+ private final int myActualLength;
+ private final int myLength;
+
+ private final Node myRoot;
+
+ public SegmentTree(int length) {
+ myActualLength = length;
+ myLength = toUpperSquare(length);
+ myRoot = new Node();
+ }
+
+ public void mark(int pos) {
+ mark(pos, pos + 1);
+ }
+
+ public void mark(int start, int end) {
+ start = correct(0, myActualLength, start);
+ end = correct(0, myActualLength, end);
+
+ myRoot.mark(0, myLength, start, end);
+ }
+
+ public boolean check(int pos) {
+ return check(pos, pos + 1);
+ }
+
+ public boolean check(int start, int end) {
+ start = correct(0, myActualLength, start);
+ end = correct(0, myActualLength, end);
+
+ return myRoot.check(0, myLength, start, end);
+ }
+
+ private static int toUpperSquare(int value) {
+ int high = Integer.highestOneBit(value);
+ return high == value ? value : high * 2;
+ }
+
+ private static class Node {
+ @Nullable
+ public Node myLeft;
+
+ @Nullable
+ public Node myRight;
+
+ public boolean myMarked;
+
+ public boolean mark(int thisStart, int thisEnd, int start, int end) {
+ if (myLeft == null && myMarked) return true;
+
+ if (start == end) return false;
+
+ myMarked = true;
+
+ if (thisStart == start && thisEnd == end) {
+ myLeft = null;
+ myRight = null;
+ return true;
+ }
+
+ if (myLeft == null) {
+ myLeft = new Node();
+ myRight = new Node();
+ }
+
+ int mid = thisStart + (thisEnd - thisStart) / 2;
+ int start1 = correct(thisStart, mid, start);
+ int end1 = correct(thisStart, mid, end);
+ int start2 = correct(mid, thisEnd, start);
+ int end2 = correct(mid, thisEnd, end);
+
+ boolean marked = true;
+ marked &= myLeft.mark(thisStart, mid, start1, end1);
+ marked &= myRight.mark(mid, thisEnd, start2, end2);
+
+ if (marked) {
+ myLeft = null;
+ myRight = null;
+ }
+
+ return marked;
+ }
+
+ public boolean check(int thisStart, int thisEnd, int start, int end) {
+ if (start == end) return false;
+
+ if (thisStart == start && thisEnd == end) {
+ return myMarked;
+ }
+
+ if (myLeft == null) return myMarked;
+
+ int mid = thisStart + (thisEnd - thisStart) / 2;
+ int start1 = correct(thisStart, mid, start);
+ int end1 = correct(thisStart, mid, end);
+ int start2 = correct(mid, thisEnd, start);
+ int end2 = correct(mid, thisEnd, end);
+
+ if (myLeft.check(thisStart, mid, start1, end1)) return true;
+ if (myRight.check(mid, thisEnd, start2, end2)) return true;
+
+ return false;
+ }
+ }
+
+ private static int correct(int start, int end, int value) {
+ if (value < start) return start;
+ if (value > end) return end;
+ return value;
+ }
+}