diff: add annotate action to diff viewers
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / tools / simple / SimpleThreesideDiffViewer.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.DiffContext;
19 import com.intellij.diff.comparison.ByLine;
20 import com.intellij.diff.comparison.ComparisonMergeUtil;
21 import com.intellij.diff.comparison.ComparisonPolicy;
22 import com.intellij.diff.comparison.DiffTooBigException;
23 import com.intellij.diff.comparison.iterables.FairDiffIterable;
24 import com.intellij.diff.contents.DiffContent;
25 import com.intellij.diff.contents.DocumentContent;
26 import com.intellij.diff.fragments.MergeLineFragment;
27 import com.intellij.diff.requests.ContentDiffRequest;
28 import com.intellij.diff.requests.DiffRequest;
29 import com.intellij.diff.tools.util.DiffNotifications;
30 import com.intellij.diff.tools.util.base.IgnorePolicy;
31 import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
32 import com.intellij.diff.tools.util.side.ThreesideTextDiffViewer;
33 import com.intellij.diff.util.DiffDividerDrawUtil;
34 import com.intellij.diff.util.DiffUtil;
35 import com.intellij.diff.util.Side;
36 import com.intellij.diff.util.ThreeSide;
37 import com.intellij.openapi.actionSystem.AnAction;
38 import com.intellij.openapi.actionSystem.Separator;
39 import com.intellij.openapi.application.ApplicationManager;
40 import com.intellij.openapi.diagnostic.Logger;
41 import com.intellij.openapi.editor.Document;
42 import com.intellij.openapi.editor.event.DocumentEvent;
43 import com.intellij.openapi.progress.ProcessCanceledException;
44 import com.intellij.openapi.progress.ProgressIndicator;
45 import com.intellij.openapi.util.Computable;
46 import com.intellij.util.containers.ContainerUtil;
47 import org.jetbrains.annotations.CalledInAwt;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import java.util.ArrayList;
52 import java.util.List;
53
54 public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewerEx {
55   public static final Logger LOG = Logger.getInstance(SimpleThreesideDiffViewer.class);
56
57   @NotNull private final List<SimpleThreesideDiffChange> myDiffChanges = new ArrayList<SimpleThreesideDiffChange>();
58   @NotNull private final List<SimpleThreesideDiffChange> myInvalidDiffChanges = new ArrayList<SimpleThreesideDiffChange>();
59
60   public SimpleThreesideDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
61     super(context, (ContentDiffRequest)request);
62   }
63
64   @NotNull
65   @Override
66   protected List<AnAction> createToolbarActions() {
67     List<AnAction> group = new ArrayList<AnAction>();
68
69     group.add(new MyIgnorePolicySettingAction());
70     //group.add(new MyHighlightPolicySettingAction()); // TODO
71     group.add(new MyToggleExpandByDefaultAction());
72     group.add(new MyToggleAutoScrollAction());
73     group.add(new MyEditorReadOnlyLockAction());
74     group.add(myEditorSettingsAction);
75
76     group.add(Separator.getInstance());
77     group.add(new ShowLeftBasePartialDiffAction());
78     group.add(new ShowBaseRightPartialDiffAction());
79     group.add(new ShowLeftRightPartialDiffAction());
80
81     group.add(Separator.getInstance());
82     group.addAll(super.createToolbarActions());
83
84     return group;
85   }
86
87   @NotNull
88   @Override
89   protected List<AnAction> createPopupActions() {
90     List<AnAction> group = new ArrayList<AnAction>();
91
92     group.add(Separator.getInstance());
93     group.add(new MyIgnorePolicySettingAction().getPopupGroup());
94     //group.add(Separator.getInstance());
95     //group.add(new MyHighlightPolicySettingAction().getPopupGroup());
96     group.add(Separator.getInstance());
97     group.add(new MyToggleAutoScrollAction());
98     group.add(new MyToggleExpandByDefaultAction());
99
100     group.add(Separator.getInstance());
101     group.addAll(super.createPopupActions());
102
103     return group;
104   }
105
106   //
107   // Diff
108   //
109
110   @Override
111   protected void onSlowRediff() {
112     super.onSlowRediff();
113     myStatusPanel.setBusy(true);
114     myInitialScrollHelper.onSlowRediff();
115   }
116
117   @Override
118   @NotNull
119   protected Runnable performRediff(@NotNull final ProgressIndicator indicator) {
120     try {
121       indicator.checkCanceled();
122
123       List<DiffContent> contents = myRequest.getContents();
124       final Document[] documents = new Document[3];
125       documents[0] = ((DocumentContent)contents.get(0)).getDocument();
126       documents[1] = ((DocumentContent)contents.get(1)).getDocument();
127       documents[2] = ((DocumentContent)contents.get(2)).getDocument();
128
129       CharSequence[] sequences = ApplicationManager.getApplication().runReadAction(new Computable<CharSequence[]>() {
130         @Override
131         public CharSequence[] compute() {
132           CharSequence[] sequences = new CharSequence[3];
133           sequences[0] = documents[0].getImmutableCharSequence();
134           sequences[1] = documents[1].getImmutableCharSequence();
135           sequences[2] = documents[2].getImmutableCharSequence();
136           return sequences;
137         }
138       });
139
140       ComparisonPolicy comparisonPolicy = getIgnorePolicy().getComparisonPolicy();
141       FairDiffIterable fragments1 = ByLine.compareTwoStepFair(sequences[1], sequences[0], comparisonPolicy, indicator);
142       FairDiffIterable fragments2 = ByLine.compareTwoStepFair(sequences[1], sequences[2], comparisonPolicy, indicator);
143       List<MergeLineFragment> mergeFragments = ComparisonMergeUtil.buildFair(fragments1, fragments2, indicator);
144
145       return apply(mergeFragments, comparisonPolicy);
146     }
147     catch (DiffTooBigException e) {
148       return applyNotification(DiffNotifications.DIFF_TOO_BIG);
149     }
150     catch (ProcessCanceledException e) {
151       throw e;
152     }
153     catch (Throwable e) {
154       LOG.error(e);
155       return applyNotification(DiffNotifications.ERROR);
156     }
157   }
158
159   @NotNull
160   private Runnable apply(@NotNull final List<MergeLineFragment> fragments,
161                          @NotNull final ComparisonPolicy comparisonPolicy) {
162     return new Runnable() {
163       @Override
164       public void run() {
165         myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
166         clearDiffPresentation();
167
168         resetChangeCounters();
169         for (MergeLineFragment fragment : fragments) {
170           SimpleThreesideDiffChange change = new SimpleThreesideDiffChange(fragment, getEditors(), comparisonPolicy);
171           myDiffChanges.add(change);
172           onChangeAdded(change);
173         }
174
175         myFoldingModel.install(fragments, myRequest, getFoldingModelSettings());
176
177         myInitialScrollHelper.onRediff();
178
179         myContentPanel.repaintDividers();
180         myStatusPanel.update();
181       }
182     };
183   }
184
185   protected void destroyChangedBlocks() {
186     super.destroyChangedBlocks();
187     for (SimpleThreesideDiffChange change : myDiffChanges) {
188       change.destroyHighlighter();
189     }
190     myDiffChanges.clear();
191
192     for (SimpleThreesideDiffChange change : myInvalidDiffChanges) {
193       change.destroyHighlighter();
194     }
195     myInvalidDiffChanges.clear();
196   }
197
198   //
199   // Impl
200   //
201
202   @Override
203   @CalledInAwt
204   protected void onBeforeDocumentChange(@NotNull DocumentEvent e) {
205     super.onBeforeDocumentChange(e);
206     if (myDiffChanges.isEmpty()) return;
207
208     ThreeSide side = null;
209     if (e.getDocument() == getEditor(ThreeSide.LEFT).getDocument()) side = ThreeSide.LEFT;
210     if (e.getDocument() == getEditor(ThreeSide.RIGHT).getDocument()) side = ThreeSide.RIGHT;
211     if (e.getDocument() == getEditor(ThreeSide.BASE).getDocument()) side = ThreeSide.BASE;
212     if (side == null) {
213       LOG.warn("Unknown document changed");
214       return;
215     }
216
217     int line1 = e.getDocument().getLineNumber(e.getOffset());
218     int line2 = e.getDocument().getLineNumber(e.getOffset() + e.getOldLength()) + 1;
219     int shift = DiffUtil.countLinesShift(e);
220
221     List<SimpleThreesideDiffChange> invalid = new ArrayList<SimpleThreesideDiffChange>();
222     for (SimpleThreesideDiffChange change : myDiffChanges) {
223       if (change.processChange(line1, line2, shift, side)) {
224         invalid.add(change);
225       }
226     }
227
228     if (!invalid.isEmpty()) {
229       myDiffChanges.removeAll(invalid);
230       myInvalidDiffChanges.addAll(invalid);
231     }
232   }
233
234   @NotNull
235   private IgnorePolicy getIgnorePolicy() {
236     IgnorePolicy policy = getTextSettings().getIgnorePolicy();
237     if (policy == IgnorePolicy.IGNORE_WHITESPACES_CHUNKS) return IgnorePolicy.IGNORE_WHITESPACES;
238     return policy;
239   }
240
241   //
242   // Getters
243   //
244
245   @NotNull
246   public List<SimpleThreesideDiffChange> getChanges() {
247     return myDiffChanges;
248   }
249
250   @NotNull
251   @Override
252   protected DiffDividerDrawUtil.DividerPaintable getDividerPaintable(@NotNull Side side) {
253     return new MyDividerPaintable(side);
254   }
255
256   //
257   // Misc
258   //
259
260   @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
261   public static boolean canShowRequest(@NotNull DiffContext context, @NotNull DiffRequest request) {
262     return ThreesideTextDiffViewer.canShowRequest(context, request);
263   }
264
265   //
266   // Actions
267   //
268
269   private class MyIgnorePolicySettingAction extends TextDiffViewerUtil.IgnorePolicySettingAction {
270     public MyIgnorePolicySettingAction() {
271       super(getTextSettings());
272     }
273
274     @NotNull
275     @Override
276     protected IgnorePolicy getCurrentSetting() {
277       return getIgnorePolicy();
278     }
279
280     @NotNull
281     @Override
282     protected List<IgnorePolicy> getAvailableSettings() {
283       ArrayList<IgnorePolicy> settings = ContainerUtil.newArrayList(IgnorePolicy.values());
284       settings.remove(IgnorePolicy.IGNORE_WHITESPACES_CHUNKS);
285       return settings;
286     }
287
288     @Override
289     protected void onSettingsChanged() {
290       rediff();
291     }
292   }
293
294   protected class MyEditorReadOnlyLockAction extends TextDiffViewerUtil.EditorReadOnlyLockAction {
295     public MyEditorReadOnlyLockAction() {
296       super(getContext(), getEditableEditors());
297     }
298   }
299
300   //
301   // Helpers
302   //
303
304   private class MyDividerPaintable implements DiffDividerDrawUtil.DividerPaintable {
305     @NotNull private final Side mySide;
306
307     public MyDividerPaintable(@NotNull Side side) {
308       mySide = side;
309     }
310
311     @Override
312     public void process(@NotNull Handler handler) {
313       ThreeSide left = mySide.select(ThreeSide.LEFT, ThreeSide.BASE);
314       ThreeSide right = mySide.select(ThreeSide.BASE, ThreeSide.RIGHT);
315
316       for (SimpleThreesideDiffChange diffChange : myDiffChanges) {
317         if (!diffChange.isChange(mySide)) continue;
318         if (!handler.process(diffChange.getStartLine(left), diffChange.getEndLine(left),
319                              diffChange.getStartLine(right), diffChange.getEndLine(right),
320                              diffChange.getDiffType().getColor(getEditor(ThreeSide.BASE)))) {
321           return;
322         }
323       }
324     }
325   }
326 }