2 * Copyright 2000-2015 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.diff.tools.fragmented;
18 import com.intellij.diff.fragments.DiffFragment;
19 import com.intellij.diff.fragments.LineFragment;
20 import com.intellij.diff.util.DiffDrawUtil;
21 import com.intellij.diff.util.DiffUtil;
22 import com.intellij.diff.util.DiffUtil.UpdatedLineRange;
23 import com.intellij.diff.util.Side;
24 import com.intellij.diff.util.TextDiffType;
25 import com.intellij.icons.AllIcons;
26 import com.intellij.openapi.actionSystem.AnAction;
27 import com.intellij.openapi.actionSystem.AnActionEvent;
28 import com.intellij.openapi.editor.Document;
29 import com.intellij.openapi.editor.ex.EditorEx;
30 import com.intellij.openapi.editor.markup.*;
31 import com.intellij.openapi.project.DumbAwareAction;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.util.TextRange;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
38 import java.util.ArrayList;
39 import java.util.List;
41 public class UnifiedDiffChange {
42 @NotNull private final UnifiedDiffViewer myViewer;
43 @NotNull private final EditorEx myEditor;
45 // Boundaries of this change in myEditor. If current state is out-of-date - approximate value.
49 @NotNull private final LineFragment myLineFragment;
51 @NotNull private final List<RangeHighlighter> myHighlighters = new ArrayList<RangeHighlighter>();
52 @NotNull private final List<MyGutterOperation> myOperations = new ArrayList<MyGutterOperation>();
54 public UnifiedDiffChange(@NotNull UnifiedDiffViewer viewer, @NotNull ChangedBlock block, boolean innerFragments) {
56 myEditor = viewer.getEditor();
58 myLine1 = block.getLine1();
59 myLine2 = block.getLine2();
60 myLineFragment = block.getLineFragment();
62 TextRange deleted = new TextRange(block.getStartOffset1(), block.getEndOffset1());
63 TextRange inserted = new TextRange(block.getStartOffset2(), block.getEndOffset2());
65 installHighlighter(deleted, inserted, innerFragments);
68 public void destroyHighlighter() {
69 for (RangeHighlighter highlighter : myHighlighters) {
70 highlighter.dispose();
72 myHighlighters.clear();
74 for (MyGutterOperation operation : myOperations) {
80 private void installHighlighter(@NotNull TextRange deleted, @NotNull TextRange inserted, boolean innerFragments) {
81 assert myHighlighters.isEmpty();
83 if (innerFragments && myLineFragment.getInnerFragments() != null) {
84 doInstallHighlighterWithInner(deleted, inserted);
87 doInstallHighlighterSimple(deleted, inserted);
89 doInstallActionHighlighters();
92 private void doInstallActionHighlighters() {
93 boolean leftEditable = myViewer.isEditable(Side.LEFT, false);
94 boolean rightEditable = myViewer.isEditable(Side.RIGHT, false);
96 if (rightEditable) myOperations.add(createOperation(Side.LEFT, false));
97 if (leftEditable) myOperations.add(createOperation(Side.RIGHT, rightEditable));
100 private void doInstallHighlighterSimple(@NotNull TextRange deleted, @NotNull TextRange inserted) {
101 createLineHighlighters(deleted, inserted, false);
104 private void doInstallHighlighterWithInner(@NotNull TextRange deleted, @NotNull TextRange inserted) {
105 List<DiffFragment> innerFragments = myLineFragment.getInnerFragments();
106 assert innerFragments != null;
108 createLineHighlighters(deleted, inserted, true);
110 for (DiffFragment fragment : innerFragments) {
111 createInlineHighlighter(TextDiffType.DELETED,
112 deleted.getStartOffset() + fragment.getStartOffset1(),
113 deleted.getStartOffset() + fragment.getEndOffset1());
114 createInlineHighlighter(TextDiffType.INSERTED,
115 inserted.getStartOffset() + fragment.getStartOffset2(),
116 inserted.getStartOffset() + fragment.getEndOffset2());
120 private void createLineHighlighters(@NotNull TextRange deleted, @NotNull TextRange inserted, boolean ignored) {
121 if (!inserted.isEmpty() && !deleted.isEmpty()) {
122 createLineMarker(TextDiffType.DELETED, getLine1(), SeparatorPlacement.TOP);
123 createHighlighter(TextDiffType.DELETED, deleted.getStartOffset(), deleted.getEndOffset(), ignored);
124 createHighlighter(TextDiffType.INSERTED, inserted.getStartOffset(), inserted.getEndOffset(), ignored);
125 createLineMarker(TextDiffType.INSERTED, getLine2() - 1, SeparatorPlacement.BOTTOM);
127 else if (!inserted.isEmpty()) {
128 createLineMarker(TextDiffType.INSERTED, getLine1(), SeparatorPlacement.TOP);
129 createHighlighter(TextDiffType.INSERTED, inserted.getStartOffset(), inserted.getEndOffset(), ignored);
130 createLineMarker(TextDiffType.INSERTED, getLine2() - 1, SeparatorPlacement.BOTTOM);
132 else if (!deleted.isEmpty()) {
133 createLineMarker(TextDiffType.DELETED, getLine1(), SeparatorPlacement.TOP);
134 createHighlighter(TextDiffType.DELETED, deleted.getStartOffset(), deleted.getEndOffset(), ignored);
135 createLineMarker(TextDiffType.DELETED, getLine2() - 1, SeparatorPlacement.BOTTOM);
139 private void createHighlighter(@NotNull TextDiffType type, int start, int end, boolean ignored) {
140 myHighlighters.addAll(DiffDrawUtil.createHighlighter(myEditor, start, end, type, ignored));
143 private void createInlineHighlighter(@NotNull TextDiffType type, int start, int end) {
144 myHighlighters.addAll(DiffDrawUtil.createInlineHighlighter(myEditor, start, end, type));
147 private void createLineMarker(@NotNull TextDiffType type, int line, @NotNull SeparatorPlacement placement) {
148 myHighlighters.addAll(DiffDrawUtil.createLineMarker(myEditor, line, type, placement));
151 public int getLine1() {
155 public int getLine2() {
160 * Warning: It does not updated on document change. Check myViewer.isStateInconsistent() before use.
163 public LineFragment getLineFragment() {
164 return myLineFragment;
167 public void processChange(int oldLine1, int oldLine2, int shift) {
168 UpdatedLineRange newRange = DiffUtil.updateRangeOnModification(myLine1, myLine2, oldLine1, oldLine2, shift);
169 myLine1 = newRange.startLine;
170 myLine2 = newRange.endLine;
177 public void updateGutterActions() {
178 for (MyGutterOperation operation : myOperations) {
184 private MyGutterOperation createOperation(@NotNull Side side, boolean secondAction) {
185 int line = secondAction ? Math.min(myLine1 + 1, myLine2 - 1) : myLine1;
186 int offset = myEditor.getDocument().getLineStartOffset(line);
188 RangeHighlighter highlighter = myEditor.getMarkupModel().addRangeHighlighter(offset, offset,
189 HighlighterLayer.ADDITIONAL_SYNTAX,
191 HighlighterTargetArea.LINES_IN_RANGE);
192 return new MyGutterOperation(side, highlighter);
195 private class MyGutterOperation {
196 @NotNull private final Side mySide;
197 @NotNull private final RangeHighlighter myHighlighter;
199 private MyGutterOperation(@NotNull Side sourceSide, @NotNull RangeHighlighter highlighter) {
201 myHighlighter = highlighter;
206 public void dispose() {
207 myHighlighter.dispose();
210 public void update() {
211 if (myHighlighter.isValid()) myHighlighter.setGutterIconRenderer(createRenderer());
215 public GutterIconRenderer createRenderer() {
216 if (myViewer.isStateIsOutOfDate()) return null;
217 if (!myViewer.isEditable(mySide.other(), true)) return null;
218 boolean bothEditable = myViewer.isEditable(mySide, true);
221 if (mySide.isLeft()) {
222 return createIconRenderer(mySide, "Apply Before", AllIcons.Diff.ArrowRight);
225 return createIconRenderer(mySide, "Apply After", AllIcons.Diff.Arrow);
229 if (mySide.isLeft()) {
230 return createIconRenderer(mySide, "Revert", AllIcons.Diff.Remove);
233 return createIconRenderer(mySide, "Apply", AllIcons.Diff.Arrow);
240 private GutterIconRenderer createIconRenderer(@NotNull final Side sourceSide,
241 @NotNull final String tooltipText,
242 @NotNull final Icon icon) {
243 return new GutterIconRenderer() {
246 public Icon getIcon() {
250 public boolean isNavigateAction() {
256 public AnAction getClickAction() {
257 return new DumbAwareAction() {
259 public void actionPerformed(AnActionEvent e) {
260 if (myViewer.isStateIsOutOfDate()) return;
261 if (!myViewer.isEditable(sourceSide.other(), true)) return;
263 final Project project = e.getProject();
264 final Document document = myViewer.getDocument(sourceSide.other());
266 DiffUtil.executeWriteCommand(document, project, "Replace change", new Runnable() {
269 myViewer.replaceChange(UnifiedDiffChange.this, sourceSide);
270 myViewer.scheduleRediff();
273 // applyChange() will schedule rediff, but we want to try to do it in sync
274 // and we can't do it inside write action
281 public boolean equals(Object obj) {
286 public int hashCode() {
287 return System.identityHashCode(this);
292 public String getTooltipText() {
297 public boolean isDumbAware() {