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.merge;
18 import com.intellij.diff.comparison.ComparisonPolicy;
19 import com.intellij.diff.fragments.MergeLineFragment;
20 import com.intellij.diff.tools.simple.ThreesideDiffChangeBase;
21 import com.intellij.diff.util.*;
22 import com.intellij.diff.util.DiffUtil.UpdatedLineRange;
23 import com.intellij.icons.AllIcons;
24 import com.intellij.openapi.actionSystem.AnAction;
25 import com.intellij.openapi.actionSystem.AnActionEvent;
26 import com.intellij.openapi.diff.DiffBundle;
27 import com.intellij.openapi.editor.Document;
28 import com.intellij.openapi.editor.Editor;
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.util.containers.ContainerUtil;
33 import org.jetbrains.annotations.CalledInAwt;
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 TextMergeChange extends ThreesideDiffChangeBase {
42 @NotNull private final TextMergeTool.TextMergeViewer myMergeViewer;
43 @NotNull private final TextMergeTool.TextMergeViewer.MyThreesideViewer myViewer;
44 @NotNull private final List<RangeHighlighter> myHighlighters = new ArrayList<RangeHighlighter>();
46 @NotNull private final List<MyGutterOperation> myOperations = new ArrayList<MyGutterOperation>();
48 private int[] myStartLines = new int[3];
49 private int[] myEndLines = new int[3];
50 private final boolean[] myResolved = new boolean[2];
53 public TextMergeChange(@NotNull MergeLineFragment fragment, @NotNull TextMergeTool.TextMergeViewer viewer) {
54 super(fragment, viewer.getViewer().getEditors(), ComparisonPolicy.DEFAULT);
55 myMergeViewer = viewer;
56 myViewer = viewer.getViewer();
58 for (ThreeSide side : ThreeSide.values()) {
59 myStartLines[side.getIndex()] = fragment.getStartLine(side);
60 myEndLines[side.getIndex()] = fragment.getEndLine(side);
66 protected void installHighlighter() {
67 assert myHighlighters.isEmpty();
69 createHighlighter(ThreeSide.BASE);
70 if (getType().isLeftChange()) createHighlighter(ThreeSide.LEFT);
71 if (getType().isRightChange()) createHighlighter(ThreeSide.RIGHT);
73 doInstallActionHighlighters();
77 public void destroyHighlighter() {
78 for (RangeHighlighter highlighter : myHighlighters) {
79 highlighter.dispose();
81 myHighlighters.clear();
83 for (MyGutterOperation operation : myOperations) {
90 public void doReinstallHighlighter() {
93 myViewer.repaintDividers();
96 private void createHighlighter(@NotNull ThreeSide side) {
97 Editor editor = side.select(myViewer.getEditors());
98 Document document = editor.getDocument();
100 TextDiffType type = getDiffType();
101 boolean resolved = isResolved(side);
102 int startLine = getStartLine(side);
103 int endLine = getEndLine(side);
107 if (startLine == endLine) {
108 start = end = startLine < DiffUtil.getLineCount(document) ? document.getLineStartOffset(startLine) : document.getTextLength();
111 start = document.getLineStartOffset(startLine);
112 end = document.getLineEndOffset(endLine - 1);
113 if (end < document.getTextLength()) end++;
116 myHighlighters.add(DiffDrawUtil.createHighlighter(editor, start, end, type, false, HighlighterTargetArea.EXACT_RANGE, resolved));
118 if (startLine == endLine) {
119 if (startLine != 0) {
120 myHighlighters.add(DiffDrawUtil.createLineMarker(editor, endLine - 1, type, SeparatorPlacement.BOTTOM, true, resolved));
124 myHighlighters.add(DiffDrawUtil.createLineMarker(editor, startLine, type, SeparatorPlacement.TOP, false, resolved));
125 myHighlighters.add(DiffDrawUtil.createLineMarker(editor, endLine - 1, type, SeparatorPlacement.BOTTOM, false, resolved));
134 public void markResolved() {
135 if (isResolved()) return;
136 myResolved[0] = true;
137 myResolved[1] = true;
138 myViewer.onChangeResolved(this);
139 myViewer.reinstallHighlighter(this);
143 public void markResolved(@NotNull Side side) {
144 if (isResolved(side)) return;
145 myResolved[side.getIndex()] = true;
146 if (isResolved()) myViewer.onChangeResolved(this);
147 myViewer.reinstallHighlighter(this);
150 public boolean isResolved() {
151 return myResolved[0] && myResolved[1];
154 public boolean isResolved(@NotNull Side side) {
155 return side.select(myResolved);
158 public boolean isResolved(@NotNull ThreeSide side) {
161 return isResolved(Side.LEFT);
165 return isResolved(Side.RIGHT);
167 throw new IllegalArgumentException(side.toString());
171 public int getStartLine(@NotNull ThreeSide side) {
172 return side.select(myStartLines);
175 public int getEndLine(@NotNull ThreeSide side) {
176 return side.select(myEndLines);
179 public void setStartLine(@NotNull ThreeSide side, int value) {
180 myStartLines[side.getIndex()] = value;
183 public void setEndLine(@NotNull ThreeSide side, int value) {
184 myEndLines[side.getIndex()] = value;
191 public boolean processBaseChange(int oldLine1, int oldLine2, int shift) {
192 int line1 = getStartLine(ThreeSide.BASE);
193 int line2 = getEndLine(ThreeSide.BASE);
194 int baseIndex = ThreeSide.BASE.getIndex();
196 UpdatedLineRange newRange = DiffUtil.updateRangeOnModification(line1, line2, oldLine1, oldLine2, shift);
197 myStartLines[baseIndex] = newRange.startLine;
198 myEndLines[baseIndex] = newRange.endLine;
200 boolean rangeAffected = oldLine2 >= line1 && oldLine1 <= line2; // RangeMarker can be updated in a different way
202 if (newRange.startLine == newRange.endLine && getDiffType() == TextDiffType.DELETED) {
206 return newRange.damaged || rangeAffected;
213 private void doInstallActionHighlighters() {
214 ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.LEFT));
215 ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.BASE));
216 ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.RIGHT));
220 private MyGutterOperation createOperation(@NotNull ThreeSide side) {
221 if (isResolved(side)) return null;
223 EditorEx editor = myViewer.getEditor(side);
224 Document document = editor.getDocument();
226 int line = getStartLine(side);
227 int offset = line == DiffUtil.getLineCount(document) ? document.getTextLength() : document.getLineStartOffset(line);
229 RangeHighlighter highlighter = editor.getMarkupModel().addRangeHighlighter(offset, offset,
230 HighlighterLayer.ADDITIONAL_SYNTAX,
232 HighlighterTargetArea.LINES_IN_RANGE);
233 return new MyGutterOperation(side, highlighter);
236 public void updateGutterActions(boolean force) {
237 for (MyGutterOperation operation : myOperations) {
238 operation.update(force);
242 private class MyGutterOperation {
243 @NotNull private final ThreeSide mySide;
244 @NotNull private final RangeHighlighter myHighlighter;
246 private boolean myCtrlPressed;
247 private boolean myShiftPressed;
249 private MyGutterOperation(@NotNull ThreeSide side, @NotNull RangeHighlighter highlighter) {
251 myHighlighter = highlighter;
256 public void dispose() {
257 myHighlighter.dispose();
260 public void update(boolean force) {
261 if (!force && !areModifiersChanged()) {
264 if (myHighlighter.isValid()) myHighlighter.setGutterIconRenderer(createRenderer());
267 private boolean areModifiersChanged() {
268 return myCtrlPressed != myViewer.getModifierProvider().isCtrlPressed() ||
269 myShiftPressed != myViewer.getModifierProvider().isShiftPressed();
273 public GutterIconRenderer createRenderer() {
274 myCtrlPressed = myViewer.getModifierProvider().isCtrlPressed();
275 myShiftPressed = myViewer.getModifierProvider().isShiftPressed();
277 if (mySide == ThreeSide.BASE) {
278 return createRevertRenderer();
281 Side versionSide = mySide.select(Side.LEFT, null, Side.RIGHT);
282 assert versionSide != null;
284 if (!isChange(versionSide)) return null;
286 boolean isAppendable = getStartLine(mySide) != getEndLine(mySide) &&
287 (getStartLine(ThreeSide.BASE) != getEndLine(ThreeSide.BASE) || isConflict());
289 if (myShiftPressed) {
290 return createRevertRenderer();
292 if (myCtrlPressed && isAppendable) {
293 return createAppendRenderer(versionSide);
295 return createApplyRenderer(versionSide);
301 private GutterIconRenderer createApplyRenderer(@NotNull final Side side) {
302 return createIconRenderer(DiffBundle.message("merge.dialog.apply.change.action.name"), AllIcons.Diff.Arrow, new Runnable() {
305 final Document document = myViewer.getEditor(ThreeSide.BASE).getDocument();
306 DiffUtil.executeWriteCommand(document, myViewer.getProject(), "Apply change", new Runnable() {
309 myViewer.replaceChange(TextMergeChange.this, side);
317 private GutterIconRenderer createAppendRenderer(@NotNull final Side side) {
318 return createIconRenderer(DiffBundle.message("merge.dialog.append.change.action.name"), AllIcons.Diff.ArrowLeftDown, new Runnable() {
321 final Document document = myViewer.getEditor(ThreeSide.BASE).getDocument();
322 DiffUtil.executeWriteCommand(document, myViewer.getProject(), "Apply change", new Runnable() {
325 myViewer.appendChange(TextMergeChange.this, side);
333 private GutterIconRenderer createRevertRenderer() {
334 return createIconRenderer(DiffBundle.message("merge.dialog.ignore.change.action.name"), AllIcons.Diff.Remove, new Runnable() {
343 private GutterIconRenderer createIconRenderer(@NotNull final String tooltipText,
344 @NotNull final Icon icon,
345 @NotNull final Runnable perform) {
346 return new GutterIconRenderer() {
349 public Icon getIcon() {
353 public boolean isNavigateAction() {
359 public AnAction getClickAction() {
360 return new DumbAwareAction() {
362 public void actionPerformed(AnActionEvent e) {
369 public boolean equals(Object obj) {
374 public int hashCode() {
375 return System.identityHashCode(this);
380 public String getTooltipText() {