[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.
This commit is contained in:
Kirill Likhodedov
2012-06-02 16:49:39 +04:00
parent 714fd989fb
commit 107dcc9f6f
13 changed files with 392 additions and 112 deletions
@@ -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() {
@@ -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);
}
}
@@ -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];
@@ -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> CHANGE_ORDER = new SimpleChange.ChangeOrder(FragmentSide.SIDE1);
public static final Comparator<Change> CHANGE_ORDER = new SimpleChange.ChangeOrder(FragmentSide.SIDE1);
private final Project myProject;
private final Document[] myDocuments = new Document[2];
private final ArrayList<Listener> myListeners = new ArrayList<Listener>();
private ArrayList<Change> myChanges;
private ArrayList<Change> 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<Change>(changes);
myAppliedChanges = new ArrayList<Change>();
}
public Project getProject() { return myProject; }
@@ -187,15 +189,28 @@ public class ChangeList {
}
public LineBlocks getLineBlocks() {
return LineBlocks.fromChanges(myChanges);
ArrayList<Change> changes = new ArrayList<Change>(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);
}
}
@@ -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;
}
}
@@ -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;
@@ -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) {
@@ -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() {
@@ -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];
@@ -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();
@@ -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<DividerPolygon> polygons = new ArrayList<DividerPolygon>();
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));
}
@@ -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<LineFragment> lines) {
ArrayList<LineBlock> filtered = new ArrayList<LineBlock>();
for (Iterator<LineFragment> 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<Change> changes) {
@NotNull
public static LineBlocks fromChanges(@NotNull List<Change> changes) {
// changes may come mixed, need to sort them to get correct intervals
Collections.sort(changes, ChangeList.CHANGE_ORDER);
ArrayList<Interval> intervals1 = new ArrayList<Interval>();
ArrayList<Interval> intervals2 = new ArrayList<Interval>();
ArrayList<TextDiffTypeEnum> types = new ArrayList<TextDiffTypeEnum>();
//int prevEnd1 = 0;
//int prevEnd2 = 0;
for (Iterator<Change> iterator = changes.iterator(); iterator.hasNext();) {
Change change = iterator.next();
if (!change.isValid()) continue;
ArrayList<TextDiffType> types = new ArrayList<TextDiffType>();
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;
}
@@ -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<TextDiffType> DIFF_TYPES = Arrays.asList(DELETED, CHANGED, INSERT);
public static final List<TextDiffType> MERGE_TYPES = Arrays.asList(DELETED, CHANGED, INSERT, CONFLICT);
private final TextAttributesKey myAttributesKey;
private final String myDisplayName;
public static final Convertor<TextDiffType, TextAttributesKey> ATTRIBUTES_KEY = new Convertor<TextDiffType, TextAttributesKey>() {
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() {