IDEA-138949 diff: allow to apply changes in oneside diff

This commit is contained in:
Aleksey Pivovarov
2015-04-27 14:10:14 +03:00
parent da6dc8354f
commit 68e6265e84
6 changed files with 259 additions and 20 deletions
@@ -76,6 +76,11 @@ public enum Side {
return isLeft() ? left : right;
}
public boolean select(@NotNull boolean[] array) {
assert array.length == 2;
return array[myIndex];
}
public int select(@NotNull int[] array) {
assert array.length == 2;
return array[myIndex];
@@ -18,12 +18,22 @@ package com.intellij.diff.tools.fragmented;
import com.intellij.diff.fragments.DiffFragment;
import com.intellij.diff.fragments.LineFragment;
import com.intellij.diff.util.DiffDrawUtil;
import com.intellij.diff.util.DiffUtil;
import com.intellij.diff.util.Side;
import com.intellij.diff.util.TextDiffType;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.SeparatorPlacement;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.CalledWithWriteLock;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
@@ -42,6 +52,7 @@ public class OnesideDiffChange {
@NotNull private final LineFragment myLineFragment;
@NotNull private final List<RangeHighlighter> myHighlighters = new ArrayList<RangeHighlighter>();
@NotNull private final List<MyGutterOperation> myOperations = new ArrayList<MyGutterOperation>();
public OnesideDiffChange(@NotNull OnesideDiffViewer viewer, @NotNull ChangedBlock block, boolean innerFragments) {
myViewer = viewer;
@@ -63,6 +74,11 @@ public class OnesideDiffChange {
highlighter.dispose();
}
myHighlighters.clear();
for (MyGutterOperation operation : myOperations) {
operation.dispose();
}
myOperations.clear();
}
public void installHighlighter(boolean innerFragments) {
@@ -74,6 +90,17 @@ public class OnesideDiffChange {
else {
doInstallHighlighterSimple();
}
doInstallActionHighlighters();
}
private void doInstallActionHighlighters() {
if (myViewer.getDocument(Side.LEFT) == null || myViewer.getDocument(Side.RIGHT) == null) return;
boolean leftEditable = myViewer.isEditable(Side.LEFT, false);
boolean rightEditable = myViewer.isEditable(Side.RIGHT, false);
if (rightEditable) myOperations.add(createOperation(Side.LEFT, false));
if (leftEditable) myOperations.add(createOperation(Side.RIGHT, rightEditable));
}
private void doInstallHighlighterSimple() {
@@ -160,4 +187,139 @@ public class OnesideDiffChange {
private boolean hasDeletion() {
return myStartOffset1 != myEndOffset1;
}
@CalledWithWriteLock
public void replaceChange(@NotNull final Side sourceSide) {
Side affectedSide = sourceSide.other();
if (!myViewer.isEditable(affectedSide, true)) return;
final Document document1 = myViewer.getDocument(Side.LEFT);
final Document document2 = myViewer.getDocument(Side.RIGHT);
assert document1 != null && document2 != null;
DiffUtil.applyModification(affectedSide.select(document1, document2),
affectedSide.getStartLine(myLineFragment), affectedSide.getEndLine(myLineFragment),
sourceSide.select(document1, document2),
sourceSide.getStartLine(myLineFragment), sourceSide.getEndLine(myLineFragment));
destroyHighlighter();
}
//
// Gutter
//
public void updateGutterActions() {
for (MyGutterOperation operation : myOperations) {
operation.update();
}
}
@NotNull
private MyGutterOperation createOperation(@NotNull Side side, boolean secondAction) {
int line = secondAction ? Math.min(myLine1 + 1, myLine2 - 1) : myLine1;
int offset = myEditor.getDocument().getLineStartOffset(line);
RangeHighlighter highlighter = myEditor.getMarkupModel().addRangeHighlighter(offset, offset,
HighlighterLayer.ADDITIONAL_SYNTAX,
null,
HighlighterTargetArea.LINES_IN_RANGE);
return new MyGutterOperation(side, highlighter);
}
private class MyGutterOperation {
@NotNull private final Side mySide;
@NotNull private final RangeHighlighter myHighlighter;
private MyGutterOperation(@NotNull Side sourceSide, @NotNull RangeHighlighter highlighter) {
mySide = sourceSide;
myHighlighter = highlighter;
update();
}
public void dispose() {
myHighlighter.dispose();
}
public void update() {
if (myHighlighter.isValid()) myHighlighter.setGutterIconRenderer(createRenderer());
}
@Nullable
public GutterIconRenderer createRenderer() {
if (!myViewer.isEditable(mySide.other(), true)) return null;
boolean bothEditable = myViewer.isEditable(mySide, true);
if (bothEditable) {
if (mySide.isLeft()) {
return createIconRenderer(mySide, "Apply Before", AllIcons.Diff.ArrowRight);
}
else {
return createIconRenderer(mySide, "Apply After", AllIcons.Diff.Arrow);
}
}
else {
if (mySide.isLeft()) {
return createIconRenderer(mySide, "Revert", AllIcons.Diff.Remove);
}
else {
return createIconRenderer(mySide, "Apply", AllIcons.Diff.Arrow);
}
}
}
}
@Nullable
private GutterIconRenderer createIconRenderer(@NotNull final Side sourceSide,
@NotNull final String tooltipText,
@NotNull final Icon icon) {
return new GutterIconRenderer() {
@NotNull
@Override
public Icon getIcon() {
return icon;
}
public boolean isNavigateAction() {
return true;
}
@Nullable
@Override
public AnAction getClickAction() {
return new DumbAwareAction() {
@Override
public void actionPerformed(AnActionEvent e) {
final Project project = e.getProject();
final Document document = myViewer.getDocument(sourceSide.other());
assert document != null;
DiffUtil.executeWriteCommand(document, project, "Replace change", new Runnable() {
@Override
public void run() {
replaceChange(sourceSide);
}
});
}
};
}
@Override
public boolean equals(Object obj) {
return obj == this;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
@Nullable
@Override
public String getTooltipText() {
return tooltipText;
}
};
}
}
@@ -93,12 +93,17 @@ public class OnesideDiffViewer extends TextDiffViewerBase {
@Nullable private ChangedBlockData myChangedBlockData;
private final boolean[] myForceReadOnlyFlags;
private boolean myReadOnlyLockSet = false;
public OnesideDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
super(context, (ContentDiffRequest)request);
myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable();
myStatusPanel = new MyStatusPanel();
myForceReadOnlyFlags = checkForceReadOnly();
List<DiffContent> contents = myRequest.getContents();
myActualContent1 = contents.get(0) instanceof DocumentContent ? ((DocumentContent)contents.get(0)) : null;
@@ -155,7 +160,7 @@ public class OnesideDiffViewer extends TextDiffViewerBase {
group.add(new MyIgnorePolicySettingAction());
group.add(new MyHighlightPolicySettingAction());
group.add(new MyToggleExpandByDefaultAction());
group.add(new ReadOnlyLockAction());
group.add(new MyReadOnlyLockAction());
group.add(myEditorSettingsAction);
return group;
@@ -557,6 +562,19 @@ public class OnesideDiffViewer extends TextDiffViewerBase {
return myStatusPanel;
}
public boolean isEditable(@NotNull Side side, boolean respectReadOnlyLock) {
if (myReadOnlyLockSet && respectReadOnlyLock) return false;
if (side.select(myForceReadOnlyFlags)) return false;
Document document = getDocument(side);
return document != null && DiffUtil.canMakeWritable(document);
}
@Nullable
public Document getDocument(@NotNull Side side) {
DocumentContent content = side.select(myActualContent1, myActualContent2);
return content != null ? content.getDocument() : null;
}
//
// Misc
//
@@ -731,6 +749,28 @@ public class OnesideDiffViewer extends TextDiffViewerBase {
}
}
private class MyReadOnlyLockAction extends ReadOnlyLockAction {
public MyReadOnlyLockAction() {
init();
}
@Override
protected void doApply(boolean readOnly) {
myReadOnlyLockSet = readOnly;
if (myChangedBlockData != null) {
for (OnesideDiffChange onesideDiffChange : myChangedBlockData.getDiffChanges()) {
onesideDiffChange.updateGutterActions();
}
}
}
@Override
protected boolean canEdit() {
return myActualContent1 != null && !myForceReadOnlyFlags[0] && DiffUtil.canMakeWritable(myActualContent1.getDocument()) ||
myActualContent2 != null && !myForceReadOnlyFlags[1] && DiffUtil.canMakeWritable(myActualContent2.getDocument());
}
}
//
// Scroll from annotate
//
@@ -590,10 +590,10 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
}
}
private class MyReadOnlyLockAction extends ReadOnlyLockAction {
private class MyReadOnlyLockAction extends EditorReadOnlyLockAction {
@Override
public void setSelected(AnActionEvent e, boolean state) {
super.setSelected(e, state);
protected void doApply(boolean readOnly) {
super.doApply(readOnly);
for (SimpleDiffChange change : myDiffChanges) {
change.updateGutterActions(true);
}
@@ -107,7 +107,7 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
//group.add(new MyHighlightPolicySettingAction()); // TODO
group.add(new MyToggleExpandByDefaultAction());
group.add(new ToggleAutoScrollAction());
group.add(new ReadOnlyLockAction());
group.add(new EditorReadOnlyLockAction());
group.add(myEditorSettingsAction);
group.add(Separator.getInstance());
@@ -377,18 +377,13 @@ public abstract class TextDiffViewerBase extends ListenerDiffViewerBase {
protected abstract void expandAll(boolean expand);
}
protected class ReadOnlyLockAction extends ToggleAction implements DumbAware {
private final List<? extends EditorEx> myEditableEditors;
protected abstract class ReadOnlyLockAction extends ToggleAction implements DumbAware {
public ReadOnlyLockAction() {
super("Disable editing", null, AllIcons.Nodes.Padlock);
setEnabledInModalContext(true);
myEditableEditors = ContainerUtil.filter(getEditors(), new Condition<EditorEx>() {
@Override
public boolean value(EditorEx editor) {
return !editor.isViewer();
}
});
}
protected void init() {
if (isVisible()) { // apply default state
setSelected(null, isSelected(null));
}
@@ -412,14 +407,51 @@ public abstract class TextDiffViewerBase extends ListenerDiffViewerBase {
@Override
public void setSelected(AnActionEvent e, boolean state) {
myContext.putUserData(READ_ONLY_LOCK_KEY, state);
for (EditorEx editor : myEditableEditors) {
editor.setViewer(state);
}
doApply(state);
}
private boolean isVisible() {
return !myEditableEditors.isEmpty() && myContext.getUserData(DiffUserDataKeysEx.SHOW_READ_ONLY_LOCK) == Boolean.TRUE;
return myContext.getUserData(DiffUserDataKeysEx.SHOW_READ_ONLY_LOCK) == Boolean.TRUE && canEdit();
}
protected abstract void doApply(boolean readOnly);
protected abstract boolean canEdit();
}
protected class EditorReadOnlyLockAction extends ReadOnlyLockAction {
private final List<? extends EditorEx> myEditableEditors;
public EditorReadOnlyLockAction() {
this(getEditableEditors(getEditors()));
}
public EditorReadOnlyLockAction(@NotNull List<? extends EditorEx> editableEditors) {
myEditableEditors = editableEditors;
init();
}
@Override
protected void doApply(boolean readOnly) {
for (EditorEx editor : myEditableEditors) {
editor.setViewer(readOnly);
}
}
@Override
protected boolean canEdit() {
return !myEditableEditors.isEmpty();
}
}
@NotNull
protected static List<? extends EditorEx> getEditableEditors(@NotNull List<? extends EditorEx> editors) {
return ContainerUtil.filter(editors, new Condition<EditorEx>() {
@Override
public boolean value(EditorEx editor) {
return !editor.isViewer();
}
});
}
private final class MyEditorMouseListener extends EditorPopupHandler {