/*
* 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.fragmented;
import com.intellij.diff.fragments.LineFragment;
import com.intellij.diff.util.*;
import com.intellij.diff.util.DiffUtil.UpdatedLineRange;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.editor.Document;
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.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
public class UnifiedDiffChange {
@NotNull private final UnifiedDiffViewer myViewer;
@NotNull private final EditorEx myEditor;
// Boundaries of this change in myEditor. If current state is out-of-date - approximate value.
private int myLine1;
private int myLine2;
@NotNull private final LineFragment myLineFragment;
@NotNull private final List<RangeHighlighter> myHighlighters = new ArrayList<>();
@NotNull private final List<MyGutterOperation> myOperations = new ArrayList<>();
public UnifiedDiffChange(@NotNull UnifiedDiffViewer viewer, @NotNull ChangedBlock block) {
myViewer = viewer;
myEditor = viewer.getEditor();
myLine1 = block.getLine1();
myLine2 = block.getLine2();
myLineFragment = block.getLineFragment();
LineRange deleted = block.getRange1();
LineRange inserted = block.getRange2();
installHighlighter(deleted, inserted);
}
public void destroyHighlighter() {
for (RangeHighlighter highlighter : myHighlighters) {
highlighter.dispose();
}
myHighlighters.clear();
for (MyGutterOperation operation : myOperations) {
operation.dispose();
}
myOperations.clear();
}
private void installHighlighter(@NotNull LineRange deleted, @NotNull LineRange inserted) {
assert myHighlighters.isEmpty();
doInstallHighlighters(deleted, inserted);
doInstallActionHighlighters();
}
private void doInstallActionHighlighters() {
boolean leftEditable = myViewer.isEditable(Side.LEFT, false);
boolean rightEditable = myViewer.isEditable(Side.RIGHT, false);
if (leftEditable && rightEditable) {
myOperations.add(createOperation(Side.LEFT));
myOperations.add(createOperation(Side.RIGHT));
}
else if (rightEditable) {
myOperations.add(createOperation(Side.LEFT));
}
}
private void doInstallHighlighters(@NotNull LineRange deleted, @NotNull LineRange inserted) {
myHighlighters.addAll(DiffDrawUtil.createUnifiedChunkHighlighters(myEditor, deleted, inserted, myLineFragment.getInnerFragments()));
}
public int getLine1() {
return myLine1;
}
public int getLine2() {
return myLine2;
}
/*
* Warning: It does not updated on document change. Check myViewer.isStateInconsistent() before use.
*/
@NotNull
public LineFragment getLineFragment() {
return myLineFragment;
}
public void processChange(int oldLine1, int oldLine2, int shift) {
UpdatedLineRange newRange = DiffUtil.updateRangeOnModification(myLine1, myLine2, oldLine1, oldLine2, shift);
myLine1 = newRange.startLine;
myLine2 = newRange.endLine;
}
//
// Gutter
//
public void updateGutterActions() {
for (MyGutterOperation operation : myOperations) {
operation.update();
}
}
@NotNull
private MyGutterOperation createOperation(@NotNull Side sourceSide) {
int offset = myEditor.getDocument().getLineStartOffset(myLine1);
RangeHighlighter highlighter = myEditor.getMarkupModel().addRangeHighlighter(offset, offset,
HighlighterLayer.ADDITIONAL_SYNTAX,
null,
HighlighterTargetArea.LINES_IN_RANGE);
return new MyGutterOperation(sourceSide, highlighter);
}
private class MyGutterOperation {
@NotNull private final Side mySide;
@NotNull private final RangeHighlighter myHighlighter;
private MyGutterOperation(@NotNull Side sourceSide, @NotNull RangeHighlighter highlighter) {
mySide = sourceSide;
myHighlighter = highlighter;
update();
}
public void dispose() {
myHighlighter.dispose();
}
public void update() {
if (myHighlighter.isValid()) myHighlighter.setGutterIconRenderer(createRenderer());
}
@Nullable
public GutterIconRenderer createRenderer() {
if (myViewer.isStateIsOutOfDate()) return null;
if (!myViewer.isEditable(mySide.other(), true)) return null;
if (mySide.isLeft()) {
return createIconRenderer(mySide, "Revert", AllIcons.Diff.Remove);
}
else {
return createIconRenderer(mySide, "Accept", AllIcons.Actions.Checked);
}
}
}
@Nullable
private GutterIconRenderer createIconRenderer(@NotNull final Side sourceSide,
@NotNull final String tooltipText,
@NotNull final Icon icon) {
return new DiffGutterRenderer(icon, tooltipText) {
@Override
protected void performAction(AnActionEvent e) {
if (myViewer.isStateIsOutOfDate()) return;
if (!myViewer.isEditable(sourceSide.other(), true)) return;
final Project project = e.getProject();
final Document document = myViewer.getDocument(sourceSide.other());
DiffUtil.executeWriteCommand(document, project, "Replace change", () -> {
myViewer.replaceChange(UnifiedDiffChange.this, sourceSide);
myViewer.scheduleRediff();
});
// applyChange() will schedule rediff, but we want to try to do it in sync
// and we can't do it inside write action
myViewer.rediff();
}
};
}
}