diff --git a/platform/diff-api/src/com/intellij/diff/util/Side.java b/platform/diff-api/src/com/intellij/diff/util/Side.java index 04bb08c2ee42..486d68d3d2b1 100644 --- a/platform/diff-api/src/com/intellij/diff/util/Side.java +++ b/platform/diff-api/src/com/intellij/diff/util/Side.java @@ -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]; diff --git a/platform/diff-impl/src/com/intellij/diff/tools/fragmented/OnesideDiffChange.java b/platform/diff-impl/src/com/intellij/diff/tools/fragmented/OnesideDiffChange.java index 1c57f071891f..063c8dfecdbf 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/fragmented/OnesideDiffChange.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/fragmented/OnesideDiffChange.java @@ -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 myHighlighters = new ArrayList(); + @NotNull private final List myOperations = new ArrayList(); 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; + } + }; + } } diff --git a/platform/diff-impl/src/com/intellij/diff/tools/fragmented/OnesideDiffViewer.java b/platform/diff-impl/src/com/intellij/diff/tools/fragmented/OnesideDiffViewer.java index 475525e58461..74bd891e03df 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/fragmented/OnesideDiffViewer.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/fragmented/OnesideDiffViewer.java @@ -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 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 // diff --git a/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java b/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java index 6b2ffd33713e..0999e124be64 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java @@ -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); } diff --git a/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleThreesideDiffViewer.java b/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleThreesideDiffViewer.java index d8a178cb7223..5fe13fabbd4d 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleThreesideDiffViewer.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleThreesideDiffViewer.java @@ -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()); diff --git a/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffViewerBase.java b/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffViewerBase.java index 28753c454b6c..693d9309825a 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffViewerBase.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/util/base/TextDiffViewerBase.java @@ -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 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() { - @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 myEditableEditors; + + public EditorReadOnlyLockAction() { + this(getEditableEditors(getEditors())); + } + + public EditorReadOnlyLockAction(@NotNull List 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 getEditableEditors(@NotNull List editors) { + return ContainerUtil.filter(editors, new Condition() { + @Override + public boolean value(EditorEx editor) { + return !editor.isViewer(); + } + }); } private final class MyEditorMouseListener extends EditorPopupHandler {