mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-110611 diff: Ability to revert lines in selection
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -265,6 +265,9 @@
|
||||
<add-to-group group-id="CloseEditorsGroup" anchor="before" relative-to-action="CloseAllUnpinnedEditors"/>
|
||||
</action>
|
||||
|
||||
<action id="RollbackLineStatusChanges" class="com.intellij.openapi.vcs.ex.RollbackLineStatusAction">
|
||||
</action>
|
||||
|
||||
<action id="WelcomeScreen.GetFromVcs" class="com.intellij.openapi.wm.impl.welcomeScreen.GetFromVcsAction"
|
||||
icon="AllIcons.General.GetProjectfromVCS">
|
||||
<add-to-group group-id="WelcomeScreen.QuickStart" anchor="first"/>
|
||||
|
||||
@@ -578,6 +578,116 @@ public class LineStatusTracker {
|
||||
}
|
||||
}
|
||||
|
||||
public void rollbackChanges(@NotNull SegmentTree lines) {
|
||||
myApplication.assertWriteAccessAllowed();
|
||||
|
||||
synchronized (myLock) {
|
||||
List<Range> affectedRanges = new ArrayList<Range>();
|
||||
|
||||
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<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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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'
|
||||
* <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) {
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<Caret> 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);
|
||||
}
|
||||
}
|
||||
+15
-28
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user