diff: refactor - extract model for SimpleDiffViewer

GitOrigin-RevId: 4e64d0ce809c17721e25ea8ffa6bbfa54a02537b
This commit is contained in:
Aleksey Pivovarov
2019-08-12 18:05:04 +03:00
committed by intellij-monorepo-bot
parent 724bdba465
commit 1c79fc589d
5 changed files with 470 additions and 350 deletions
@@ -15,170 +15,44 @@
*/
package com.intellij.diff.tools.simple;
import com.intellij.diff.fragments.DiffFragment;
import com.intellij.diff.fragments.LineFragment;
import com.intellij.diff.util.*;
import com.intellij.openapi.actionSystem.Shortcut;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
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.keymap.KeymapManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.diff.util.DiffUtil;
import com.intellij.diff.util.Side;
import com.intellij.diff.util.TextDiffType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
public class SimpleDiffChange {
@NotNull protected final SimpleDiffViewer myViewer;
private final int myIndex;
@NotNull private final LineFragment myFragment;
@Nullable private final List<DiffFragment> myInnerFragments;
private final boolean myIsExcluded;
private final boolean myIsSkipped;
@NotNull protected final List<RangeHighlighter> myHighlighters = new ArrayList<>();
@NotNull protected final List<GutterOperation> myOperations = new ArrayList<>();
private boolean myIsValid = true;
private boolean myIsDestroyed = true;
private final int[] myLineStartShifts = new int[2];
private final int[] myLineEndShifts = new int[2];
public SimpleDiffChange(@NotNull SimpleDiffViewer viewer,
public SimpleDiffChange(int index,
@NotNull LineFragment fragment) {
this(viewer, fragment, false, false);
this(index, fragment, false, false);
}
public SimpleDiffChange(@NotNull SimpleDiffViewer viewer,
public SimpleDiffChange(int index,
@NotNull LineFragment fragment,
boolean isExcluded,
boolean isSkipped) {
myViewer = viewer;
myIndex = index;
myFragment = fragment;
myInnerFragments = fragment.getInnerFragments();
myIsExcluded = isExcluded;
myIsSkipped = isSkipped;
}
public void installHighlighter(@Nullable LineFragment previousFragment) {
assert myHighlighters.isEmpty();
if (myInnerFragments != null) {
doInstallHighlighterWithInner();
}
else {
doInstallHighlighterSimple();
}
doInstallNonSquashedChangesSeparator(previousFragment);
doInstallActionHighlighters();
public int getIndex() {
return myIndex;
}
public void destroyHighlighter() {
for (RangeHighlighter highlighter : myHighlighters) {
highlighter.dispose();
}
myHighlighters.clear();
for (GutterOperation operation : myOperations) {
operation.dispose();
}
myOperations.clear();
}
private void doInstallHighlighterSimple() {
createHighlighter(Side.LEFT, false);
createHighlighter(Side.RIGHT, false);
}
private void doInstallHighlighterWithInner() {
assert myInnerFragments != null;
createHighlighter(Side.LEFT, true);
createHighlighter(Side.RIGHT, true);
for (DiffFragment fragment : myInnerFragments) {
createInlineHighlighter(fragment, Side.LEFT);
createInlineHighlighter(fragment, Side.RIGHT);
}
}
private void doInstallNonSquashedChangesSeparator(@Nullable LineFragment previousFragment) {
createNonSquashedChangesSeparator(previousFragment, Side.LEFT);
createNonSquashedChangesSeparator(previousFragment, Side.RIGHT);
}
protected void doInstallActionHighlighters() {
if (myIsSkipped) return;
myOperations.add(new AcceptGutterOperation(Side.LEFT));
myOperations.add(new AcceptGutterOperation(Side.RIGHT));
}
private void createHighlighter(@NotNull Side side, boolean ignored) {
Editor editor = myViewer.getEditor(side);
TextDiffType type = DiffUtil.getLineDiffType(myFragment);
int startLine = side.getStartLine(myFragment);
int endLine = side.getEndLine(myFragment);
myHighlighters.addAll(new DiffDrawUtil.LineHighlighterBuilder(editor, startLine, endLine, type)
.withIgnored(ignored)
.withExcludedInEditor(myIsSkipped)
.withExcludedInGutter(myIsExcluded)
.done());
}
private void createInlineHighlighter(@NotNull DiffFragment fragment, @NotNull Side side) {
if (myIsSkipped) return;
int start = side.getStartOffset(fragment);
int end = side.getEndOffset(fragment);
TextDiffType type = DiffUtil.getDiffType(fragment);
int startOffset = side.getStartOffset(myFragment);
start += startOffset;
end += startOffset;
Editor editor = myViewer.getEditor(side);
myHighlighters.addAll(DiffDrawUtil.createInlineHighlighter(editor, start, end, type));
}
private void createNonSquashedChangesSeparator(@Nullable LineFragment previousFragment, @NotNull Side side) {
if (previousFragment == null) return;
int startLine = side.getStartLine(myFragment);
int endLine = side.getEndLine(myFragment);
int prevStartLine = side.getStartLine(previousFragment);
int prevEndLine = side.getEndLine(previousFragment);
if (startLine == endLine) return;
if (prevStartLine == prevEndLine) return;
if (prevEndLine != startLine) return;
myHighlighters.addAll(DiffDrawUtil.createLineMarker(myViewer.getEditor(side), startLine, TextDiffType.MODIFIED));
}
public void updateGutterActions(boolean force) {
for (GutterOperation operation : myOperations) {
operation.update(force);
}
}
//
// Getters
//
public int getStartLine(@NotNull Side side) {
return side.getStartLine(myFragment) + side.select(myLineStartShifts);
}
@@ -204,16 +78,16 @@ public class SimpleDiffChange {
return myIsValid;
}
public boolean isDestroyed() {
return myIsDestroyed;
}
@NotNull
public LineFragment getFragment() {
return myFragment;
}
//
// Shift
//
public boolean processChange(int oldLine1, int oldLine2, int shift, @NotNull Side side) {
public boolean processDocumentChange(int oldLine1, int oldLine2, int shift, @NotNull Side side) {
int line1 = getStartLine(side);
int line2 = getEndLine(side);
int sideIndex = side.getIndex();
@@ -223,124 +97,13 @@ public class SimpleDiffChange {
myLineEndShifts[sideIndex] += newRange.endLine - line2;
if (newRange.damaged) {
for (GutterOperation operation : myOperations) {
operation.dispose();
}
myOperations.clear();
myIsValid = false;
}
return newRange.damaged;
}
//
// Helpers
//
protected abstract class GutterOperation {
@NotNull protected final Side mySide;
@NotNull private final RangeHighlighter myHighlighter;
protected boolean myCtrlPressed;
public GutterOperation(@NotNull Side side) {
mySide = side;
int offset = side.getStartOffset(myFragment);
EditorEx editor = myViewer.getEditor(side);
myHighlighter = editor.getMarkupModel().addRangeHighlighter(offset, offset,
HighlighterLayer.ADDITIONAL_SYNTAX,
null,
HighlighterTargetArea.LINES_IN_RANGE);
update(true);
}
public void dispose() {
myHighlighter.dispose();
}
public void update(boolean force) {
if (!force && !areModifiersChanged()) {
return;
}
if (myHighlighter.isValid()) myHighlighter.setGutterIconRenderer(createRenderer());
}
private boolean areModifiersChanged() {
return myCtrlPressed != myViewer.getModifierProvider().isCtrlPressed();
}
@Nullable
public abstract GutterIconRenderer createRenderer();
}
private class AcceptGutterOperation extends GutterOperation {
AcceptGutterOperation(@NotNull Side side) {
super(side);
}
@Nullable
@Override
public GutterIconRenderer createRenderer() {
myCtrlPressed = myViewer.getModifierProvider().isCtrlPressed();
boolean isOtherEditable = DiffUtil.isEditable(myViewer.getEditor(mySide.other()));
boolean isAppendable = myFragment.getStartLine1() != myFragment.getEndLine1() &&
myFragment.getStartLine2() != myFragment.getEndLine2();
if (isOtherEditable) {
if (myCtrlPressed && isAppendable) {
return createAppendRenderer(mySide);
}
else {
return createApplyRenderer(mySide);
}
}
return null;
}
}
@Nullable
private GutterIconRenderer createApplyRenderer(@NotNull final Side side) {
String text;
Icon icon = DiffUtil.getArrowIcon(side);
if (side == Side.LEFT && myViewer.isDiffForLocalChanges()) {
text = "Revert";
}
else {
text = "Accept";
}
String actionId = side.select("Diff.ApplyLeftSide", "Diff.ApplyRightSide");
Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(actionId);
String shortcutsText = StringUtil.nullize(KeymapUtil.getShortcutsText(shortcuts));
String tooltipText = DiffUtil.createTooltipText(text, shortcutsText);
return createIconRenderer(side, tooltipText, icon, () -> myViewer.replaceChange(this, side));
}
@Nullable
private GutterIconRenderer createAppendRenderer(@NotNull final Side side) {
return createIconRenderer(side, "Append", DiffUtil.getArrowDownIcon(side), () -> myViewer.appendChange(this, side));
}
@Nullable
private GutterIconRenderer createIconRenderer(@NotNull final Side sourceSide,
@NotNull final String tooltipText,
@NotNull final Icon icon,
@NotNull final Runnable perform) {
if (!DiffUtil.isEditable(myViewer.getEditor(sourceSide.other()))) return null;
return new DiffGutterRenderer(icon, tooltipText) {
@Override
protected void handleMouseClick() {
if (!myIsValid) return;
final Project project = myViewer.getProject();
final Document document = myViewer.getEditor(sourceSide.other()).getDocument();
DiffUtil.executeWriteCommand(document, project, "Replace change", perform);
}
};
public void markDestroyed() {
myIsDestroyed = true;
}
}
@@ -0,0 +1,257 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diff.tools.simple;
import com.intellij.diff.fragments.DiffFragment;
import com.intellij.diff.util.*;
import com.intellij.openapi.actionSystem.Shortcut;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
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.keymap.KeymapManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
public class SimpleDiffChangeUi {
@NotNull protected final SimpleDiffViewer myViewer;
@NotNull protected final SimpleDiffChange myChange;
@NotNull protected final List<RangeHighlighter> myHighlighters = new ArrayList<>();
@NotNull protected final List<GutterOperation> myOperations = new ArrayList<>();
public SimpleDiffChangeUi(@NotNull SimpleDiffViewer viewer, @NotNull SimpleDiffChange change) {
myViewer = viewer;
myChange = change;
}
public void installHighlighter(@Nullable SimpleDiffChange previousChange) {
assert myHighlighters.isEmpty();
List<DiffFragment> innerFragments = myChange.getFragment().getInnerFragments();
if (innerFragments != null) {
doInstallHighlighterWithInner(innerFragments);
}
else {
doInstallHighlighterSimple();
}
doInstallNonSquashedChangesSeparator(previousChange);
doInstallActionHighlighters();
}
public void destroyHighlighter() {
for (RangeHighlighter highlighter : myHighlighters) {
highlighter.dispose();
}
myHighlighters.clear();
for (GutterOperation operation : myOperations) {
operation.dispose();
}
myOperations.clear();
}
private void doInstallHighlighterSimple() {
createHighlighter(Side.LEFT, false);
createHighlighter(Side.RIGHT, false);
}
private void doInstallHighlighterWithInner(@NotNull List<DiffFragment> innerFragments) {
createHighlighter(Side.LEFT, true);
createHighlighter(Side.RIGHT, true);
for (DiffFragment fragment : innerFragments) {
createInlineHighlighter(fragment, Side.LEFT);
createInlineHighlighter(fragment, Side.RIGHT);
}
}
private void doInstallNonSquashedChangesSeparator(@Nullable SimpleDiffChange previousChange) {
createNonSquashedChangesSeparator(previousChange, Side.LEFT);
createNonSquashedChangesSeparator(previousChange, Side.RIGHT);
}
protected void doInstallActionHighlighters() {
if (myChange.isSkipped()) return;
myOperations.add(new AcceptGutterOperation(Side.LEFT));
myOperations.add(new AcceptGutterOperation(Side.RIGHT));
}
private void createHighlighter(@NotNull Side side, boolean ignored) {
Editor editor = myViewer.getEditor(side);
TextDiffType type = myChange.getDiffType();
int startLine = myChange.getStartLine(side);
int endLine = myChange.getEndLine(side);
myHighlighters.addAll(new DiffDrawUtil.LineHighlighterBuilder(editor, startLine, endLine, type)
.withIgnored(ignored)
.withExcludedInEditor(myChange.isSkipped())
.withExcludedInGutter(myChange.isExcluded())
.done());
}
private void createInlineHighlighter(@NotNull DiffFragment innerFragment, @NotNull Side side) {
if (myChange.isSkipped()) return;
int start = side.getStartOffset(innerFragment);
int end = side.getEndOffset(innerFragment);
TextDiffType type = DiffUtil.getDiffType(innerFragment);
int startOffset = side.getStartOffset(myChange.getFragment());
start += startOffset;
end += startOffset;
Editor editor = myViewer.getEditor(side);
myHighlighters.addAll(DiffDrawUtil.createInlineHighlighter(editor, start, end, type));
}
private void createNonSquashedChangesSeparator(@Nullable SimpleDiffChange previousChange, @NotNull Side side) {
if (previousChange == null) return;
int startLine = myChange.getStartLine(side);
int endLine = myChange.getEndLine(side);
int prevStartLine = previousChange.getStartLine(side);
int prevEndLine = previousChange.getEndLine(side);
if (startLine == endLine) return;
if (prevStartLine == prevEndLine) return;
if (prevEndLine != startLine) return;
myHighlighters.addAll(DiffDrawUtil.createLineMarker(myViewer.getEditor(side), startLine, TextDiffType.MODIFIED));
}
public void updateGutterActions(boolean force) {
for (GutterOperation operation : myOperations) {
operation.update(force);
}
}
public void invalidate() {
for (GutterOperation operation : myOperations) {
operation.dispose();
}
myOperations.clear();
}
//
// Helpers
//
protected abstract class GutterOperation {
@NotNull protected final Side mySide;
@NotNull private final RangeHighlighter myHighlighter;
protected boolean myCtrlPressed;
public GutterOperation(@NotNull Side side) {
mySide = side;
int offset = side.getStartOffset(myChange.getFragment());
EditorEx editor = myViewer.getEditor(side);
myHighlighter = editor.getMarkupModel().addRangeHighlighter(offset, offset,
HighlighterLayer.ADDITIONAL_SYNTAX,
null,
HighlighterTargetArea.LINES_IN_RANGE);
update(true);
}
public void dispose() {
myHighlighter.dispose();
}
public void update(boolean force) {
if (!force && !areModifiersChanged()) {
return;
}
if (myHighlighter.isValid()) myHighlighter.setGutterIconRenderer(createRenderer());
}
private boolean areModifiersChanged() {
return myCtrlPressed != myViewer.getModifierProvider().isCtrlPressed();
}
@Nullable
public abstract GutterIconRenderer createRenderer();
}
private class AcceptGutterOperation extends GutterOperation {
AcceptGutterOperation(@NotNull Side side) {
super(side);
}
@Nullable
@Override
public GutterIconRenderer createRenderer() {
myCtrlPressed = myViewer.getModifierProvider().isCtrlPressed();
boolean isOtherEditable = DiffUtil.isEditable(myViewer.getEditor(mySide.other()));
boolean isAppendable = myChange.getDiffType() == TextDiffType.MODIFIED;
if (isOtherEditable) {
if (myCtrlPressed && isAppendable) {
return createAppendRenderer(mySide);
}
else {
return createApplyRenderer(mySide);
}
}
return null;
}
}
@Nullable
private GutterIconRenderer createApplyRenderer(@NotNull final Side side) {
String text;
Icon icon = DiffUtil.getArrowIcon(side);
if (side == Side.LEFT && myViewer.isDiffForLocalChanges()) {
text = "Revert";
}
else {
text = "Accept";
}
String actionId = side.select("Diff.ApplyLeftSide", "Diff.ApplyRightSide");
Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(actionId);
String shortcutsText = StringUtil.nullize(KeymapUtil.getShortcutsText(shortcuts));
String tooltipText = DiffUtil.createTooltipText(text, shortcutsText);
return createIconRenderer(side, tooltipText, icon, () -> myViewer.replaceChange(myChange, side));
}
@Nullable
private GutterIconRenderer createAppendRenderer(@NotNull final Side side) {
return createIconRenderer(side, "Append", DiffUtil.getArrowDownIcon(side), () -> myViewer.appendChange(myChange, side));
}
@Nullable
private GutterIconRenderer createIconRenderer(@NotNull final Side sourceSide,
@NotNull final String tooltipText,
@NotNull final Icon icon,
@NotNull final Runnable perform) {
if (!DiffUtil.isEditable(myViewer.getEditor(sourceSide.other()))) return null;
return new DiffGutterRenderer(icon, tooltipText) {
@Override
protected void handleMouseClick() {
if (!myChange.isValid()) return;
final Project project = myViewer.getProject();
final Document document = myViewer.getEditor(sourceSide.other()).getDocument();
DiffUtil.executeWriteCommand(document, project, "Replace change", perform);
}
};
}
}
@@ -0,0 +1,102 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.diff.tools.simple;
import com.intellij.diff.util.DiffUtil;
import com.intellij.diff.util.LineRange;
import com.intellij.diff.util.Side;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class SimpleDiffModel {
@NotNull private final SimpleDiffViewer myViewer;
@NotNull private final List<SimpleDiffChange> myValidChanges = new ArrayList<>();
@NotNull private final List<SimpleDiffChange> myAllChanges = new ArrayList<>();
@NotNull private ThreeState myIsContentsEqual = ThreeState.UNSURE;
@NotNull private final List<SimpleDiffChangeUi> myPresentations = new ArrayList<>();
public SimpleDiffModel(@NotNull SimpleDiffViewer viewer) {
myViewer = viewer;
}
@NotNull
public ThreeState isContentsEqual() {
return myIsContentsEqual;
}
@NotNull
public List<SimpleDiffChange> getChanges() {
return myValidChanges;
}
@NotNull
public List<SimpleDiffChange> getAllChanges() {
return ContainerUtil.filter(myAllChanges, it -> !it.isDestroyed());
}
public void setChanges(@NotNull List<SimpleDiffChange> changes, boolean isContentsEqual) {
clear();
for (int i = 0; i < changes.size(); i++) {
SimpleDiffChange change = changes.get(i);
SimpleDiffChange previousChange = i != 0 ? changes.get(i - 1) : null;
SimpleDiffChangeUi changeUi = myViewer.createUi(change);
changeUi.installHighlighter(previousChange);
myPresentations.add(changeUi);
}
myValidChanges.addAll(changes);
myAllChanges.addAll(changes);
myIsContentsEqual = ThreeState.fromBoolean(isContentsEqual);
}
public void clear() {
for (SimpleDiffChangeUi changeUi : myPresentations) {
changeUi.destroyHighlighter();
}
myValidChanges.clear();
myAllChanges.clear();
myPresentations.clear();
myIsContentsEqual = ThreeState.UNSURE;
}
public void destroyChange(@NotNull SimpleDiffChange change) {
SimpleDiffChangeUi changeUi = myPresentations.set(change.getIndex(), null);
if (changeUi != null) changeUi.destroyHighlighter();
myValidChanges.remove(change);
change.markDestroyed();
}
public void updateGutterActions(boolean force) {
for (SimpleDiffChangeUi changeUi : myPresentations) {
changeUi.updateGutterActions(force);
}
}
public void handleBeforeDocumentChange(@NotNull Side side, @NotNull DocumentEvent e) {
if (myValidChanges.isEmpty()) return;
LineRange lineRange = DiffUtil.getAffectedLineRange(e);
int shift = DiffUtil.countLinesShift(e);
List<SimpleDiffChange> invalidated = new ArrayList<>();
for (SimpleDiffChange change : myValidChanges) {
if (change.processDocumentChange(lineRange.start, lineRange.end, shift, side)) {
invalidated.add(change);
SimpleDiffChangeUi changeUi = myPresentations.get(change.getIndex());
if (changeUi != null) changeUi.invalidate();
}
}
myValidChanges.removeAll(invalidated);
}
}
@@ -42,6 +42,7 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.*;
@@ -59,9 +60,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
@NotNull private final PrevNextDifferenceIterable myPrevNextDifferenceIterable;
@NotNull private final StatusPanel myStatusPanel;
@NotNull private final List<SimpleDiffChange> myDiffChanges = new ArrayList<>();
@NotNull private final List<SimpleDiffChange> myInvalidDiffChanges = new ArrayList<>();
private boolean myIsContentsEqual;
@NotNull private final SimpleDiffModel myModel = new SimpleDiffModel(this);
@NotNull private final MyFoldingModel myFoldingModel;
@NotNull private final MyInitialScrollHelper myInitialScrollHelper = new MyInitialScrollHelper();
@@ -97,7 +96,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
@Override
@CalledInAwt
protected void onDispose() {
destroyChangedBlocks();
myModel.clear();
myFoldingModel.destroy();
super.onDispose();
}
@@ -161,6 +160,11 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
myInitialScrollHelper.updateContext(myRequest);
}
@NotNull
protected SimpleDiffChangeUi createUi(@NotNull SimpleDiffChange change) {
return new SimpleDiffChangeUi(this, change);
}
//
// Diff
//
@@ -197,7 +201,10 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
return apply(null, isContentsEqual);
}
else {
List<SimpleDiffChange> changes = ContainerUtil.map(lineFragments, fragment -> new SimpleDiffChange(this, fragment));
List<SimpleDiffChange> changes = new ArrayList<>();
for (LineFragment fragment : lineFragments) {
changes.add(new SimpleDiffChange(changes.size(), fragment));
}
return apply(changes, isContentsEqual);
}
}
@@ -224,22 +231,13 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
clearDiffPresentation();
myIsContentsEqual = isContentsEqual;
if (isContentsEqual) {
boolean equalCharsets = TextDiffViewerUtil.areEqualCharsets(getContents());
boolean equalSeparators = TextDiffViewerUtil.areEqualLineSeparators(getContents());
myPanel.addNotification(DiffNotifications.createEqualContents(equalCharsets, equalSeparators));
}
if (changes != null) {
for (int i = 0; i < changes.size(); i++) {
SimpleDiffChange change = changes.get(i);
LineFragment previousChangeFragment = i != 0 ? changes.get(i - 1).getFragment() : null;
change.installHighlighter(previousChangeFragment);
}
myDiffChanges.addAll(changes);
}
myModel.setChanges(ContainerUtil.notNullize(changes), isContentsEqual);
myFoldingModel.install(foldingState, myRequest, getFoldingModelSettings());
@@ -260,37 +258,23 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
}
private void clearDiffPresentation() {
myStatusPanel.setBusy(false);
myModel.clear();
myPanel.resetNotifications();
destroyChangedBlocks();
myStatusPanel.setBusy(false);
myContentPanel.repaintDivider();
myStatusPanel.update();
}
//
// Impl
//
private void destroyChangedBlocks() {
myIsContentsEqual = false;
for (SimpleDiffChange change : myDiffChanges) {
change.destroyHighlighter();
}
myDiffChanges.clear();
for (SimpleDiffChange change : myInvalidDiffChanges) {
change.destroyHighlighter();
}
myInvalidDiffChanges.clear();
myContentPanel.repaintDivider();
myStatusPanel.update();
}
@Override
@CalledInAwt
protected void onBeforeDocumentChange(@NotNull DocumentEvent e) {
super.onBeforeDocumentChange(e);
if (myDiffChanges.isEmpty()) return;
List<Document> documents = ContainerUtil.map(getEditors(), Editor::getDocument);
Side side = Side.fromValue(documents, e.getDocument());
@@ -299,26 +283,13 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
return;
}
LineRange lineRange = DiffUtil.getAffectedLineRange(e);
int shift = DiffUtil.countLinesShift(e);
List<SimpleDiffChange> invalid = new ArrayList<>();
for (SimpleDiffChange change : myDiffChanges) {
if (change.processChange(lineRange.start, lineRange.end, shift, side)) {
invalid.add(change);
}
}
if (!invalid.isEmpty()) {
myDiffChanges.removeAll(invalid);
myInvalidDiffChanges.addAll(invalid);
}
myModel.handleBeforeDocumentChange(side, e);
}
@CalledInAwt
protected boolean doScrollToChange(@NotNull ScrollToPolicy scrollToPolicy) {
SimpleDiffChange targetChange = scrollToPolicy.select(getNonSkippedDiffChanges());
if (targetChange == null) targetChange = scrollToPolicy.select(myDiffChanges);
if (targetChange == null) targetChange = scrollToPolicy.select(getDiffChanges());
if (targetChange == null) return false;
doScrollToChange(targetChange, false);
@@ -358,12 +329,12 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
@NotNull
protected List<SimpleDiffChange> getDiffChanges() {
return myDiffChanges;
return myModel.getChanges();
}
@NotNull
private List<SimpleDiffChange> getNonSkippedDiffChanges() {
return ContainerUtil.filter(myDiffChanges, it -> !it.isSkipped());
return ContainerUtil.filter(myModel.getChanges(), it -> !it.isSkipped());
}
@NotNull
@@ -409,10 +380,11 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
}
protected boolean isSomeChangeSelected(@NotNull Side side) {
if (myDiffChanges.isEmpty()) return false;
if (getDiffChanges().isEmpty()) return false;
EditorEx editor = getEditor(side);
return DiffUtil.isSomeRangeSelected(editor, lines -> ContainerUtil.exists(myDiffChanges, change -> isChangeSelected(change, lines, side)));
return DiffUtil.isSomeRangeSelected(editor, lines ->
ContainerUtil.exists(getDiffChanges(), change -> isChangeSelected(change, lines, side)));
}
@NotNull
@@ -420,7 +392,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
protected List<SimpleDiffChange> getSelectedChanges(@NotNull Side side) {
EditorEx editor = getEditor(side);
BitSet lines = DiffUtil.getSelectedLines(editor);
return ContainerUtil.filter(myDiffChanges, change -> isChangeSelected(change, lines, side));
return ContainerUtil.filter(getDiffChanges(), change -> isChangeSelected(change, lines, side));
}
private static boolean isChangeSelected(SimpleDiffChange change, @NotNull BitSet lines, @NotNull Side side) {
@@ -434,7 +406,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
protected SimpleDiffChange getSelectedChange(@NotNull Side side) {
int caretLine = getEditor(side).getCaretModel().getLogicalPosition().line;
for (SimpleDiffChange change : myDiffChanges) {
for (SimpleDiffChange change : getDiffChanges()) {
int line1 = change.getStartLine(side);
int line2 = change.getEndLine(side);
@@ -484,9 +456,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
@Override
protected void doApply(boolean readOnly) {
super.doApply(readOnly);
for (SimpleDiffChange change : myDiffChanges) {
change.updateGutterActions(true);
}
myModel.updateGutterActions(true);
}
}
@@ -631,8 +601,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
DiffUtil.applyModification(getEditor(outputSide).getDocument(), change.getStartLine(outputSide), change.getEndLine(outputSide),
getEditor(sourceSide).getDocument(), change.getStartLine(sourceSide), change.getEndLine(sourceSide));
change.destroyHighlighter();
myDiffChanges.remove(change);
myModel.destroyChange(change);
}
@CalledWithWriteLock
@@ -644,8 +613,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
DiffUtil.applyModification(getEditor(outputSide).getDocument(), change.getEndLine(outputSide), change.getEndLine(outputSide),
getEditor(sourceSide).getDocument(), change.getStartLine(sourceSide), change.getEndLine(sourceSide));
change.destroyHighlighter();
myDiffChanges.remove(change);
myModel.destroyChange(change);
}
private class MyToggleExpandByDefaultAction extends TextDiffViewerUtil.ToggleExpandByDefaultAction {
@@ -672,12 +640,12 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
@Override
public boolean hasNextBlock() {
return myIndex < myDiffChanges.size();
return myIndex < getDiffChanges().size();
}
@Override
public void loadNextBlock() {
SimpleDiffChange change = myDiffChanges.get(myIndex);
SimpleDiffChange change = getDiffChanges().get(myIndex);
myIndex++;
int line1 = change.getStartLine(Side.RIGHT);
@@ -723,14 +691,14 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
@NotNull
@Override
public Range getRange(@NotNull Side baseSide, int line) {
if (myDiffChanges.isEmpty()) return idRange(line);
if (getDiffChanges().isEmpty()) return idRange(line);
return super.getRange(baseSide, line);
}
@Override
protected void processHelper(@NotNull ScrollHelper helper) {
if (!helper.process(0, 0)) return;
for (SimpleDiffChange diffChange : myDiffChanges) {
for (SimpleDiffChange diffChange : getDiffChanges()) {
if (!helper.process(diffChange.getStartLine(Side.LEFT), diffChange.getStartLine(Side.RIGHT))) return;
if (!helper.process(diffChange.getEndLine(Side.LEFT), diffChange.getEndLine(Side.RIGHT))) return;
}
@@ -755,7 +723,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
@Override
public void process(@NotNull Handler handler) {
for (SimpleDiffChange diffChange : myDiffChanges) {
for (SimpleDiffChange diffChange : getDiffChanges()) {
if (!handler.processExcludable(diffChange.getStartLine(Side.LEFT), diffChange.getEndLine(Side.LEFT),
diffChange.getStartLine(Side.RIGHT), diffChange.getEndLine(Side.RIGHT),
getEditor1(), diffChange.getDiffType(), diffChange.isExcluded())) {
@@ -772,10 +740,10 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
if (myTextDiffProvider.isHighlightingDisabled()) {
return DiffBundle.message("diff.highlighting.disabled.text");
}
int excludedChanges = ContainerUtil.count(myDiffChanges, it -> it.isExcluded()) +
ContainerUtil.count(myInvalidDiffChanges, it -> it.isExcluded());
int changesCount = myDiffChanges.size() + myInvalidDiffChanges.size();
if (changesCount == 0 && !myIsContentsEqual) {
List<SimpleDiffChange> allChanges = myModel.getAllChanges();
int excludedChanges = ContainerUtil.count(allChanges, it -> it.isExcluded());
int changesCount = allChanges.size();
if (changesCount == 0 && myModel.isContentsEqual() == ThreeState.NO) {
return DiffBundle.message("diff.all.differences.ignored.text");
}
String message = DiffBundle.message("diff.count.differences.status.text", changesCount - excludedChanges);
@@ -791,9 +759,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
@Override
public void onModifiersChanged() {
for (SimpleDiffChange change : myDiffChanges) {
change.updateGutterActions(false);
}
myModel.updateGutterActions(false);
}
}
@@ -5,6 +5,7 @@ import com.intellij.diff.DiffContext;
import com.intellij.diff.comparison.DiffTooBigException;
import com.intellij.diff.fragments.LineFragment;
import com.intellij.diff.tools.simple.SimpleDiffChange;
import com.intellij.diff.tools.simple.SimpleDiffChangeUi;
import com.intellij.diff.tools.simple.SimpleDiffViewer;
import com.intellij.diff.tools.util.DiffNotifications;
import com.intellij.diff.util.DiffGutterRenderer;
@@ -114,6 +115,13 @@ public class SimpleLocalChangeListDiffViewer extends SimpleDiffViewer {
return group;
}
@NotNull
@Override
protected SimpleDiffChangeUi createUi(@NotNull SimpleDiffChange change) {
if (change instanceof MySimpleDiffChange) return new MySimpleDiffChangeUi(this, (MySimpleDiffChange)change);
return super.createUi(change);
}
@Override
@NotNull
protected Runnable performRediff(@NotNull final ProgressIndicator indicator) {
@@ -218,7 +226,10 @@ public class SimpleLocalChangeListDiffViewer extends SimpleDiffViewer {
boolean isSkipped = !isFromActiveChangelist;
boolean isExcluded = !isFromActiveChangelist || (myAllowExcludeChangesFromCommit && isExcludedFromCommit);
changes.addAll(ContainerUtil.map(rangeFragments, fragment -> new MySimpleDiffChange(fragment, isExcluded, isSkipped, localRange.getChangelistId(), isExcludedFromCommit)));
for (LineFragment fragment : rangeFragments) {
changes.add(new MySimpleDiffChange(changes.size(), fragment, isExcluded, isSkipped,
localRange.getChangelistId(), isFromActiveChangelist, isExcludedFromCommit));
}
}
return apply(changes, isContentsEqual);
@@ -230,37 +241,57 @@ public class SimpleLocalChangeListDiffViewer extends SimpleDiffViewer {
myExcludeAllCheckboxPanel.refresh();
}
private class MySimpleDiffChange extends SimpleDiffChange {
private static class MySimpleDiffChange extends SimpleDiffChange {
@NotNull private final String myChangelistId;
private final boolean myIsFromActiveChangelist;
private final boolean myIsExcludedFromCommit;
MySimpleDiffChange(@NotNull LineFragment fragment,
boolean isExcluded,
boolean isSkipped,
@NotNull String changelistId,
boolean isExcludedFromCommit) {
super(SimpleLocalChangeListDiffViewer.this, fragment, isExcluded, isSkipped);
MySimpleDiffChange(int index,
@NotNull LineFragment fragment,
boolean isExcluded,
boolean isSkipped,
@NotNull String changelistId,
boolean isFromActiveChangelist,
boolean isExcludedFromCommit) {
super(index, fragment, isExcluded, isSkipped);
myChangelistId = changelistId;
myIsFromActiveChangelist = isFromActiveChangelist;
myIsExcludedFromCommit = isExcludedFromCommit;
}
@NotNull
public String getChangelistId() {
return myChangelistId;
}
public boolean isFromActiveChangelist() {
return myIsFromActiveChangelist;
}
public boolean isExcludedFromCommit() {
return myIsExcludedFromCommit;
}
}
private static class MySimpleDiffChangeUi extends SimpleDiffChangeUi {
private MySimpleDiffChangeUi(@NotNull SimpleLocalChangeListDiffViewer viewer, @NotNull MySimpleDiffChange change) {
super(viewer, change);
}
@NotNull
private SimpleLocalChangeListDiffViewer getViewer() {
return (SimpleLocalChangeListDiffViewer)myViewer;
}
public boolean isFromActiveChangelist() {
return myChangelistId.equals(getViewer().myChangelistId);
}
public boolean isExcludedFromCommit() {
return myIsExcludedFromCommit;
@NotNull
private MySimpleDiffChange getChange() {
return ((MySimpleDiffChange)myChange);
}
@Override
protected void doInstallActionHighlighters() {
super.doInstallActionHighlighters();
if (myAllowExcludeChangesFromCommit && isFromActiveChangelist()) {
if (getViewer().myAllowExcludeChangesFromCommit && getChange().isFromActiveChangelist()) {
myOperations.add(new ExcludeGutterOperation());
}
}
@@ -272,20 +303,21 @@ public class SimpleLocalChangeListDiffViewer extends SimpleDiffViewer {
@Override
public GutterIconRenderer createRenderer() {
if (!isFromActiveChangelist()) return null;
if (!getChange().isFromActiveChangelist()) return null;
Icon icon = myIsExcludedFromCommit ? AllIcons.Diff.GutterCheckBox : AllIcons.Diff.GutterCheckBoxSelected;
final boolean isExcludedFromCommit = getChange().isExcludedFromCommit();
Icon icon = isExcludedFromCommit ? AllIcons.Diff.GutterCheckBox : AllIcons.Diff.GutterCheckBoxSelected;
return new DiffGutterRenderer(icon, "Include into commit") {
@Override
protected void handleMouseClick() {
if (!isValid()) return;
if (!myChange.isValid()) return;
PartialLocalLineStatusTracker tracker = getViewer().getPartialTracker();
if (tracker == null) return;
LocalRange range = tracker.getRangeForLine(getStartLine(Side.RIGHT));
LocalRange range = tracker.getRangeForLine(myChange.getStartLine(Side.RIGHT));
if (range == null) return;
tracker.setExcludedFromCommit(range, !myIsExcludedFromCommit);
tracker.setExcludedFromCommit(range, !isExcludedFromCommit);
getViewer().rediff();
}