diff: add annotate action to diff viewers
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / tools / simple / SimpleDiffViewer.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.actions.BufferedLineIterator;
20 import com.intellij.diff.actions.NavigationContextChecker;
21 import com.intellij.diff.comparison.DiffTooBigException;
22 import com.intellij.diff.fragments.LineFragment;
23 import com.intellij.diff.requests.ContentDiffRequest;
24 import com.intellij.diff.requests.DiffRequest;
25 import com.intellij.diff.tools.util.*;
26 import com.intellij.diff.tools.util.base.HighlightPolicy;
27 import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
28 import com.intellij.diff.tools.util.side.TwosideTextDiffViewer;
29 import com.intellij.diff.util.*;
30 import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
31 import com.intellij.icons.AllIcons;
32 import com.intellij.openapi.Disposable;
33 import com.intellij.openapi.actionSystem.*;
34 import com.intellij.openapi.application.ApplicationManager;
35 import com.intellij.openapi.diagnostic.Logger;
36 import com.intellij.openapi.diff.DiffNavigationContext;
37 import com.intellij.openapi.editor.Caret;
38 import com.intellij.openapi.editor.Document;
39 import com.intellij.openapi.editor.Editor;
40 import com.intellij.openapi.editor.event.DocumentEvent;
41 import com.intellij.openapi.editor.ex.EditorEx;
42 import com.intellij.openapi.progress.ProcessCanceledException;
43 import com.intellij.openapi.progress.ProgressIndicator;
44 import com.intellij.openapi.project.DumbAware;
45 import com.intellij.openapi.util.Computable;
46 import com.intellij.openapi.util.Pair;
47 import com.intellij.openapi.util.UserDataHolder;
48 import com.intellij.openapi.util.text.StringUtil;
49 import com.intellij.util.Function;
50 import org.jetbrains.annotations.*;
51
52 import javax.swing.*;
53 import java.awt.*;
54 import java.util.ArrayList;
55 import java.util.BitSet;
56 import java.util.Iterator;
57 import java.util.List;
58
59 import static com.intellij.diff.util.DiffUtil.getLineCount;
60
61 public class SimpleDiffViewer extends TwosideTextDiffViewer {
62   public static final Logger LOG = Logger.getInstance(SimpleDiffViewer.class);
63
64   @NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable;
65   @NotNull private final PrevNextDifferenceIterable myPrevNextDifferenceIterable;
66   @NotNull private final StatusPanel myStatusPanel;
67
68   @NotNull private final List<SimpleDiffChange> myDiffChanges = new ArrayList<SimpleDiffChange>();
69   @NotNull private final List<SimpleDiffChange> myInvalidDiffChanges = new ArrayList<SimpleDiffChange>();
70
71   @NotNull private final MyFoldingModel myFoldingModel;
72   @NotNull private final MyInitialScrollHelper myInitialScrollHelper = new MyInitialScrollHelper();
73   @NotNull private final ModifierProvider myModifierProvider;
74
75   public SimpleDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
76     super(context, (ContentDiffRequest)request);
77
78     mySyncScrollable = new MySyncScrollable();
79     myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable();
80     myStatusPanel = new MyStatusPanel();
81     myFoldingModel = new MyFoldingModel(getEditors(), this);
82
83     myModifierProvider = new ModifierProvider();
84
85     DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.LEFT, true), myPanel);
86     DiffUtil.registerAction(new AppendSelectedChangesAction(Side.LEFT, true), myPanel);
87     DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.RIGHT, true), myPanel);
88     DiffUtil.registerAction(new AppendSelectedChangesAction(Side.RIGHT, true), myPanel);
89   }
90
91   @Override
92   @CalledInAwt
93   protected void onInit() {
94     super.onInit();
95     myContentPanel.setPainter(new MyDividerPainter());
96     myModifierProvider.init();
97   }
98
99   @Override
100   @CalledInAwt
101   protected void onDispose() {
102     destroyChangedBlocks();
103     super.onDispose();
104   }
105
106   @NotNull
107   @Override
108   protected List<AnAction> createToolbarActions() {
109     List<AnAction> group = new ArrayList<AnAction>();
110
111     group.add(new MyIgnorePolicySettingAction());
112     group.add(new MyHighlightPolicySettingAction());
113     group.add(new MyToggleExpandByDefaultAction());
114     group.add(new MyToggleAutoScrollAction());
115     group.add(new MyReadOnlyLockAction());
116     group.add(myEditorSettingsAction);
117
118     group.add(Separator.getInstance());
119     group.addAll(super.createToolbarActions());
120
121     return group;
122   }
123
124   @NotNull
125   @Override
126   protected List<AnAction> createPopupActions() {
127     List<AnAction> group = new ArrayList<AnAction>();
128
129     group.add(Separator.getInstance());
130     group.add(new MyIgnorePolicySettingAction().getPopupGroup());
131     group.add(Separator.getInstance());
132     group.add(new MyHighlightPolicySettingAction().getPopupGroup());
133     group.add(Separator.getInstance());
134     group.add(new MyToggleAutoScrollAction());
135     group.add(new MyToggleExpandByDefaultAction());
136
137     group.add(Separator.getInstance());
138     group.addAll(super.createPopupActions());
139
140     return group;
141   }
142
143   @NotNull
144   @Override
145   protected List<AnAction> createEditorPopupActions() {
146     List<AnAction> group = new ArrayList<AnAction>();
147
148     group.add(new ReplaceSelectedChangesAction(Side.LEFT, false));
149     group.add(new AppendSelectedChangesAction(Side.LEFT, false));
150     group.add(new ReplaceSelectedChangesAction(Side.RIGHT, false));
151     group.add(new AppendSelectedChangesAction(Side.RIGHT, false));
152     group.add(new RevertSelectedChangesAction(Side.LEFT));
153     group.add(new RevertSelectedChangesAction(Side.RIGHT));
154
155     group.add(Separator.getInstance());
156     group.addAll(super.createEditorPopupActions());
157
158     return group;
159   }
160
161   @Override
162   @CalledInAwt
163   protected void processContextHints() {
164     super.processContextHints();
165     myInitialScrollHelper.processContext(myRequest);
166   }
167
168   @Override
169   @CalledInAwt
170   protected void updateContextHints() {
171     super.updateContextHints();
172     myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
173     myInitialScrollHelper.updateContext(myRequest);
174   }
175
176   //
177   // Diff
178   //
179
180   @NotNull
181   public FoldingModelSupport.Settings getFoldingModelSettings() {
182     return TextDiffViewerUtil.getFoldingModelSettings(myContext);
183   }
184
185   @Override
186   protected void onSlowRediff() {
187     super.onSlowRediff();
188     myStatusPanel.setBusy(true);
189     myInitialScrollHelper.onSlowRediff();
190   }
191
192   @Override
193   @NotNull
194   protected Runnable performRediff(@NotNull final ProgressIndicator indicator) {
195     try {
196       indicator.checkCanceled();
197
198       final Document document1 = getContent1().getDocument();
199       final Document document2 = getContent2().getDocument();
200
201       CharSequence[] texts = ApplicationManager.getApplication().runReadAction(new Computable<CharSequence[]>() {
202         @Override
203         public CharSequence[] compute() {
204           return new CharSequence[]{document1.getImmutableCharSequence(), document2.getImmutableCharSequence()};
205         }
206       });
207
208       List<LineFragment> lineFragments = null;
209       if (getHighlightPolicy().isShouldCompare()) {
210         lineFragments = DiffUtil.compare(texts[0], texts[1], getDiffConfig(), indicator);
211       }
212
213       boolean isEqualContents = (lineFragments == null || lineFragments.isEmpty()) &&
214                                 StringUtil.equals(document1.getCharsSequence(), document2.getCharsSequence());
215
216       return apply(new CompareData(lineFragments, isEqualContents));
217     }
218     catch (DiffTooBigException e) {
219       return applyNotification(DiffNotifications.DIFF_TOO_BIG);
220     }
221     catch (ProcessCanceledException e) {
222       throw e;
223     }
224     catch (Throwable e) {
225       LOG.error(e);
226       return applyNotification(DiffNotifications.ERROR);
227     }
228   }
229
230   @NotNull
231   private Runnable apply(@NotNull final CompareData data) {
232     return new Runnable() {
233       @Override
234       public void run() {
235         myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
236         clearDiffPresentation();
237
238         if (data.isEqualContent()) myPanel.addNotification(DiffNotifications.EQUAL_CONTENTS);
239
240         if (data.getFragments() != null) {
241           for (LineFragment fragment : data.getFragments()) {
242             myDiffChanges.add(new SimpleDiffChange(SimpleDiffViewer.this, fragment, getHighlightPolicy().isFineFragments()));
243           }
244         }
245
246         myFoldingModel.install(data.getFragments(), myRequest, getFoldingModelSettings());
247
248         myInitialScrollHelper.onRediff();
249
250         myContentPanel.repaintDivider();
251         myStatusPanel.update();
252       }
253     };
254   }
255
256   @NotNull
257   private Runnable applyNotification(@Nullable final JComponent notification) {
258     return new Runnable() {
259       @Override
260       public void run() {
261         clearDiffPresentation();
262         if (notification != null) myPanel.addNotification(notification);
263       }
264     };
265   }
266
267   private void clearDiffPresentation() {
268     myStatusPanel.setBusy(false);
269     myPanel.resetNotifications();
270     destroyChangedBlocks();
271   }
272
273   @NotNull
274   private DiffUtil.DiffConfig getDiffConfig() {
275     return new DiffUtil.DiffConfig(getTextSettings().getIgnorePolicy(), getHighlightPolicy());
276   }
277
278   @NotNull
279   private HighlightPolicy getHighlightPolicy() {
280     return getTextSettings().getHighlightPolicy();
281   }
282
283   //
284   // Impl
285   //
286
287   private void destroyChangedBlocks() {
288     for (SimpleDiffChange change : myDiffChanges) {
289       change.destroyHighlighter();
290     }
291     myDiffChanges.clear();
292
293     for (SimpleDiffChange change : myInvalidDiffChanges) {
294       change.destroyHighlighter();
295     }
296     myInvalidDiffChanges.clear();
297
298     myFoldingModel.destroy();
299
300     myContentPanel.repaintDivider();
301     myStatusPanel.update();
302   }
303
304   @Override
305   @CalledInAwt
306   protected void onBeforeDocumentChange(@NotNull DocumentEvent e) {
307     super.onBeforeDocumentChange(e);
308     if (myDiffChanges.isEmpty()) return;
309
310     Side side = null;
311     if (e.getDocument() == getEditor(Side.LEFT).getDocument()) side = Side.LEFT;
312     if (e.getDocument() == getEditor(Side.RIGHT).getDocument()) side = Side.RIGHT;
313     if (side == null) {
314       LOG.warn("Unknown document changed");
315       return;
316     }
317
318     int line1 = e.getDocument().getLineNumber(e.getOffset());
319     int line2 = e.getDocument().getLineNumber(e.getOffset() + e.getOldLength()) + 1;
320     int shift = DiffUtil.countLinesShift(e);
321
322     List<SimpleDiffChange> invalid = new ArrayList<SimpleDiffChange>();
323     for (SimpleDiffChange change : myDiffChanges) {
324       if (change.processChange(line1, line2, shift, side)) {
325         invalid.add(change);
326       }
327     }
328
329     if (!invalid.isEmpty()) {
330       myDiffChanges.removeAll(invalid);
331       myInvalidDiffChanges.addAll(invalid);
332     }
333   }
334
335   @Override
336   protected void onDocumentChange(@NotNull DocumentEvent e) {
337     super.onDocumentChange(e);
338     myFoldingModel.onDocumentChanged(e);
339   }
340
341   @CalledInAwt
342   protected boolean doScrollToChange(@NotNull ScrollToPolicy scrollToPolicy) {
343     SimpleDiffChange targetChange = scrollToPolicy.select(myDiffChanges);
344     if (targetChange == null) return false;
345
346     doScrollToChange(targetChange, false);
347     return true;
348   }
349
350   private void doScrollToChange(@NotNull SimpleDiffChange change, final boolean animated) {
351     final int line1 = change.getStartLine(Side.LEFT);
352     final int line2 = change.getStartLine(Side.RIGHT);
353     final int endLine1 = change.getEndLine(Side.LEFT);
354     final int endLine2 = change.getEndLine(Side.RIGHT);
355
356     DiffUtil.moveCaret(getEditor1(), line1);
357     DiffUtil.moveCaret(getEditor2(), line2);
358
359     getSyncScrollSupport().makeVisible(getCurrentSide(), line1, endLine1, line2, endLine2, animated);
360   }
361
362   protected boolean doScrollToContext(@NotNull DiffNavigationContext context) {
363     ChangedLinesIterator changedLinesIterator = new ChangedLinesIterator(Side.RIGHT);
364     NavigationContextChecker checker = new NavigationContextChecker(changedLinesIterator, context);
365     int line = checker.contextMatchCheck();
366     if (line == -1) {
367       // this will work for the case, when spaces changes are ignored, and corresponding fragments are not reported as changed
368       // just try to find target line  -> +-
369       AllLinesIterator allLinesIterator = new AllLinesIterator(Side.RIGHT);
370       NavigationContextChecker checker2 = new NavigationContextChecker(allLinesIterator, context);
371       line = checker2.contextMatchCheck();
372     }
373     if (line == -1) return false;
374
375     scrollToLine(Side.RIGHT, line);
376     return true;
377   }
378
379   //
380   // Getters
381   //
382
383   @NotNull
384   protected List<SimpleDiffChange> getDiffChanges() {
385     return myDiffChanges;
386   }
387
388   @NotNull
389   @Override
390   protected SyncScrollSupport.SyncScrollable getSyncScrollable() {
391     return mySyncScrollable;
392   }
393
394   @NotNull
395   @Override
396   protected JComponent getStatusPanel() {
397     return myStatusPanel;
398   }
399
400   @NotNull
401   public ModifierProvider getModifierProvider() {
402     return myModifierProvider;
403   }
404
405   @NotNull
406   @Override
407   public SyncScrollSupport.TwosideSyncScrollSupport getSyncScrollSupport() {
408     //noinspection ConstantConditions
409     return super.getSyncScrollSupport();
410   }
411
412   //
413   // Misc
414   //
415
416   @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
417   public static boolean canShowRequest(@NotNull DiffContext context, @NotNull DiffRequest request) {
418     return TwosideTextDiffViewer.canShowRequest(context, request);
419   }
420
421   @NotNull
422   @CalledInAwt
423   private List<SimpleDiffChange> getSelectedChanges(@NotNull Side side) {
424     final BitSet lines = DiffUtil.getSelectedLines(getEditor(side));
425
426     List<SimpleDiffChange> affectedChanges = new ArrayList<SimpleDiffChange>();
427     for (int i = myDiffChanges.size() - 1; i >= 0; i--) {
428       SimpleDiffChange change = myDiffChanges.get(i);
429       int line1 = change.getStartLine(side);
430       int line2 = change.getEndLine(side);
431
432       if (DiffUtil.isSelectedByLine(lines, line1, line2)) {
433         affectedChanges.add(change);
434       }
435     }
436     return affectedChanges;
437   }
438
439   @Nullable
440   @CalledInAwt
441   private SimpleDiffChange getSelectedChange(@NotNull Side side) {
442     int caretLine = getEditor(side).getCaretModel().getLogicalPosition().line;
443
444     for (SimpleDiffChange change : myDiffChanges) {
445       int line1 = change.getStartLine(side);
446       int line2 = change.getEndLine(side);
447
448       if (DiffUtil.isSelectedByLine(caretLine, line1, line2)) return change;
449     }
450     return null;
451   }
452
453   //
454   // Actions
455   //
456
457   private class MyPrevNextDifferenceIterable extends PrevNextDifferenceIterableBase<SimpleDiffChange> {
458     @NotNull
459     @Override
460     protected List<SimpleDiffChange> getChanges() {
461       return myDiffChanges;
462     }
463
464     @NotNull
465     @Override
466     protected EditorEx getEditor() {
467       return getCurrentEditor();
468     }
469
470     @Override
471     protected int getStartLine(@NotNull SimpleDiffChange change) {
472       return change.getStartLine(getCurrentSide());
473     }
474
475     @Override
476     protected int getEndLine(@NotNull SimpleDiffChange change) {
477       return change.getEndLine(getCurrentSide());
478     }
479
480     @Override
481     protected void scrollToChange(@NotNull SimpleDiffChange change) {
482       doScrollToChange(change, true);
483     }
484   }
485
486   private class MyReadOnlyLockAction extends TextDiffViewerUtil.EditorReadOnlyLockAction {
487     public MyReadOnlyLockAction() {
488       super(getContext(), getEditableEditors());
489     }
490
491     @Override
492     protected void doApply(boolean readOnly) {
493       super.doApply(readOnly);
494       for (SimpleDiffChange change : myDiffChanges) {
495         change.updateGutterActions(true);
496       }
497     }
498   }
499
500   //
501   // Modification operations
502   //
503
504   private abstract class ApplySelectedChangesActionBase extends AnAction implements DumbAware {
505     @NotNull protected final Side myModifiedSide;
506     private final boolean myShortcut;
507
508     public ApplySelectedChangesActionBase(@NotNull Side modifiedSide, boolean shortcut) {
509       myModifiedSide = modifiedSide;
510       myShortcut = shortcut;
511     }
512
513     @Override
514     public void update(@NotNull AnActionEvent e) {
515       if (myShortcut) {
516         // consume shortcut even if there are nothing to do - avoid calling some other action
517         e.getPresentation().setEnabledAndVisible(true);
518         return;
519       }
520
521       Editor editor = e.getData(CommonDataKeys.EDITOR);
522       if (editor != getEditor1() && editor != getEditor2()) {
523         e.getPresentation().setEnabledAndVisible(false);
524         return;
525       }
526
527       Side side = Side.fromLeft(editor == getEditor(Side.LEFT));
528       if (!isVisible(side)) {
529         e.getPresentation().setEnabledAndVisible(false);
530         return;
531       }
532
533       Editor modifiedEditor = getEditor(myModifiedSide);
534       if (!DiffUtil.isEditable(modifiedEditor)) {
535         e.getPresentation().setEnabledAndVisible(false);
536         return;
537       }
538
539       e.getPresentation().setVisible(true);
540       e.getPresentation().setEnabled(isSomeChangeSelected(side));
541     }
542
543     @Override
544     public void actionPerformed(@NotNull final AnActionEvent e) {
545       Editor editor = e.getData(CommonDataKeys.EDITOR);
546       if (editor != getEditor1() && editor != getEditor2()) return;
547
548       final Side side = Side.fromLeft(editor == getEditor(Side.LEFT));
549       final List<SimpleDiffChange> selectedChanges = getSelectedChanges(side);
550       if (selectedChanges.isEmpty()) return;
551
552       Editor modifiedEditor = getEditor(myModifiedSide);
553       if (!DiffUtil.isEditable(modifiedEditor)) return;
554
555       String title = e.getPresentation().getText() + " selected changes";
556       DiffUtil.executeWriteCommand(modifiedEditor.getDocument(), e.getProject(), title, new Runnable() {
557         @Override
558         public void run() {
559           apply(selectedChanges);
560         }
561       });
562     }
563
564     protected boolean isSomeChangeSelected(@NotNull Side side) {
565       if (myDiffChanges.isEmpty()) return false;
566
567       EditorEx editor = getEditor(side);
568       List<Caret> carets = editor.getCaretModel().getAllCarets();
569       if (carets.size() != 1) return true;
570       Caret caret = carets.get(0);
571       if (caret.hasSelection()) return true;
572       int line = editor.getDocument().getLineNumber(editor.getExpectedCaretOffset());
573
574       for (SimpleDiffChange change : myDiffChanges) {
575         if (change.isSelectedByLine(line, side)) return true;
576       }
577       return false;
578     }
579
580     protected abstract boolean isVisible(@NotNull Side side);
581
582     @CalledWithWriteLock
583     protected abstract void apply(@NotNull List<SimpleDiffChange> changes);
584   }
585
586   private class ReplaceSelectedChangesAction extends ApplySelectedChangesActionBase {
587     public ReplaceSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
588       super(focusedSide.other(), shortcut);
589
590       setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.ApplyLeftSide", "Diff.ApplyRightSide")).getShortcutSet());
591       getTemplatePresentation().setText("Replace");
592       getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRight, AllIcons.Diff.Arrow));
593     }
594
595     @Override
596     protected boolean isVisible(@NotNull Side side) {
597       return side == myModifiedSide.other();
598     }
599
600     @Override
601     protected void apply(@NotNull List<SimpleDiffChange> changes) {
602       for (SimpleDiffChange change : changes) {
603         replaceChange(change, myModifiedSide.other());
604       }
605     }
606   }
607
608   private class AppendSelectedChangesAction extends ApplySelectedChangesActionBase {
609     public AppendSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
610       super(focusedSide.other(), shortcut);
611
612       setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.AppendLeftSide", "Diff.AppendRightSide")).getShortcutSet());
613       getTemplatePresentation().setText("Insert");
614       getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRightDown, AllIcons.Diff.ArrowLeftDown));
615     }
616
617     @Override
618     protected boolean isVisible(@NotNull Side side) {
619       return side == myModifiedSide.other();
620     }
621
622     @Override
623     protected void apply(@NotNull List<SimpleDiffChange> changes) {
624       for (SimpleDiffChange change : changes) {
625         appendChange(change, myModifiedSide.other());
626       }
627     }
628   }
629
630   private class RevertSelectedChangesAction extends ApplySelectedChangesActionBase {
631     public RevertSelectedChangesAction(@NotNull Side focusedSide) {
632       super(focusedSide, false);
633       getTemplatePresentation().setText("Revert");
634       getTemplatePresentation().setIcon(AllIcons.Diff.Remove);
635     }
636
637     @Override
638     protected boolean isVisible(@NotNull Side side) {
639       return side == myModifiedSide;
640     }
641
642     @Override
643     protected void apply(@NotNull List<SimpleDiffChange> changes) {
644       for (SimpleDiffChange change : changes) {
645         replaceChange(change, myModifiedSide.other());
646       }
647     }
648   }
649
650   @CalledWithWriteLock
651   public void replaceChange(@NotNull SimpleDiffChange change, @NotNull final Side sourceSide) {
652     if (!change.isValid()) return;
653     Side outputSide = sourceSide.other();
654
655     DiffUtil.applyModification(getEditor(outputSide).getDocument(), change.getStartLine(outputSide), change.getEndLine(outputSide),
656                                getEditor(sourceSide).getDocument(), change.getStartLine(sourceSide), change.getEndLine(sourceSide));
657
658     change.destroyHighlighter();
659     myDiffChanges.remove(change);
660   }
661
662   @CalledWithWriteLock
663   public void appendChange(@NotNull SimpleDiffChange change, @NotNull final Side sourceSide) {
664     if (!change.isValid()) return;
665     if (change.getStartLine(sourceSide) == change.getEndLine(sourceSide)) return;
666     Side outputSide = sourceSide.other();
667
668     DiffUtil.applyModification(getEditor(outputSide).getDocument(), change.getEndLine(outputSide), change.getEndLine(outputSide),
669                                getEditor(sourceSide).getDocument(), change.getStartLine(sourceSide), change.getEndLine(sourceSide));
670
671     change.destroyHighlighter();
672     myDiffChanges.remove(change);
673   }
674
675   private class MyHighlightPolicySettingAction extends TextDiffViewerUtil.HighlightPolicySettingAction {
676     public MyHighlightPolicySettingAction() {
677       super(getTextSettings());
678     }
679
680     @Override
681     protected void onSettingsChanged() {
682       rediff();
683     }
684   }
685
686   private class MyIgnorePolicySettingAction extends TextDiffViewerUtil.IgnorePolicySettingAction {
687     public MyIgnorePolicySettingAction() {
688       super(getTextSettings());
689     }
690
691     @Override
692     protected void onSettingsChanged() {
693       rediff();
694     }
695   }
696
697   private class MyToggleExpandByDefaultAction extends TextDiffViewerUtil.ToggleExpandByDefaultAction {
698     public MyToggleExpandByDefaultAction() {
699       super(getTextSettings());
700     }
701
702     @Override
703     protected void expandAll(boolean expand) {
704       myFoldingModel.expandAll(expand);
705     }
706   }
707
708   //
709   // Scroll from annotate
710   //
711
712   private class AllLinesIterator implements Iterator<Pair<Integer, CharSequence>> {
713     @NotNull private final Side mySide;
714     @NotNull private final Document myDocument;
715     private int myLine = 0;
716
717     private AllLinesIterator(@NotNull Side side) {
718       mySide = side;
719
720       myDocument = getEditor(mySide).getDocument();
721     }
722
723     @Override
724     public boolean hasNext() {
725       return myLine < getLineCount(myDocument);
726     }
727
728     @Override
729     public Pair<Integer, CharSequence> next() {
730       int offset1 = myDocument.getLineStartOffset(myLine);
731       int offset2 = myDocument.getLineEndOffset(myLine);
732
733       CharSequence text = myDocument.getImmutableCharSequence().subSequence(offset1, offset2);
734
735       Pair<Integer, CharSequence> pair = new Pair<Integer, CharSequence>(myLine, text);
736       myLine++;
737
738       return pair;
739     }
740
741     @Override
742     public void remove() {
743       throw new UnsupportedOperationException();
744     }
745   }
746
747   private class ChangedLinesIterator extends BufferedLineIterator {
748     @NotNull private final Side mySide;
749     private int myIndex = 0;
750
751     private ChangedLinesIterator(@NotNull Side side) {
752       mySide = side;
753       init();
754     }
755
756     @Override
757     public boolean hasNextBlock() {
758       return myIndex < myDiffChanges.size();
759     }
760
761     @Override
762     public void loadNextBlock() {
763       SimpleDiffChange change = myDiffChanges.get(myIndex);
764       myIndex++;
765
766       int line1 = change.getStartLine(mySide);
767       int line2 = change.getEndLine(mySide);
768
769       Document document = getEditor(mySide).getDocument();
770
771       for (int i = line1; i < line2; i++) {
772         int offset1 = document.getLineStartOffset(i);
773         int offset2 = document.getLineEndOffset(i);
774
775         CharSequence text = document.getImmutableCharSequence().subSequence(offset1, offset2);
776         addLine(i, text);
777       }
778     }
779   }
780
781   //
782   // Helpers
783   //
784
785   @Nullable
786   @Override
787   public Object getData(@NonNls String dataId) {
788     if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
789       return myPrevNextDifferenceIterable;
790     }
791     else if (DiffDataKeys.CURRENT_CHANGE_RANGE.is(dataId)) {
792       SimpleDiffChange change = getSelectedChange(getCurrentSide());
793       if (change != null) {
794         return new LineRange(change.getStartLine(getCurrentSide()), change.getEndLine(getCurrentSide()));
795       }
796     }
797     return super.getData(dataId);
798   }
799
800   private class MySyncScrollable extends BaseSyncScrollable {
801     @Override
802     public boolean isSyncScrollEnabled() {
803       return getTextSettings().isEnableSyncScroll();
804     }
805
806     public int transfer(@NotNull Side baseSide, int line) {
807       if (myDiffChanges.isEmpty()) {
808         return line;
809       }
810
811       return super.transfer(baseSide, line);
812     }
813
814     @Override
815     protected void processHelper(@NotNull ScrollHelper helper) {
816       if (!helper.process(0, 0)) return;
817       for (SimpleDiffChange diffChange : myDiffChanges) {
818         if (!helper.process(diffChange.getStartLine(Side.LEFT), diffChange.getStartLine(Side.RIGHT))) return;
819         if (!helper.process(diffChange.getEndLine(Side.LEFT), diffChange.getEndLine(Side.RIGHT))) return;
820       }
821       helper.process(getEditor1().getDocument().getLineCount(), getEditor2().getDocument().getLineCount());
822     }
823   }
824
825   private class MyDividerPainter implements DiffSplitter.Painter, DiffDividerDrawUtil.DividerPaintable {
826     @Override
827     public void paint(@NotNull Graphics g, @NotNull JComponent divider) {
828       Graphics2D gg = DiffDividerDrawUtil.getDividerGraphics(g, divider, getEditor1().getComponent());
829
830       gg.setColor(DiffDrawUtil.getDividerColor(getEditor1()));
831       gg.fill(gg.getClipBounds());
832
833       //DividerPolygonUtil.paintSimplePolygons(gg, divider.getWidth(), getEditor1(), getEditor2(), this);
834       DiffDividerDrawUtil.paintPolygons(gg, divider.getWidth(), getEditor1(), getEditor2(), this);
835
836       myFoldingModel.paintOnDivider(gg, divider);
837
838       gg.dispose();
839     }
840
841     @Override
842     public void process(@NotNull Handler handler) {
843       for (SimpleDiffChange diffChange : myDiffChanges) {
844         if (!handler.process(diffChange.getStartLine(Side.LEFT), diffChange.getEndLine(Side.LEFT),
845                              diffChange.getStartLine(Side.RIGHT), diffChange.getEndLine(Side.RIGHT),
846                              diffChange.getDiffType().getColor(getEditor1()))) {
847           return;
848         }
849       }
850     }
851   }
852
853   private class MyStatusPanel extends StatusPanel {
854     @Override
855     protected int getChangesCount() {
856       return myDiffChanges.size() + myInvalidDiffChanges.size();
857     }
858   }
859
860   private static class CompareData {
861     @Nullable private final List<LineFragment> myFragments;
862     private final boolean myEqualContent;
863
864     public CompareData(@Nullable List<LineFragment> fragments, boolean equalContent) {
865       myFragments = fragments;
866       myEqualContent = equalContent;
867     }
868
869     @Nullable
870     public List<LineFragment> getFragments() {
871       return myFragments;
872     }
873
874     public boolean isEqualContent() {
875       return myEqualContent;
876     }
877   }
878
879   public class ModifierProvider extends KeyboardModifierListener {
880     public void init() {
881       init(myPanel, SimpleDiffViewer.this);
882     }
883
884     @Override
885     public void onModifiersChanged() {
886       for (SimpleDiffChange change : myDiffChanges) {
887         change.updateGutterActions(false);
888       }
889     }
890   }
891
892   private static class MyFoldingModel extends FoldingModelSupport {
893     private final MyPaintable myPaintable = new MyPaintable(0, 1);
894
895     public MyFoldingModel(@NotNull List<? extends EditorEx> editors, @NotNull Disposable disposable) {
896       super(editors.toArray(new EditorEx[2]), disposable);
897     }
898
899     public void install(@Nullable final List<LineFragment> fragments,
900                         @NotNull UserDataHolder context,
901                         @NotNull FoldingModelSupport.Settings settings) {
902       Iterator<int[]> it = map(fragments, new Function<LineFragment, int[]>() {
903         @Override
904         public int[] fun(LineFragment fragment) {
905           return new int[]{
906             fragment.getStartLine1(),
907             fragment.getEndLine1(),
908             fragment.getStartLine2(),
909             fragment.getEndLine2()};
910         }
911       });
912       install(it, context, settings);
913     }
914
915     public void paintOnDivider(@NotNull Graphics2D gg, @NotNull Component divider) {
916       myPaintable.paintOnDivider(gg, divider);
917     }
918   }
919
920   private class MyInitialScrollHelper extends MyInitialScrollPositionHelper {
921     @Override
922     protected boolean doScrollToChange() {
923       if (myScrollToChange == null) return false;
924       return SimpleDiffViewer.this.doScrollToChange(myScrollToChange);
925     }
926
927     @Override
928     protected boolean doScrollToFirstChange() {
929       return SimpleDiffViewer.this.doScrollToChange(ScrollToPolicy.FIRST_CHANGE);
930     }
931
932     @Override
933     protected boolean doScrollToContext() {
934       if (myNavigationContext == null) return false;
935       return SimpleDiffViewer.this.doScrollToContext(myNavigationContext);
936     }
937   }
938 }