diff: add annotate action to diff viewers
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / tools / simple / ThreesideDiffChangeBase.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.diff.tools.simple;
17
18 import com.intellij.diff.comparison.ComparisonManager;
19 import com.intellij.diff.comparison.ComparisonPolicy;
20 import com.intellij.diff.fragments.MergeLineFragment;
21 import com.intellij.diff.util.DiffUtil;
22 import com.intellij.diff.util.Side;
23 import com.intellij.diff.util.TextDiffType;
24 import com.intellij.diff.util.ThreeSide;
25 import com.intellij.openapi.editor.ex.DocumentEx;
26 import com.intellij.openapi.editor.ex.EditorEx;
27 import com.intellij.openapi.util.text.StringUtil;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.util.List;
32
33 public abstract class ThreesideDiffChangeBase {
34   @NotNull private ConflictType myType;
35
36   public ThreesideDiffChangeBase(@NotNull MergeLineFragment fragment,
37                                  @NotNull List<? extends EditorEx> editors,
38                                  @NotNull ComparisonPolicy policy) {
39     myType = calcType(fragment, editors, policy);
40   }
41
42   //
43   // Getters
44   //
45
46   public abstract int getStartLine(@NotNull ThreeSide side);
47
48   public abstract int getEndLine(@NotNull ThreeSide side);
49
50   @NotNull
51   public TextDiffType getDiffType() {
52     return myType.getDiffType();
53   }
54
55   @NotNull
56   public ConflictType getType() {
57     return myType;
58   }
59
60   public boolean isConflict() {
61     return getDiffType() == TextDiffType.CONFLICT;
62   }
63
64   public boolean isChange(@NotNull Side side) {
65     return myType.isChange(side);
66   }
67
68   //
69   // Type
70   //
71
72   @NotNull
73   private static ConflictType calcType(@NotNull MergeLineFragment fragment,
74                                        @NotNull List<? extends EditorEx> editors,
75                                        @NotNull ComparisonPolicy policy) {
76     boolean isLeftEmpty = isIntervalEmpty(fragment, ThreeSide.LEFT);
77     boolean isBaseEmpty = isIntervalEmpty(fragment, ThreeSide.BASE);
78     boolean isRightEmpty = isIntervalEmpty(fragment, ThreeSide.RIGHT);
79     assert !isLeftEmpty || !isBaseEmpty || !isRightEmpty;
80
81     if (isBaseEmpty) {
82       if (isLeftEmpty) { // --=
83         return new ConflictType(TextDiffType.INSERTED, false, true);
84       }
85       else if (isRightEmpty) { // =--
86         return new ConflictType(TextDiffType.INSERTED, true, false);
87       }
88       else { // =-=
89         boolean equalModifications = compareLeftAndRight(fragment, editors, policy);
90         return new ConflictType(equalModifications ? TextDiffType.INSERTED : TextDiffType.CONFLICT);
91       }
92     }
93     else {
94       if (isLeftEmpty && isRightEmpty) { // -=-
95         return new ConflictType(TextDiffType.DELETED);
96       }
97       else { // -==, ==-, ===
98         boolean unchangedLeft = compareWithBase(fragment, editors, ThreeSide.LEFT);
99         boolean unchangedRight = compareWithBase(fragment, editors, ThreeSide.RIGHT);
100         assert !unchangedLeft || !unchangedRight;
101
102         if (unchangedLeft) return new ConflictType(isRightEmpty ? TextDiffType.DELETED : TextDiffType.MODIFIED, false, true);
103         if (unchangedRight) return new ConflictType(isLeftEmpty ? TextDiffType.DELETED : TextDiffType.MODIFIED, true, false);
104
105         boolean equalModifications = compareLeftAndRight(fragment, editors, policy);
106         return new ConflictType(equalModifications ? TextDiffType.MODIFIED : TextDiffType.CONFLICT);
107       }
108     }
109   }
110
111   private static boolean compareLeftAndRight(@NotNull MergeLineFragment fragment,
112                                              @NotNull List<? extends EditorEx> editors,
113                                              @NotNull ComparisonPolicy policy) {
114     CharSequence content1 = getRangeContent(fragment, editors, ThreeSide.LEFT);
115     CharSequence content2 = getRangeContent(fragment, editors, ThreeSide.RIGHT);
116
117     if (policy == ComparisonPolicy.IGNORE_WHITESPACES) {
118       if (content1 == null) content1 = "";
119       if (content2 == null) content2 = "";
120     }
121
122     if (content1 == null && content2 == null) return true;
123     if (content1 == null ^ content2 == null) return false;
124
125     return ComparisonManager.getInstance().isEquals(content1, content2, policy);
126   }
127
128   private static boolean compareWithBase(@NotNull MergeLineFragment fragment,
129                                          @NotNull List<? extends EditorEx> editors,
130                                          @NotNull ThreeSide side) {
131     CharSequence content1 = getRangeContent(fragment, editors, ThreeSide.BASE);
132     CharSequence content2 = getRangeContent(fragment, editors, side);
133
134     return StringUtil.equals(content1, content2);
135   }
136
137   @Nullable
138   private static CharSequence getRangeContent(@NotNull MergeLineFragment fragment,
139                                               @NotNull List<? extends EditorEx> editors,
140                                               @NotNull ThreeSide side) {
141     DocumentEx document = side.select(editors).getDocument();
142     int line1 = fragment.getStartLine(side);
143     int line2 = fragment.getEndLine(side);
144     if (line1 == line2) return null;
145     return DiffUtil.getLinesContent(document, line1, line2);
146   }
147
148   private static boolean isIntervalEmpty(@NotNull MergeLineFragment fragment, @NotNull ThreeSide side) {
149     return fragment.getStartLine(side) == fragment.getEndLine(side);
150   }
151
152   //
153   // Helpers
154   //
155
156   public static class ConflictType {
157     @NotNull private final TextDiffType myType;
158     private final boolean myLeftChange;
159     private final boolean myRightChange;
160
161     public ConflictType(@NotNull TextDiffType type) {
162       this(type, true, true);
163     }
164
165     public ConflictType(@NotNull TextDiffType type, boolean leftChange, boolean rightChange) {
166       myType = type;
167       myLeftChange = leftChange;
168       myRightChange = rightChange;
169     }
170
171     @NotNull
172     public TextDiffType getDiffType() {
173       return myType;
174     }
175
176     public boolean isLeftChange() {
177       return myLeftChange;
178     }
179
180     public boolean isRightChange() {
181       return myRightChange;
182     }
183
184     public boolean isChange(@NotNull Side side) {
185       return side.isLeft() ? myLeftChange : myRightChange;
186     }
187   }
188 }