/*
* Copyright 2000-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.diff.tools.simple;
import com.intellij.diff.DiffContext;
import com.intellij.diff.comparison.ComparisonManager;
import com.intellij.diff.comparison.ComparisonPolicy;
import com.intellij.diff.comparison.DiffTooBigException;
import com.intellij.diff.contents.DiffContent;
import com.intellij.diff.contents.DocumentContent;
import com.intellij.diff.fragments.MergeLineFragment;
import com.intellij.diff.requests.ContentDiffRequest;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.diff.tools.util.DiffNotifications;
import com.intellij.diff.tools.util.base.HighlightPolicy;
import com.intellij.diff.tools.util.base.IgnorePolicy;
import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
import com.intellij.diff.tools.util.side.ThreesideTextDiffViewer;
import com.intellij.diff.util.*;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnSeparator;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import consulo.annotations.RequiredDispatchThread;
import java.util.ArrayList;
import java.util.List;
public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewerEx {
public static final Logger LOG = Logger.getInstance(SimpleThreesideDiffViewer.class);
@NotNull private final List<SimpleThreesideDiffChange> myDiffChanges = new ArrayList<>();
@NotNull private final List<SimpleThreesideDiffChange> myInvalidDiffChanges = new ArrayList<>();
public SimpleThreesideDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
super(context, (ContentDiffRequest)request);
}
@NotNull
@Override
protected List<AnAction> createToolbarActions() {
List<AnAction> group = new ArrayList<>();
group.add(new MyIgnorePolicySettingAction());
group.add(new MyHighlightPolicySettingAction());
group.add(new MyToggleExpandByDefaultAction());
group.add(new MyToggleAutoScrollAction());
group.add(new MyEditorReadOnlyLockAction());
group.add(myEditorSettingsAction);
group.add(AnSeparator.getInstance());
group.add(new TextShowPartialDiffAction(PartialDiffMode.LEFT_BASE));
group.add(new TextShowPartialDiffAction(PartialDiffMode.BASE_RIGHT));
group.add(new TextShowPartialDiffAction(PartialDiffMode.LEFT_RIGHT));
group.add(AnSeparator.getInstance());
group.addAll(super.createToolbarActions());
return group;
}
@NotNull
@Override
protected List<AnAction> createPopupActions() {
List<AnAction> group = new ArrayList<>();
group.add(AnSeparator.getInstance());
group.add(new MyIgnorePolicySettingAction().getPopupGroup());
//group.add(Separator.getInstance());
//group.add(new MyHighlightPolicySettingAction().getPopupGroup());
group.add(AnSeparator.getInstance());
group.add(new MyToggleAutoScrollAction());
group.add(new MyToggleExpandByDefaultAction());
group.add(AnSeparator.getInstance());
group.addAll(super.createPopupActions());
return group;
}
//
// Diff
//
@Override
protected void onSlowRediff() {
super.onSlowRediff();
myStatusPanel.setBusy(true);
myInitialScrollHelper.onSlowRediff();
}
@Override
@NotNull
protected Runnable performRediff(@NotNull final ProgressIndicator indicator) {
try {
indicator.checkCanceled();
final List<DiffContent> contents = myRequest.getContents();
final List<Document> documents = ContainerUtil.map(contents, content -> ((DocumentContent)content).getDocument());
final List<CharSequence> sequences = ReadAction.compute(() -> {
indicator.checkCanceled();
return ContainerUtil.map(documents, Document::getImmutableCharSequence);
});
final ComparisonPolicy comparisonPolicy = getIgnorePolicy().getComparisonPolicy();
ComparisonManager manager = ComparisonManager.getInstance();
List<MergeLineFragment> lineFragments = manager.compareLines(sequences.get(0), sequences.get(1), sequences.get(2),
comparisonPolicy, indicator);
List<MergeConflictType> conflictTypes = ReadAction.compute(() -> {
indicator.checkCanceled();
return ContainerUtil.map(lineFragments, (fragment) -> DiffUtil.getLineMergeType(fragment, documents, comparisonPolicy));
});
List<MergeInnerDifferences> innerFragments = null;
if (getHighlightPolicy().isFineFragments()) {
innerFragments = new ArrayList<>(lineFragments.size());
for (int i = 0; i < lineFragments.size(); i++) {
final MergeLineFragment fragment = lineFragments.get(i);
final MergeConflictType conflictType = conflictTypes.get(i);
List<CharSequence> chunks = ReadAction.compute(() -> {
indicator.checkCanceled();
return ThreeSide.map(side -> {
if (!conflictType.isChange(side)) return null;
return getChunkContent(fragment, documents, side);
});
});
innerFragments.add(DiffUtil.compareThreesideInner(chunks, comparisonPolicy, indicator));
}
}
return apply(lineFragments, conflictTypes, innerFragments);
}
catch (DiffTooBigException e) {
return applyNotification(DiffNotifications.createDiffTooBig());
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Throwable e) {
LOG.error(e);
return applyNotification(DiffNotifications.createError());
}
}
@Nullable
private static CharSequence getChunkContent(@NotNull MergeLineFragment fragment,
@NotNull List<Document> documents,
@NotNull ThreeSide side) {
int startLine = fragment.getStartLine(side);
int endLine = fragment.getEndLine(side);
return startLine != endLine ? DiffUtil.getLinesContent(side.select(documents), startLine, endLine) : null;
}
@NotNull
private Runnable apply(@NotNull final List<MergeLineFragment> fragments,
@NotNull final List<MergeConflictType> conflictTypes,
@Nullable final List<MergeInnerDifferences> innerDifferences) {
return () -> {
myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
clearDiffPresentation();
resetChangeCounters();
for (int i = 0; i < fragments.size(); i++) {
MergeLineFragment fragment = fragments.get(i);
MergeConflictType conflictType = conflictTypes.get(i);
MergeInnerDifferences innerFragments = innerDifferences != null ? innerDifferences.get(i) : null;
SimpleThreesideDiffChange change = new SimpleThreesideDiffChange(fragment, conflictType, innerFragments, this);
myDiffChanges.add(change);
onChangeAdded(change);
}
myFoldingModel.install(fragments, myRequest, getFoldingModelSettings());
myInitialScrollHelper.onRediff();
myContentPanel.repaintDividers();
myStatusPanel.update();
};
}
@Override
@RequiredDispatchThread
protected void destroyChangedBlocks() {
super.destroyChangedBlocks();
for (SimpleThreesideDiffChange change : myDiffChanges) {
change.destroy();
}
myDiffChanges.clear();
for (SimpleThreesideDiffChange change : myInvalidDiffChanges) {
change.destroy();
}
myInvalidDiffChanges.clear();
}
//
// Impl
//
@Override
@RequiredDispatchThread
protected void onBeforeDocumentChange(@NotNull DocumentEvent e) {
super.onBeforeDocumentChange(e);
if (myDiffChanges.isEmpty()) return;
List<Document> documents = ContainerUtil.map(getEditors(), Editor::getDocument);
ThreeSide side = ThreeSide.fromValue(documents, e.getDocument());
if (side == null) {
LOG.warn("Unknown document changed");
return;
}
LineRange lineRange = DiffUtil.getAffectedLineRange(e);
int shift = DiffUtil.countLinesShift(e);
List<SimpleThreesideDiffChange> invalid = new ArrayList<>();
for (SimpleThreesideDiffChange change : myDiffChanges) {
if (change.processChange(lineRange.start, lineRange.end, shift, side)) {
invalid.add(change);
}
}
if (!invalid.isEmpty()) {
myDiffChanges.removeAll(invalid);
myInvalidDiffChanges.addAll(invalid);
}
}
@NotNull
private IgnorePolicy getIgnorePolicy() {
IgnorePolicy policy = getTextSettings().getIgnorePolicy();
if (policy == IgnorePolicy.IGNORE_WHITESPACES_CHUNKS) return IgnorePolicy.IGNORE_WHITESPACES;
return policy;
}
@NotNull
private HighlightPolicy getHighlightPolicy() {
HighlightPolicy policy = getTextSettings().getHighlightPolicy();
if (policy == HighlightPolicy.BY_WORD_SPLIT) return HighlightPolicy.BY_WORD;
if (policy == HighlightPolicy.DO_NOT_HIGHLIGHT) return HighlightPolicy.BY_LINE;
return policy;
}
//
// Getters
//
@NotNull
@Override
public List<SimpleThreesideDiffChange> getChanges() {
return myDiffChanges;
}
@NotNull
@Override
protected DiffDividerDrawUtil.DividerPaintable getDividerPaintable(@NotNull Side side) {
return new MyDividerPaintable(side);
}
//
// Misc
//
@SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
public static boolean canShowRequest(@NotNull DiffContext context, @NotNull DiffRequest request) {
return ThreesideTextDiffViewer.canShowRequest(context, request);
}
//
// Actions
//
private class MyIgnorePolicySettingAction extends TextDiffViewerUtil.IgnorePolicySettingAction {
public MyIgnorePolicySettingAction() {
super(getTextSettings());
}
@NotNull
@Override
protected IgnorePolicy getCurrentSetting() {
return getIgnorePolicy();
}
@NotNull
@Override
protected List<IgnorePolicy> getAvailableSettings() {
ArrayList<IgnorePolicy> settings = ContainerUtil.newArrayList(IgnorePolicy.values());
settings.remove(IgnorePolicy.IGNORE_WHITESPACES_CHUNKS);
return settings;
}
@Override
protected void onSettingsChanged() {
rediff();
}
}
private class MyHighlightPolicySettingAction extends TextDiffViewerUtil.HighlightPolicySettingAction {
public MyHighlightPolicySettingAction() {
super(getTextSettings());
}
@NotNull
@Override
protected HighlightPolicy getCurrentSetting() {
return getHighlightPolicy();
}
@NotNull
@Override
protected List<HighlightPolicy> getAvailableSettings() {
return ContainerUtil.list(HighlightPolicy.BY_LINE, HighlightPolicy.BY_WORD);
}
@Override
protected void onSettingsChanged() {
rediff();
}
}
protected class MyEditorReadOnlyLockAction extends TextDiffViewerUtil.EditorReadOnlyLockAction {
public MyEditorReadOnlyLockAction() {
super(getContext(), getEditableEditors());
}
}
//
// Helpers
//
private class MyDividerPaintable implements DiffDividerDrawUtil.DividerPaintable {
@NotNull private final Side mySide;
public MyDividerPaintable(@NotNull Side side) {
mySide = side;
}
@Override
public void process(@NotNull Handler handler) {
ThreeSide left = mySide.select(ThreeSide.LEFT, ThreeSide.BASE);
ThreeSide right = mySide.select(ThreeSide.BASE, ThreeSide.RIGHT);
for (SimpleThreesideDiffChange diffChange : myDiffChanges) {
if (!diffChange.isChange(mySide)) continue;
if (!handler.process(diffChange.getStartLine(left), diffChange.getEndLine(left),
diffChange.getStartLine(right), diffChange.getEndLine(right),
diffChange.getDiffType().getColor(getEditor(ThreeSide.BASE)))) {
return;
}
}
}
}
}