diff: extract LST-managed diff computation logic

GitOrigin-RevId: e4b2003e7bf7cbbd7c919e1dfff5a93b2d3331c0
This commit is contained in:
Aleksey Pivovarov
2019-08-12 18:05:04 +03:00
committed by intellij-monorepo-bot
parent 72458eea71
commit 55b8eada71
3 changed files with 221 additions and 156 deletions
@@ -185,28 +185,7 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
@NotNull
protected Runnable performRediff(@NotNull final ProgressIndicator indicator) {
try {
indicator.checkCanceled();
final Document document1 = getContent1().getDocument();
final Document document2 = getContent2().getDocument();
CharSequence[] texts = ReadAction.compute(() -> new CharSequence[]{document1.getImmutableCharSequence(), document2.getImmutableCharSequence()});
List<LineFragment> lineFragments = myTextDiffProvider.compare(texts[0], texts[1], indicator);
boolean isContentsEqual = (lineFragments == null || lineFragments.isEmpty()) &&
StringUtil.equals(texts[0], texts[1]);
if (lineFragments == null) {
return apply(null, isContentsEqual);
}
else {
List<SimpleDiffChange> changes = new ArrayList<>();
for (LineFragment fragment : lineFragments) {
changes.add(new SimpleDiffChange(changes.size(), fragment));
}
return apply(changes, isContentsEqual);
}
return computeDifferences(indicator);
}
catch (DiffTooBigException e) {
return applyNotification(DiffNotifications.createDiffTooBig());
@@ -220,6 +199,33 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
}
}
@NotNull
protected Runnable computeDifferences(@NotNull ProgressIndicator indicator) {
indicator.checkCanceled();
final Document document1 = getContent1().getDocument();
final Document document2 = getContent2().getDocument();
CharSequence[] texts =
ReadAction.compute(() -> new CharSequence[]{document1.getImmutableCharSequence(), document2.getImmutableCharSequence()});
List<LineFragment> lineFragments = myTextDiffProvider.compare(texts[0], texts[1], indicator);
boolean isContentsEqual = (lineFragments == null || lineFragments.isEmpty()) &&
StringUtil.equals(texts[0], texts[1]);
if (lineFragments == null) {
return apply(null, isContentsEqual);
}
else {
List<SimpleDiffChange> changes = new ArrayList<>();
for (LineFragment fragment : lineFragments) {
changes.add(new SimpleDiffChange(changes.size(), fragment));
}
return apply(changes, isContentsEqual);
}
}
@NotNull
protected Runnable apply(@Nullable List<SimpleDiffChange> changes,
boolean isContentsEqual) {
@@ -0,0 +1,127 @@
// 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.openapi.vcs.changes.actions.diff.lst
import com.intellij.diff.fragments.LineFragment
import com.intellij.diff.tools.util.text.TwosideTextDiffProvider
import com.intellij.diff.util.Range
import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.editor.Document
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vcs.ex.LineStatusTracker
import com.intellij.openapi.vcs.ex.LocalRange
import com.intellij.openapi.vcs.ex.PartialLocalLineStatusTracker
import com.intellij.openapi.vcs.ex.SimpleLocalLineStatusTracker
object LocalTrackerDiffUtil {
@JvmStatic
fun computeDifferences(tracker: LineStatusTracker<*>?,
document1: Document,
document2: Document,
changelistId: String,
textDiffProvider: TwosideTextDiffProvider,
indicator: ProgressIndicator,
handler: LocalTrackerDiffHandler): Runnable {
if (tracker is SimpleLocalLineStatusTracker) {
// partial changes are disabled for file (ex: it is marked as "unmodified")
return handler.fallback()
}
val partialTracker = tracker as? PartialLocalLineStatusTracker
if (partialTracker == null || document2 != tracker.document) {
return handler.error() // DiffRequest is out of date
}
indicator.checkCanceled()
val data = runReadAction {
partialTracker.readLock {
val isReleased = partialTracker.isReleased
val isOperational = partialTracker.isOperational()
val affectedChangelistIds = partialTracker.getAffectedChangeListsIds()
if (!isOperational) {
TrackerData(isReleased, affectedChangelistIds, null)
}
else {
val ranges = partialTracker.getRanges()
val localText = document2.immutableCharSequence
val vcsText = document1.immutableCharSequence
val trackerVcsText = partialTracker.vcsDocument.immutableCharSequence
val diffData = TrackerDiffData(ranges, localText, vcsText, trackerVcsText)
TrackerData(isReleased, affectedChangelistIds, diffData)
}
}
}
if (data.isReleased) {
return handler.error() // DiffRequest is out of date
}
val diffData = data.diffData
if (diffData?.ranges == null) {
if (data.affectedChangelist.size == 1 && data.affectedChangelist.contains(changelistId)) {
// tracker is waiting for initialisation
// there are only one changelist, so it's safe to fallback to default logic
return handler.fallbackWithProgress()
}
return handler.retryLater()
}
val ranges = diffData.ranges
val isContentsEqual = ranges.isEmpty()
if (!StringUtil.equals(diffData.vcsText, diffData.trackerVcsText)) {
return handler.error() // DiffRequest is out of date
}
if (textDiffProvider.isHighlightingDisabled) {
return handler.done(isContentsEqual, emptyList(), emptyList())
}
val linesRanges = ranges.map { range -> Range(range.vcsLine1, range.vcsLine2, range.line1, range.line2) }
val newFragments = textDiffProvider.compare(diffData.vcsText, diffData.localText, linesRanges, indicator)!!
val fragments = mutableListOf<LineFragment>()
val fragmentsData = mutableListOf<LineFragmentData>()
for (i in ranges.indices) {
val localRange = ranges[i]
val rangeFragments = newFragments[i]
fragments.addAll(rangeFragments)
val fragmentData = LineFragmentData(localRange.isExcludedFromCommit, localRange.changelistId)
repeat(rangeFragments.size) { fragmentsData.add(fragmentData) }
}
return handler.done(isContentsEqual, fragments, fragmentsData)
}
interface LocalTrackerDiffHandler {
fun done(isContentsEqual: Boolean,
fragments: List<LineFragment>,
fragmentsData: List<LineFragmentData>): Runnable
fun retryLater(): Runnable
fun fallback(): Runnable
fun fallbackWithProgress(): Runnable
fun error(): Runnable
}
data class LineFragmentData(val isExcluded: Boolean,
val changelistId: String)
private data class TrackerData(val isReleased: Boolean,
val affectedChangelist: List<String>,
val diffData: TrackerDiffData?)
private data class TrackerDiffData(val ranges: List<LocalRange>?,
val localText: CharSequence,
val vcsText: CharSequence,
val trackerVcsText: CharSequence)
}
@@ -2,7 +2,6 @@
package com.intellij.openapi.vcs.changes.actions.diff.lst;
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;
@@ -10,7 +9,6 @@ import com.intellij.diff.tools.simple.SimpleDiffViewer;
import com.intellij.diff.tools.util.DiffNotifications;
import com.intellij.diff.util.DiffGutterRenderer;
import com.intellij.diff.util.DiffUtil;
import com.intellij.diff.util.Range;
import com.intellij.diff.util.Side;
import com.intellij.icons.AllIcons;
import com.intellij.idea.ActionsBundle;
@@ -21,7 +19,6 @@ import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
@@ -50,8 +47,6 @@ import java.awt.event.ComponentEvent;
import java.util.List;
import java.util.*;
import static com.intellij.util.ObjectUtils.notNull;
public class SimpleLocalChangeListDiffViewer extends SimpleDiffViewer {
@NotNull private final LocalChangeListDiffRequest myLocalRequest;
@NotNull private final String myChangelistId;
@@ -122,117 +117,85 @@ public class SimpleLocalChangeListDiffViewer extends SimpleDiffViewer {
return super.createUi(change);
}
@Override
@NotNull
protected Runnable performRediff(@NotNull final ProgressIndicator indicator) {
LineStatusTracker tracker = myLocalRequest.getLineStatusTracker();
if (tracker instanceof SimpleLocalLineStatusTracker) {
// partial changes are disabled for file (ex: it is marked as "unmodified")
return super.performRediff(indicator);
}
PartialLocalLineStatusTracker partialTracker = ObjectUtils.tryCast(tracker, PartialLocalLineStatusTracker.class);
if (partialTracker == null || getContent2().getDocument() != tracker.getDocument()) {
return applyNotification(DiffNotifications.createError()); // DiffRequest is out of date
}
try {
indicator.checkCanceled();
TrackerData data = ReadAction.compute(() -> partialTracker.readLock(() -> {
boolean isReleased = partialTracker.isReleased();
boolean isOperational = partialTracker.isOperational();
List<String> affectedChangelistIds = partialTracker.getAffectedChangeListsIds();
if (!isOperational) {
return new TrackerData(isReleased, affectedChangelistIds, null);
}
List<LocalRange> ranges = partialTracker.getRanges();
CharSequence localText = getContent2().getDocument().getImmutableCharSequence();
CharSequence vcsText = getContent1().getDocument().getImmutableCharSequence();
CharSequence trackerVcsText = partialTracker.getVcsDocument().getImmutableCharSequence();
TrackerDiffData diffData = new TrackerDiffData(ranges, localText, vcsText, trackerVcsText);
return new TrackerData(isReleased, affectedChangelistIds, diffData);
}));
if (data.isReleased) {
return applyNotification(DiffNotifications.createError()); // DiffRequest is out of date
}
TrackerDiffData diffData = data.diffData;
if (diffData == null || diffData.ranges == null) {
if (data.affectedChangelist.size() == 1 &&
data.affectedChangelist.contains(myChangelistId)) {
// tracker is waiting for initialisation
// there are only one changelist, so it's safe to fallback to default logic
Runnable callback = super.performRediff(indicator);
return () -> {
callback.run();
getStatusPanel().setBusy(true);
};
}
scheduleRediff();
throw new ProcessCanceledException();
}
return performRediffUsingPartialTracker(diffData.ranges, diffData.localText, diffData.vcsText, diffData.trackerVcsText, indicator);
}
catch (DiffTooBigException e) {
return applyNotification(DiffNotifications.createDiffTooBig());
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Throwable e) {
LOG.error(e);
return applyNotification(DiffNotifications.createError());
}
private Runnable superComputeDifferences(@NotNull ProgressIndicator indicator) {
return super.computeDifferences(indicator);
}
@Override
@NotNull
private Runnable performRediffUsingPartialTracker(@NotNull List<LocalRange> ranges,
@NotNull CharSequence localText,
@NotNull CharSequence vcsText,
@NotNull CharSequence trackerVcsText,
@NotNull ProgressIndicator indicator) {
if (!StringUtil.equals(trackerVcsText, vcsText)) {
return applyNotification(DiffNotifications.createError()); // DiffRequest is out of date
protected Runnable computeDifferences(@NotNull ProgressIndicator indicator) {
return LocalTrackerDiffUtil.computeDifferences(
myLocalRequest.getLineStatusTracker(),
getContent1().getDocument(),
getContent2().getDocument(),
myChangelistId,
myTextDiffProvider,
indicator,
new MyLocalTrackerDiffHandler(indicator)
);
}
private class MyLocalTrackerDiffHandler implements LocalTrackerDiffUtil.LocalTrackerDiffHandler {
@NotNull private final ProgressIndicator myIndicator;
private MyLocalTrackerDiffHandler(@NotNull ProgressIndicator indicator) {
myIndicator = indicator;
}
if (myTextDiffProvider.isHighlightingDisabled()) {
return apply(null, ranges.isEmpty());
}
@NotNull
@Override
public Runnable done(boolean isContentsEqual,
@NotNull List<? extends LineFragment> fragments,
@NotNull List<LocalTrackerDiffUtil.LineFragmentData> fragmentsData) {
List<SimpleDiffChange> changes = new ArrayList<>();
for (int i = 0; i < fragments.size(); i++) {
LineFragment fragment = fragments.get(i);
LocalTrackerDiffUtil.LineFragmentData data = fragmentsData.get(i);
List<Range> linesRanges = ContainerUtil.map(ranges, range -> new Range(range.getVcsLine1(), range.getVcsLine2(), range.getLine1(), range.getLine2()));
boolean isExcludedFromCommit = data.isExcluded();
boolean isFromActiveChangelist = data.getChangelistId().equals(myChangelistId);
boolean isSkipped = !isFromActiveChangelist;
boolean isExcluded = !isFromActiveChangelist ||
(myAllowExcludeChangesFromCommit && isExcludedFromCommit);
List<List<LineFragment>> newFragments = notNull(myTextDiffProvider.compare(vcsText, localText, linesRanges, indicator));
boolean isContentsEqual = ranges.isEmpty();
List<SimpleDiffChange> changes = new ArrayList<>();
for (int i = 0; i < ranges.size(); i++) {
LocalRange localRange = ranges.get(i);
List<LineFragment> rangeFragments = newFragments.get(i);
boolean isExcludedFromCommit = localRange.isExcludedFromCommit();
boolean isFromActiveChangelist = localRange.getChangelistId().equals(myChangelistId);
boolean isSkipped = !isFromActiveChangelist;
boolean isExcluded = !isFromActiveChangelist || (myAllowExcludeChangesFromCommit && isExcludedFromCommit);
for (LineFragment fragment : rangeFragments) {
changes.add(new MySimpleDiffChange(changes.size(), fragment, isExcluded, isSkipped,
localRange.getChangelistId(), isFromActiveChangelist, isExcludedFromCommit));
data.getChangelistId(), isFromActiveChangelist,
isExcludedFromCommit));
}
return apply(changes, isContentsEqual);
}
return apply(changes, isContentsEqual);
@NotNull
@Override
public Runnable retryLater() {
scheduleRediff();
throw new ProcessCanceledException();
}
@NotNull
@Override
public Runnable fallback() {
return superComputeDifferences(myIndicator);
}
@NotNull
@Override
public Runnable fallbackWithProgress() {
Runnable callback = superComputeDifferences(myIndicator);
return () -> {
callback.run();
getStatusPanel().setBusy(true);
};
}
@NotNull
@Override
public Runnable error() {
return applyNotification(DiffNotifications.createError());
}
}
@Override
@@ -606,35 +569,4 @@ public class SimpleLocalChangeListDiffViewer extends SimpleDiffViewer {
}
}
}
private static class TrackerData {
private final boolean isReleased;
@NotNull public final List<String> affectedChangelist;
@Nullable public final TrackerDiffData diffData;
TrackerData(boolean isReleased,
@NotNull List<String> affectedChangelist,
@Nullable TrackerDiffData diffData) {
this.isReleased = isReleased;
this.affectedChangelist = affectedChangelist;
this.diffData = diffData;
}
}
private static class TrackerDiffData {
@Nullable public final List<LocalRange> ranges;
@NotNull public final CharSequence localText;
@NotNull public final CharSequence vcsText;
@NotNull public final CharSequence trackerVcsText;
TrackerDiffData(@Nullable List<LocalRange> ranges,
@NotNull CharSequence localText,
@NotNull CharSequence vcsText,
@NotNull CharSequence trackerVcsText) {
this.ranges = ranges;
this.localText = localText;
this.vcsText = vcsText;
this.trackerVcsText = trackerVcsText;
}
}
}