From 107dcc9f6f87fe9c951562ddb8b39a0a014f447a Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Sat, 2 Jun 2012 16:49:39 +0400 Subject: [PATCH] [merge] Display applied changes; allow applying both sides of a conflict. OVERVIEW: Don't hide highlighting markers on "apply", as it is done on "ignore". Instead use a lighter highlighting: remove the background and paint a separator with a color of change type (red for conflict, green for insert, etc.). Show, what has been changed, i.e. highlight both original side and the applied change in the base/result side. For conflicting changes, on apply neither remove nor apply the second "variant" of the conflict. Instead propose to apply (or ignore) the second variant below the first one. This fixes IDEA-36811 "Add ability to merge in lines from both left and right in three way merge". DETAILS: * Define Change#onApplied handled differently: - for the SimpleChange update highlighting & remove apply/ignore actions & update the ChangeList - for the ConflictChange additionally break the connection between left-to-base and right-to-base changes via the MergeConflict by nullifying the non-applied change in one case and defining its own MergeConflict for this non-applied change. - Update MergeConflict and MergeList to be aware of nullable changes in semi-applied MergeConflict. * Make ChangeType and TextDiffType modifiable: each one can be applied. - TextDiffType returns different background for applied changes (no background actually). - ChangeType displays different line separator for applied changes. * Manage ChangeList#myAppliedChanges collection of the changes which has been applied. Pass both myChanges (not applied yet) and myAppliedChanges in getLineBlocks(), but make LineBlocks be aware of not-sorted Changes collection. * Make LineBlocks depend on TextDiffType rather than on TextDiffTypeEnum to get correct coloring attribute for applied changes. * Some javadocs, cleanups and code style fixes. KNOWN PROBLEMS: * Applied deletion is not easy to find, because it occupies zero place in both original and resulting side. * "Ignore" works differently from "apply" in that way that highlighting is completely removed for "ignore" (as it were): not sure if it should be fixed or that corresponds to the nature of "ignore" action name. * Trapezium-dividers are the same for applied changes as for non-applied. Will be changed. --- .../diff/impl/incrementalMerge/Change.java | 60 +++++++++- .../impl/incrementalMerge/ChangeCounter.java | 8 +- .../ChangeHighlighterHolder.java | 6 +- .../impl/incrementalMerge/ChangeList.java | 29 ++++- .../impl/incrementalMerge/ChangeType.java | 105 +++++++++++++----- .../impl/incrementalMerge/ConflictChange.java | 48 +++++++- .../impl/incrementalMerge/MergeConflict.java | 69 ++++++++++-- .../diff/impl/incrementalMerge/MergeList.java | 22 +++- .../impl/incrementalMerge/SimpleChange.java | 30 ++++- .../impl/incrementalMerge/ui/MergePanel2.java | 7 ++ .../diff/impl/splitter/DividerPolygon.java | 6 +- .../diff/impl/splitter/LineBlocks.java | 71 +++++------- .../openapi/diff/impl/util/TextDiffType.java | 43 +++++-- 13 files changed, 392 insertions(+), 112 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/Change.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/Change.java index 30dab2e70de0..55855a709167 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/Change.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/Change.java @@ -25,8 +25,10 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.ReadonlyStatusHandler; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Comparator; @@ -47,26 +49,64 @@ public abstract class Change { protected abstract void removeFromList(); + /** + * Called when a change has been applied. + */ + public abstract void onApplied(); + + /** + * Called when a change has been removed from the list. + */ public abstract void onRemovedFromList(); public abstract boolean isValid(); + /** + * Apply the change, i.e. change the "Merge result" document and update range markers, highlighting, gutters, etc. + * @param source The source side of the change, which is being applied. + */ private void apply(@NotNull FragmentSide original) { FragmentSide targetSide = original.otherSide(); RangeMarker originalRangeMarker = getRangeMarker(original); RangeMarker rangeMarker = getRangeMarker(targetSide); if (originalRangeMarker != null && rangeMarker != null) { - apply(getProject(), originalRangeMarker, rangeMarker); - if (isValid()) { - removeFromList(); + TextRange textRange = modifyDocument(getProject(), originalRangeMarker, rangeMarker); + if (textRange != null && isValid()) { + updateTargetRangeMarker(targetSide, textRange); } + onApplied(); } } - private static void apply(@NotNull Project project, @NotNull RangeMarker original, @NotNull RangeMarker target) { + /** + * Updates the target marker of a change after the change has been applied + * to allow highlighting of the document modification which has been performed. + * @param targetFragmentSide The side to be changed. + * @param updatedTextRange New text range to be applied to the side. + */ + protected final void updateTargetRangeMarker(@NotNull FragmentSide targetFragmentSide, @NotNull TextRange updatedTextRange) { + ChangeSide targetSide = getChangeSide(targetFragmentSide); + DiffRangeMarker originalRange = targetSide.getRange(); + DiffRangeMarker updatedRange = new DiffRangeMarker(originalRange.getDocument(), updatedTextRange, null); + changeSide(targetSide, updatedRange); + } + + /** + * Substitutes the specified side of this change to a new side which contains the given range. + * @param sideToChange The side to be changed. + * @param newRange New text range of the new side. + */ + protected abstract void changeSide(ChangeSide sideToChange, DiffRangeMarker newRange); + + /** + * Applies the text from the original marker to the target marker. + * @return the resulting TextRange from the target document, or null if the document if not writable. + */ + @Nullable + private static TextRange modifyDocument(@NotNull Project project, @NotNull RangeMarker original, @NotNull RangeMarker target) { Document document = target.getDocument(); - if (!ReadonlyStatusHandler.ensureDocumentWritable(project, document)) return; + if (!ReadonlyStatusHandler.ensureDocumentWritable(project, document)) { return null; } if (DocumentUtil.isEmpty(original)) { int offset = target.getStartOffset(); document.deleteString(offset, target.getEndOffset()); @@ -78,6 +118,7 @@ public abstract class Change { } else { document.replaceString(startOffset, target.getEndOffset(), text); } + return new TextRange(startOffset, startOffset + text.length()); } public void addMarkup(Editor[] editors) { @@ -152,11 +193,18 @@ public abstract class Change { protected static class SimpleChangeSide extends ChangeSide { private final FragmentSide mySide; private final DiffRangeMarker myRange; - private final ChangeHighlighterHolder myHighlighterHolder = new ChangeHighlighterHolder(); + private final ChangeHighlighterHolder myHighlighterHolder; public SimpleChangeSide(FragmentSide side, DiffRangeMarker rangeMarker) { mySide = side; myRange = rangeMarker; + myHighlighterHolder = new ChangeHighlighterHolder(); + } + + public SimpleChangeSide(ChangeSide originalSide, DiffRangeMarker newRange) { + mySide = ((SimpleChangeSide)originalSide).getFragmentSide(); + myRange = newRange; + myHighlighterHolder = originalSide.getHighlighterHolder(); } public FragmentSide getFragmentSide() { diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeCounter.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeCounter.java index 24ecfef705dc..c4e0dbefcee7 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeCounter.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeCounter.java @@ -35,6 +35,11 @@ public class ChangeCounter implements ChangeList.Listener { updateCounters(); } + @Override + public void onChangeApplied(ChangeList source) { + updateCounters(); + } + public void onChangeRemoved(ChangeList source) { updateCounters(); } @@ -60,8 +65,7 @@ public class ChangeCounter implements ChangeList.Listener { private void fireCountersChanged() { Listener[] listeners = myListeners.toArray(new Listener[myListeners.size()]); - for (int i = 0; i < listeners.length; i++) { - Listener listener = listeners[i]; + for (Listener listener : listeners) { listener.onCountersChanged(this); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeHighlighterHolder.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeHighlighterHolder.java index 137aac7cc6d6..3c445cc106c8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeHighlighterHolder.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeHighlighterHolder.java @@ -82,7 +82,7 @@ class ChangeHighlighterHolder { private void setHighlighter(ChangeSide changeSide, ChangeType type) { myMainHighlighter = type.addMarker(changeSide, this); - updateAction(); + updateActions(); } public Editor getEditor() { @@ -112,10 +112,10 @@ class ChangeHighlighterHolder { public void setActions(AnAction[] action) { myActions = action; - updateAction(); + updateActions(); } - private void updateAction() { + private void updateActions() { removeActionHighlighters(); if (myMainHighlighter != null && myActions != null && myActions.length > 0) { myActionHighlighters = new RangeHighlighter[myActions.length]; diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeList.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeList.java index ded9f26b3cc1..078bba811638 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeList.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeList.java @@ -33,12 +33,13 @@ import java.util.*; public class ChangeList { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.incrementalMerge.ChangeList"); - private static final Comparator CHANGE_ORDER = new SimpleChange.ChangeOrder(FragmentSide.SIDE1); + public static final Comparator CHANGE_ORDER = new SimpleChange.ChangeOrder(FragmentSide.SIDE1); private final Project myProject; private final Document[] myDocuments = new Document[2]; private final ArrayList myListeners = new ArrayList(); private ArrayList myChanges; + private ArrayList myAppliedChanges; public ChangeList(Document base, Document version, Project project) { myDocuments[0] = base; @@ -70,6 +71,7 @@ public class ChangeList { LOG.assertTrue(change.isValid()); } myChanges = new ArrayList(changes); + myAppliedChanges = new ArrayList(); } public Project getProject() { return myProject; } @@ -187,15 +189,28 @@ public class ChangeList { } public LineBlocks getLineBlocks() { - return LineBlocks.fromChanges(myChanges); + ArrayList changes = new ArrayList(myChanges); + changes.addAll(myAppliedChanges); + return LineBlocks.fromChanges(changes); } public void remove(Change change) { - LOG.assertTrue(myChanges.remove(change), change); + if (change.getType().isApplied()) { + LOG.assertTrue(myAppliedChanges.remove(change), change); + } + else { + LOG.assertTrue(myChanges.remove(change), change); + } change.onRemovedFromList(); fireOnChangeRemoved(); } + public void apply(Change change) { + LOG.assertTrue(myChanges.remove(change), change); + myAppliedChanges.add(change); + fireOnChangeApplied(); + } + private void fireOnChangeRemoved() { Listener[] listeners = myListeners.toArray(new Listener[myListeners.size()]); for (Listener listener : listeners) { @@ -203,7 +218,15 @@ public class ChangeList { } } + void fireOnChangeApplied() { + Listener[] listeners = myListeners.toArray(new Listener[myListeners.size()]); + for (Listener listener : listeners) { + listener.onChangeApplied(this); + } + } + public interface Listener { void onChangeRemoved(ChangeList source); + void onChangeApplied(ChangeList source); } } diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeType.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeType.java index 38382b003050..d53490ff22b3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeType.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ChangeType.java @@ -17,17 +17,11 @@ package com.intellij.openapi.diff.impl.incrementalMerge; import com.intellij.openapi.diff.ex.DiffFragment; import com.intellij.openapi.diff.impl.highlighting.LineRenderer; -import com.intellij.openapi.diff.impl.util.DocumentUtil; import com.intellij.openapi.diff.impl.util.TextDiffType; -import com.intellij.openapi.editor.Document; -import com.intellij.openapi.editor.RangeMarker; -import com.intellij.openapi.editor.markup.HighlighterLayer; -import com.intellij.openapi.editor.markup.HighlighterTargetArea; -import com.intellij.openapi.editor.markup.RangeHighlighter; -import com.intellij.openapi.editor.markup.SeparatorPlacement; -import com.intellij.openapi.project.Project; +import com.intellij.openapi.editor.colors.EditorColorsScheme; +import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.util.TextRange; -import com.intellij.openapi.vfs.ReadonlyStatusHandler; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -36,44 +30,94 @@ import java.awt.*; public class ChangeType { private static final int LAYER = HighlighterLayer.SELECTION - 1; - private static final ChangeType INSERT = new ChangeType(TextDiffType.INSERT); - private static final ChangeType DELETED = new ChangeType(TextDiffType.DELETED); - private static final ChangeType CHANGE = new ChangeType(TextDiffType.CHANGED); - static final ChangeType CONFLICT = new ChangeType(TextDiffType.CONFLICT); + private static final ChangeType INSERT = new ChangeType(TextDiffType.INSERT, false); + private static final ChangeType DELETED = new ChangeType(TextDiffType.DELETED, false); + private static final ChangeType CHANGE = new ChangeType(TextDiffType.CHANGED, false); + static final ChangeType CONFLICT = new ChangeType(TextDiffType.CONFLICT, false); private final TextDiffType myDiffType; + private final boolean myApplied; - private ChangeType(TextDiffType diffType) { - myDiffType = diffType; + private ChangeType(TextDiffType diffType, boolean applied) { + myApplied = applied; + if (applied) { + myDiffType = TextDiffType.deriveApplied(diffType); + } + else { + myDiffType = diffType; + } } + public boolean isApplied() { + return myApplied; + } + + @NotNull + public static ChangeType deriveApplied(ChangeType type) { + return new ChangeType(type.myDiffType, true); + } + + @Nullable public RangeHighlighter addMarker(ChangeSide changeSide, ChangeHighlighterHolder markup) { String text = changeSide.getText(); - if (text != null && text.length() > 0) return addBlock(text, changeSide, markup, myDiffType); - else return addLine(markup, changeSide.getStartLine(), myDiffType, SeparatorPlacement.TOP); + if (text != null && text.length() > 0) { + return addBlock(text, changeSide, markup, myDiffType); + } + else { + return addLine(markup, changeSide.getStartLine(), myDiffType, SeparatorPlacement.TOP); + } } + @NotNull public TextDiffType getTypeKey() { return myDiffType; } - public TextDiffType getTextDiffType() { return getTypeKey(); } + @NotNull + public TextDiffType getTextDiffType() { + return getTypeKey(); + } + + @Nullable + private RangeHighlighter addBlock(String text, ChangeSide changeSide, ChangeHighlighterHolder markup, TextDiffType diffType) { + EditorColorsScheme colorScheme = markup.getEditor().getColorsScheme(); + Color separatorColor = getSeparatorColor(diffType.getLegendColor(colorScheme)); + LineSeparatorRenderer separatorRenderer = new LineSeparatorRenderer() { + @Override + public void drawLine(Graphics g, int x1, int x2, int y) { + Graphics2D g2 = (Graphics2D) g; + if (myApplied) { + UIUtil.drawBoldDottedLine(g2, x1, x2, y, g2.getBackground(), g2.getColor(), false); + } + else { + UIUtil.drawDottedLine(g2, x1, y, x2, y, g2.getBackground(), g2.getColor()); + } + } + }; - private static RangeHighlighter addBlock(String text, ChangeSide changeSide, ChangeHighlighterHolder markup, TextDiffType diffType) { int length = text.length(); int start = changeSide.getStart(); int end = start + length; - RangeHighlighter highlighter = markup.addRangeHighlighter(start, end, ChangeType.LAYER, diffType, HighlighterTargetArea.EXACT_RANGE); - highlighter.setLineSeparatorPlacement(SeparatorPlacement.TOP); - highlighter.setLineMarkerRenderer(LineRenderer.top()); - highlighter.setLineSeparatorColor(Color.GRAY); + RangeHighlighter highlighter = markup.addRangeHighlighter(start, end, LAYER, diffType, HighlighterTargetArea.EXACT_RANGE); + + if (highlighter != null) { + highlighter.setLineSeparatorPlacement(SeparatorPlacement.TOP); + highlighter.setLineSeparatorColor(separatorColor); + highlighter.setLineSeparatorRenderer(separatorRenderer); + highlighter.setLineMarkerRenderer(LineRenderer.top()); + } + if (text.charAt(length - 1) == '\n') { end--; } + highlighter = markup.addRangeHighlighter(start, end, LAYER, TextDiffType.NONE, HighlighterTargetArea.EXACT_RANGE); - highlighter.setLineSeparatorPlacement(SeparatorPlacement.BOTTOM); - highlighter.setLineSeparatorColor(Color.GRAY); - highlighter.setLineMarkerRenderer(LineRenderer.bottom()); + if (highlighter != null) { + highlighter.setLineSeparatorPlacement(SeparatorPlacement.BOTTOM); + highlighter.setLineSeparatorColor(separatorColor); + highlighter.setLineSeparatorRenderer(separatorRenderer); + highlighter.setLineMarkerRenderer(LineRenderer.bottom()); + } return highlighter; } @@ -103,4 +147,13 @@ public class ChangeType { return myDiffType.getDisplayName(); } + @NotNull + public Color getSeparatorColor(@Nullable Color highlightColor) { + if (myApplied) { + return highlightColor == null ? Color.DARK_GRAY : highlightColor.darker(); + } + return Color.GRAY; + } + + } diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ConflictChange.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ConflictChange.java index c6820c91dfbe..f43adc6c0fd1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ConflictChange.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ConflictChange.java @@ -15,6 +15,8 @@ */ package com.intellij.openapi.diff.impl.incrementalMerge; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.highlighting.FragmentSide; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.util.TextRange; @@ -25,12 +27,24 @@ import com.intellij.openapi.util.TextRange; */ class ConflictChange extends Change implements DiffRangeMarker.RangeInvalidListener { + private static final Logger LOG = Logger.getInstance(ConflictChange.class); + private SimpleChangeSide myOriginalSide; private MergeConflict myConflict; + private final ChangeList myChangeList; + private ChangeType myType; + private boolean mySemiApplied; - public ConflictChange(MergeConflict conflict, FragmentSide mergeSide, TextRange range) { + public ConflictChange(MergeConflict conflict, FragmentSide mergeSide, TextRange range, ChangeList changeList) { myConflict = conflict; + myChangeList = changeList; myOriginalSide = new SimpleChangeSide(mergeSide, new DiffRangeMarker((DocumentEx)conflict.getOriginalDocument(mergeSide), range, this)); + myType = ChangeType.CONFLICT; + } + + @Override + protected void changeSide(ChangeSide sideToChange, DiffRangeMarker newRange) { + myConflict.setRange(newRange); } protected void removeFromList() { @@ -51,13 +65,43 @@ class ConflictChange extends Change implements DiffRangeMarker.RangeInvalidListe } public ChangeType getType() { - return ChangeType.CONFLICT; + return myType; } public ChangeList getChangeList() { return myConflict.getMergeList().getChanges(myOriginalSide.getFragmentSide()); } + @Override + public void onApplied() { + myType = ChangeType.deriveApplied(myType); + myChangeList.apply(this); + + myOriginalSide.getHighlighterHolder().updateHighlighter(myOriginalSide, myType); + myOriginalSide.getHighlighterHolder().setActions(new AnAction[0]); + + // display, what one side of the conflict was resolved to + myConflict.getHighlighterHolder().updateHighlighter(myConflict, myType); + + // update the other variant of the conflict to point to the bottom + if (!mySemiApplied) { + ConflictChange otherChange = myConflict.getOtherChange(this); + LOG.assertTrue(otherChange != null, String.format("Other change is null. This change: %s Merge conflict: %s", this, myConflict)); + otherChange.mySemiApplied = true; + otherChange.updateOtherSideOnConflictApply(); + myConflict.removeOtherChange(this); + } + } + + private void updateOtherSideOnConflictApply() { + int startOffset = myConflict.getRange().getEndOffset(); + TextRange emptyRange = new TextRange(startOffset, startOffset); + myConflict = myConflict.deriveSideForNotAppliedChange(emptyRange, null, this); + myOriginalSide.getHighlighterHolder().updateHighlighter(myOriginalSide, myType); + myConflict.getHighlighterHolder().updateHighlighter(myConflict, myType); + myChangeList.fireOnChangeApplied(); + } + public void onRemovedFromList() { myOriginalSide.getRange().removeListener(this); myConflict = null; diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/MergeConflict.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/MergeConflict.java index ca18b0183b18..728888c8167c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/MergeConflict.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/MergeConflict.java @@ -20,6 +20,7 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.util.TextRange; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Represents a merge conflict, i.e. two {@link ConflictChange conflicting changes}, one from left, another from right. @@ -27,16 +28,33 @@ import org.jetbrains.annotations.NotNull; class MergeConflict extends ChangeSide implements DiffRangeMarker.RangeInvalidListener { @NotNull private final MergeList myMergeList; - @NotNull private final DiffRangeMarker myCommonRange; - @NotNull private final ConflictChange myLeftChange; - @NotNull private final ConflictChange myRightChange; - @NotNull private final ChangeHighlighterHolder myCommonHighlighterHolder = new ChangeHighlighterHolder(); + @NotNull private DiffRangeMarker myCommonRange; + @Nullable private ConflictChange myLeftChange; + @Nullable private ConflictChange myRightChange; + @NotNull private final ChangeHighlighterHolder myCommonHighlighterHolder; MergeConflict(TextRange commonRange, MergeList mergeList, TextRange leftMarker, TextRange rightMarker) { myCommonRange = new DiffRangeMarker((DocumentEx)mergeList.getBaseDocument(),commonRange, this); myMergeList = mergeList; - myLeftChange = new ConflictChange(this, FragmentSide.SIDE1, leftMarker); - myRightChange = new ConflictChange(this, FragmentSide.SIDE2, rightMarker); + myCommonHighlighterHolder = new ChangeHighlighterHolder(); + myLeftChange = new ConflictChange(this, FragmentSide.SIDE1, leftMarker, mergeList.getLeftChangeList()); + myRightChange = new ConflictChange(this, FragmentSide.SIDE2, rightMarker, mergeList.getRightChangeList()); + } + + private MergeConflict(TextRange commonRange, MergeList mergeList, ChangeHighlighterHolder highlighterHolder, + ConflictChange leftChange, ConflictChange rightChange) { + myCommonRange = new DiffRangeMarker((DocumentEx)mergeList.getBaseDocument(),commonRange, this); + myMergeList = mergeList; + myCommonHighlighterHolder = highlighterHolder; + myLeftChange = leftChange; + myRightChange = rightChange; + } + + public MergeConflict deriveSideForNotAppliedChange(TextRange baseRange, @Nullable ConflictChange originalChange, ConflictChange otherChange) { + ChangeHighlighterHolder highlighterHolder = new ChangeHighlighterHolder(); + MergeConflict mergeConflict = new MergeConflict(baseRange, myMergeList, highlighterHolder, originalChange, otherChange); + highlighterHolder.highlight(mergeConflict, myCommonHighlighterHolder.getEditor(), ChangeType.CONFLICT); + return mergeConflict; } public ChangeHighlighterHolder getHighlighterHolder() { @@ -47,16 +65,29 @@ class MergeConflict extends ChangeSide implements DiffRangeMarker.RangeInvalidLi return myCommonRange; } - @NotNull + @Nullable public ConflictChange getLeftChange() { return myLeftChange; } - @NotNull + @Nullable public ConflictChange getRightChange() { return myRightChange; } + @Nullable + ConflictChange getOtherChange(ConflictChange change) { + if (change == myLeftChange) { + return myRightChange; + } + else if (change == myRightChange) { + return myLeftChange; + } + else { + throw new IllegalStateException("Unexpected change: " + change); + } + } + public void conflictRemoved() { removeHighlighters(myLeftChange); removeHighlighters(myRightChange); @@ -65,8 +96,26 @@ class MergeConflict extends ChangeSide implements DiffRangeMarker.RangeInvalidLi myCommonRange.removeListener(this); } - private static void removeHighlighters(@NotNull ConflictChange change) { - change.getOriginalSide().getHighlighterHolder().removeHighlighters(); + public void setRange(DiffRangeMarker range) { + myCommonRange = range; + } + + public void removeOtherChange(ConflictChange change) { + if (change == myLeftChange) { + myRightChange = null; + } + else if (change == myRightChange) { + myLeftChange = null; + } + else { + throw new IllegalStateException("Unexpected change: " + change); + } + } + + private static void removeHighlighters(@Nullable ConflictChange change) { + if (change != null) { + change.getOriginalSide().getHighlighterHolder().removeHighlighters(); + } } public Document getOriginalDocument(FragmentSide mergeSide) { diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/MergeList.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/MergeList.java index 95c7fd938aa7..979c76f7cb1e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/MergeList.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/MergeList.java @@ -67,6 +67,16 @@ public class MergeList implements UserDataHolder { myBaseToRightChangeList = new ChangeList(base, right, project); } + @NotNull + public ChangeList getLeftChangeList() { + return myBaseToLeftChangeList; + } + + @NotNull + public ChangeList getRightChangeList() { + return myBaseToRightChangeList; + } + public static MergeList create(@NotNull Project project, @NotNull Document left, @NotNull Document base, @NotNull Document right) throws FilesTooBigForDiffException { MergeList mergeList = new MergeList(project, left, base, right); @@ -109,6 +119,8 @@ public class MergeList implements UserDataHolder { } else { MergeConflict conflict = new MergeConflict(baseRange, mergeList, leftRange, rightRange); + assert conflict.getLeftChange() != null; + assert conflict.getRightChange() != null; leftChanges.add(conflict.getLeftChange()); rightChanges.add(conflict.getRightChange()); } @@ -224,9 +236,13 @@ public class MergeList implements UserDataHolder { } } - public void removeChanges(@NotNull Change leftChange, @NotNull Change rightChange) { - myBaseToLeftChangeList.remove(leftChange); - myBaseToRightChangeList.remove(rightChange); + public void removeChanges(@Nullable Change leftChange, @Nullable Change rightChange) { + if (leftChange != null) { + myBaseToLeftChangeList.remove(leftChange); + } + if (rightChange != null) { + myBaseToRightChangeList.remove(rightChange); + } } public Document getBaseDocument() { diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/SimpleChange.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/SimpleChange.java index e0736c3bb373..7432f1438975 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/SimpleChange.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/SimpleChange.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.diff.impl.incrementalMerge; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.highlighting.FragmentSide; import com.intellij.openapi.editor.ex.DocumentEx; @@ -23,7 +24,7 @@ import org.jetbrains.annotations.NotNull; class SimpleChange extends Change implements DiffRangeMarker.RangeInvalidListener{ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.incrementalMerge.Change"); - private final ChangeType myType; + private ChangeType myType; private final SimpleChangeSide[] mySides; private final ChangeList myChangeList; @@ -38,6 +39,22 @@ class SimpleChange extends Change implements DiffRangeMarker.RangeInvalidListene return new SimpleChangeSide(side, new DiffRangeMarker((DocumentEx)changeList.getDocument(side), range1, this)); } + /** + * Changes the given Side of a Change to a new text range. + * @param sideToChange Side to be changed. + * @param newRange New change range. + */ + @Override + protected void changeSide(ChangeSide sideToChange, DiffRangeMarker newRange) { + for (int i = 0; i < mySides.length; i++) { + SimpleChangeSide side = mySides[i]; + if (side.equals(sideToChange)) { + mySides[i] = new SimpleChangeSide(sideToChange, newRange); + break; + } + } + } + protected void removeFromList() { myChangeList.remove(this); } @@ -54,6 +71,17 @@ class SimpleChange extends Change implements DiffRangeMarker.RangeInvalidListene return myChangeList; } + @Override + public void onApplied() { + myType = ChangeType.deriveApplied(myType); + for (SimpleChangeSide side : mySides) { + ChangeHighlighterHolder highlighterHolder = side.getHighlighterHolder(); + highlighterHolder.setActions(new AnAction[0]); + highlighterHolder.updateHighlighter(side, myType); + } + myChangeList.apply(this); + } + public void onRemovedFromList() { for (int i = 0; i < mySides.length; i++) { SimpleChangeSide side = mySides[i]; diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ui/MergePanel2.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ui/MergePanel2.java index 37bcf703b30e..03d95b5b698c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ui/MergePanel2.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/incrementalMerge/ui/MergePanel2.java @@ -497,6 +497,13 @@ public class MergePanel2 implements DiffViewer { } private class DividersRepainter implements ChangeList.Listener { + + @Override + public void onChangeApplied(ChangeList source) { + FragmentSide side = myMergeList.getSideOf(source); + myDividers[side.getIndex()].repaint(); + } + public void onChangeRemoved(ChangeList source) { FragmentSide side = myMergeList.getSideOf(source); myDividers[side.getIndex()].repaint(); diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/splitter/DividerPolygon.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/splitter/DividerPolygon.java index e5cf315e8e87..023ec4e9aaf2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/splitter/DividerPolygon.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/splitter/DividerPolygon.java @@ -18,7 +18,6 @@ package com.intellij.openapi.diff.impl.splitter; import com.intellij.openapi.diff.impl.EditingSides; import com.intellij.openapi.diff.impl.highlighting.FragmentSide; import com.intellij.openapi.diff.impl.util.TextDiffType; -import com.intellij.openapi.diff.impl.util.TextDiffTypeEnum; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.util.Comparing; @@ -99,10 +98,7 @@ public class DividerPolygon { ArrayList polygons = new ArrayList(); for (int i = indices.getStart(); i < indices.getEnd(); i++) { Trapezium trapezium = lineBlocks.getTrapezium(i); - final TextDiffTypeEnum diffTypeEnum = lineBlocks.getType(i); - if (diffTypeEnum == null) continue; - TextDiffType type = TextDiffType.create(diffTypeEnum); - if (type == null) continue; + final TextDiffType type = lineBlocks.getType(i); Color color = type.getPolygonColor(editor1); polygons.add(createPolygon(transformations, trapezium, color, left)); } diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/splitter/LineBlocks.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/splitter/LineBlocks.java index 728bd8c55c2f..3bb4c4f44104 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/splitter/LineBlocks.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/splitter/LineBlocks.java @@ -20,21 +20,20 @@ import com.intellij.openapi.diff.impl.fragments.LineBlock; import com.intellij.openapi.diff.impl.fragments.LineFragment; import com.intellij.openapi.diff.impl.highlighting.FragmentSide; import com.intellij.openapi.diff.impl.incrementalMerge.Change; -import com.intellij.openapi.diff.impl.util.TextDiffTypeEnum; +import com.intellij.openapi.diff.impl.incrementalMerge.ChangeList; +import com.intellij.openapi.diff.impl.util.TextDiffType; import com.intellij.util.containers.IntArrayList; +import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Iterator; +import java.util.*; public class LineBlocks { - public static final LineBlocks EMPTY = new LineBlocks(SimpleIntervalProvider.EMPTY, new TextDiffTypeEnum[0]); + public static final LineBlocks EMPTY = new LineBlocks(SimpleIntervalProvider.EMPTY, new TextDiffType[0]); private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.splitter.LineBlocks"); private final IntervalsProvider myIntervalsProvider; - private final TextDiffTypeEnum[] myTypes; + private final TextDiffType[] myTypes; - private LineBlocks(IntervalsProvider intervalsProvider, TextDiffTypeEnum[] types) { + private LineBlocks(IntervalsProvider intervalsProvider, TextDiffType[] types) { myIntervalsProvider = intervalsProvider; myTypes = types; } @@ -52,11 +51,11 @@ public class LineBlocks { } public Trapezium getTrapezium(int index) { - return new Trapezium(getIntervals(FragmentSide.SIDE1)[index], + return new Trapezium(getIntervals(FragmentSide.SIDE1)[index], getIntervals(FragmentSide.SIDE2)[index]); } - public TextDiffTypeEnum getType(int index) { + public TextDiffType getType(int index) { return myTypes[index]; } @@ -68,8 +67,8 @@ public class LineBlocks { IntArrayList result = new IntArrayList(getIntervals(side).length); int previousBeginning = Integer.MIN_VALUE; Interval[] sideIntervals = getIntervals(side); - for (int i = 0; i < sideIntervals.length; i++) { - int start = sideIntervals[i].getStart(); + for (Interval sideInterval : sideIntervals) { + int start = sideInterval.getStart(); if (start != previousBeginning) result.add(start); previousBeginning = start; } @@ -140,8 +139,7 @@ public class LineBlocks { public static LineBlocks fromLineFragments(ArrayList lines) { ArrayList filtered = new ArrayList(); - for (Iterator iterator = lines.iterator(); iterator.hasNext();) { - LineFragment fragment = iterator.next(); + for (LineFragment fragment : lines) { if (fragment.getType() != null) filtered.add(fragment); } return createLineBlocks(filtered.toArray(new LineBlock[filtered.size()])); @@ -151,56 +149,45 @@ public class LineBlocks { Arrays.sort(blocks, LineBlock.COMPARATOR); Interval[] intervals1 = new Interval[blocks.length]; Interval[] intervals2 = new Interval[blocks.length]; - TextDiffTypeEnum[] types = new TextDiffTypeEnum[blocks.length]; + TextDiffType[] types = new TextDiffType[blocks.length]; for (int i = 0; i < blocks.length; i++) { LineBlock block = blocks[i]; intervals1[i] = new Interval(block.getStartingLine1(), block.getModifiedLines1()); intervals2[i] = new Interval(block.getStartingLine2(), block.getModifiedLines2()); - types[i] = block.getType(); + types[i] = TextDiffType.create(block.getType()); } return create(intervals1, intervals2, types); } - private static LineBlocks create(Interval[] intervals1, Interval[] intervals2, TextDiffTypeEnum[] types) { + private static LineBlocks create(Interval[] intervals1, Interval[] intervals2, TextDiffType[] types) { return new LineBlocks(new SimpleIntervalProvider(intervals1, intervals2), types); } - public static LineBlocks fromChanges(ArrayList changes) { + @NotNull + public static LineBlocks fromChanges(@NotNull List changes) { + // changes may come mixed, need to sort them to get correct intervals + Collections.sort(changes, ChangeList.CHANGE_ORDER); + ArrayList intervals1 = new ArrayList(); ArrayList intervals2 = new ArrayList(); - ArrayList types = new ArrayList(); - //int prevEnd1 = 0; - //int prevEnd2 = 0; - for (Iterator iterator = changes.iterator(); iterator.hasNext();) { - Change change = iterator.next(); - if (!change.isValid()) continue; + ArrayList types = new ArrayList(); + for (Change change : changes) { + if (!change.isValid()) { continue; } int start1 = change.getChangeSide(FragmentSide.SIDE1).getStartLine(); - int start2 = change.getChangeSide(FragmentSide.SIDE2).getStartLine(); - //if (start1 != prevEnd1 || start2 != prevEnd2) { - // intervals1.add(Interval.fromTo(prevEnd1, start1)); - // intervals2.add(Interval.fromTo(prevEnd2, start2)); - // types.add(null); - //} int end1 = change.getChangeSide(FragmentSide.SIDE1).getEndLine(); intervals1.add(Interval.fromTo(start1, end1)); + + int start2 = change.getChangeSide(FragmentSide.SIDE2).getStartLine(); int end2 = change.getChangeSide(FragmentSide.SIDE2).getEndLine(); intervals2.add(Interval.fromTo(start2, end2)); - types.add(change.getType().getTypeKey().getType()); - //prevEnd1 = end1; - //prevEnd2 = end2; - } - //LOG.assertTrue(prevEnd1 < length1 && prevEnd2 < length2); - //if (prevEnd1 != length1 || prevEnd2 != length2) { - // intervals1.add(Interval.fromTo(prevEnd1, length1)); - // intervals2.add(Interval.fromTo(prevEnd2, length2)); - // types.add(null); - //} + types.add(change.getType().getTypeKey()); + } return create(intervals1.toArray(new Interval[intervals1.size()]), - intervals2.toArray(new Interval[intervals2.size()]), types.toArray(new TextDiffTypeEnum[types.size()])); + intervals2.toArray(new Interval[intervals2.size()]), types.toArray(new TextDiffType[types.size()])); } - public TextDiffTypeEnum[] getTypes() { + public TextDiffType[] getTypes() { return myTypes; } diff --git a/platform/platform-impl/src/com/intellij/openapi/diff/impl/util/TextDiffType.java b/platform/platform-impl/src/com/intellij/openapi/diff/impl/util/TextDiffType.java index f713c7e5153d..5c7595d8ae60 100644 --- a/platform/platform-impl/src/com/intellij/openapi/diff/impl/util/TextDiffType.java +++ b/platform/platform-impl/src/com/intellij/openapi/diff/impl/util/TextDiffType.java @@ -38,17 +38,20 @@ public class TextDiffType implements DiffStatusBar.LegendTypeDescriptor { public static final TextDiffType NONE = new TextDiffType(TextDiffTypeEnum.NONE, DiffBundle.message("diff.type.none.name"), null); - private final TextDiffTypeEnum myType; public static final List DIFF_TYPES = Arrays.asList(DELETED, CHANGED, INSERT); public static final List MERGE_TYPES = Arrays.asList(DELETED, CHANGED, INSERT, CONFLICT); - private final TextAttributesKey myAttributesKey; - private final String myDisplayName; + public static final Convertor ATTRIBUTES_KEY = new Convertor() { public TextAttributesKey convert(TextDiffType textDiffType) { return textDiffType.getAttributesKey(); } }; + private final TextDiffTypeEnum myType; + private final TextAttributesKey myAttributesKey; + private final String myDisplayName; + private final boolean myApplied; + public static TextDiffType create(@NotNull final TextDiffTypeEnum type) { if (TextDiffTypeEnum.INSERT.equals(type)) { return INSERT; @@ -59,15 +62,29 @@ public class TextDiffType implements DiffStatusBar.LegendTypeDescriptor { } else if (TextDiffTypeEnum.CONFLICT.equals(type)) { return CONFLICT; } else { - // NONE return NONE; } } - private TextDiffType(TextDiffTypeEnum type, String displayName, TextAttributesKey attrubutesKey) { + /** + * Creates a new TextDiffType based on the given one. + * @param source + * @return + */ + @NotNull + public static TextDiffType deriveApplied(@NotNull TextDiffType source) { + return new TextDiffType(source.myType, source.myDisplayName, source.myAttributesKey, true); + } + + private TextDiffType(TextDiffTypeEnum type, String displayName, TextAttributesKey attributesKey) { + this(type, displayName, attributesKey, false); + } + + private TextDiffType(TextDiffTypeEnum type, String displayName, TextAttributesKey attributesKey, boolean applied) { myType = type; - myAttributesKey = attrubutesKey; + myAttributesKey = attributesKey; myDisplayName = displayName; + myApplied = applied; } public String getDisplayName() { @@ -76,7 +93,7 @@ public class TextDiffType implements DiffStatusBar.LegendTypeDescriptor { @Nullable public Color getLegendColor(EditorColorsScheme colorScheme) { - TextAttributes attributes = getTextAttributes(colorScheme); + TextAttributes attributes = colorScheme.getAttributes(myAttributesKey); return attributes != null ? attributes.getBackgroundColor() : null; } @@ -85,7 +102,15 @@ public class TextDiffType implements DiffStatusBar.LegendTypeDescriptor { } public TextAttributes getTextAttributes(EditorColorsScheme scheme) { - return scheme.getAttributes(myAttributesKey); + TextAttributes originalAttrs = scheme.getAttributes(myAttributesKey); + if (!myApplied) { + return originalAttrs; + } + else { + TextAttributes overridingAttributes = new TextAttributes(); + overridingAttributes.setBackgroundColor(scheme.getDefaultBackground()); + return TextAttributes.merge(originalAttrs, overridingAttributes); + } } @Nullable @@ -104,7 +129,7 @@ public class TextDiffType implements DiffStatusBar.LegendTypeDescriptor { } public String toString(){ - return myDisplayName; + return myApplied ? myDisplayName + "_applied" : myDisplayName; } public TextDiffTypeEnum getType() {