diff: add annotate action to diff viewers
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / tools / fragmented / UnifiedDiffViewer.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.fragmented;
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.actions.impl.OpenInEditorWithMouseAction;
22 import com.intellij.diff.actions.impl.SetEditorSettingsAction;
23 import com.intellij.diff.comparison.DiffTooBigException;
24 import com.intellij.diff.contents.DocumentContent;
25 import com.intellij.diff.fragments.LineFragment;
26 import com.intellij.diff.requests.ContentDiffRequest;
27 import com.intellij.diff.requests.DiffRequest;
28 import com.intellij.diff.tools.util.*;
29 import com.intellij.diff.tools.util.base.*;
30 import com.intellij.diff.tools.util.side.TwosideTextDiffViewer;
31 import com.intellij.diff.util.*;
32 import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
33 import com.intellij.icons.AllIcons;
34 import com.intellij.openapi.Disposable;
35 import com.intellij.openapi.actionSystem.*;
36 import com.intellij.openapi.application.ApplicationManager;
37 import com.intellij.openapi.command.undo.UndoManager;
38 import com.intellij.openapi.diagnostic.Logger;
39 import com.intellij.openapi.diff.LineTokenizer;
40 import com.intellij.openapi.editor.*;
41 import com.intellij.openapi.editor.actionSystem.EditorActionManager;
42 import com.intellij.openapi.editor.actionSystem.ReadonlyFragmentModificationHandler;
43 import com.intellij.openapi.editor.colors.EditorColors;
44 import com.intellij.openapi.editor.event.DocumentAdapter;
45 import com.intellij.openapi.editor.event.DocumentEvent;
46 import com.intellij.openapi.editor.ex.EditorEx;
47 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
48 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
49 import com.intellij.openapi.fileTypes.FileType;
50 import com.intellij.openapi.progress.ProcessCanceledException;
51 import com.intellij.openapi.progress.ProgressIndicator;
52 import com.intellij.openapi.project.DumbAware;
53 import com.intellij.openapi.project.Project;
54 import com.intellij.openapi.util.Computable;
55 import com.intellij.openapi.util.Pair;
56 import com.intellij.openapi.util.UserDataHolder;
57 import com.intellij.util.Function;
58 import com.intellij.util.containers.ContainerUtil;
59 import gnu.trove.TIntFunction;
60 import org.jetbrains.annotations.*;
61
62 import javax.swing.*;
63 import java.util.*;
64
65 import static com.intellij.diff.util.DiffUtil.getLineCount;
66
67 public class UnifiedDiffViewer extends ListenerDiffViewerBase {
68   public static final Logger LOG = Logger.getInstance(UnifiedDiffViewer.class);
69
70   @NotNull protected final EditorEx myEditor;
71   @NotNull protected final Document myDocument;
72   @NotNull private final UnifiedDiffPanel myPanel;
73
74   @NotNull private final SetEditorSettingsAction myEditorSettingsAction;
75   @NotNull private final PrevNextDifferenceIterable myPrevNextDifferenceIterable;
76   @NotNull private final MyStatusPanel myStatusPanel;
77
78   @NotNull private final MyInitialScrollHelper myInitialScrollHelper = new MyInitialScrollHelper();
79   @NotNull private final MyFoldingModel myFoldingModel;
80
81   @NotNull protected Side myMasterSide = Side.RIGHT;
82
83   @Nullable private ChangedBlockData myChangedBlockData;
84
85   private final boolean[] myForceReadOnlyFlags;
86   private boolean myReadOnlyLockSet = false;
87
88   private boolean myDuringOnesideDocumentModification;
89   private boolean myDuringTwosideDocumentModification;
90
91   private boolean myStateIsOutOfDate; // whether something was changed since last rediff
92   private boolean mySuppressEditorTyping; // our state is inconsistent. No typing can be handled correctly
93
94   public UnifiedDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
95     super(context, (ContentDiffRequest)request);
96
97     myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable();
98     myStatusPanel = new MyStatusPanel();
99
100     myForceReadOnlyFlags = TextDiffViewerUtil.checkForceReadOnly(myContext, myRequest);
101
102     boolean leftEditable = isEditable(Side.LEFT, false);
103     boolean rightEditable = isEditable(Side.RIGHT, false);
104     if (leftEditable && !rightEditable) myMasterSide = Side.LEFT;
105     if (!leftEditable && rightEditable) myMasterSide = Side.RIGHT;
106
107
108     myDocument = EditorFactory.getInstance().createDocument("");
109     myEditor = DiffUtil.createEditor(myDocument, myProject, true, true);
110
111     List<JComponent> titles = DiffUtil.createTextTitles(myRequest, ContainerUtil.list(myEditor, myEditor));
112     UnifiedContentPanel contentPanel = new UnifiedContentPanel(titles, myEditor);
113
114     myPanel = new UnifiedDiffPanel(myProject, contentPanel, this, myContext);
115
116     myFoldingModel = new MyFoldingModel(myEditor, this);
117
118     myEditorSettingsAction = new SetEditorSettingsAction(getTextSettings(), getEditors());
119     myEditorSettingsAction.applyDefaults();
120
121     new MyOpenInEditorWithMouseAction().register(getEditors());
122
123     TextDiffViewerUtil.checkDifferentDocuments(myRequest);
124
125     DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.LEFT, true), myPanel);
126     DiffUtil.registerAction(new AppendSelectedChangesAction(Side.LEFT, true), myPanel);
127     DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.RIGHT, true), myPanel);
128     DiffUtil.registerAction(new AppendSelectedChangesAction(Side.RIGHT, true), myPanel);
129   }
130
131   @Override
132   @CalledInAwt
133   protected void onInit() {
134     super.onInit();
135     installEditorListeners();
136     installTypingSupport();
137     myPanel.setLoadingContent(); // We need loading panel only for initial rediff()
138     myPanel.setPersistentNotifications(DiffUtil.getCustomNotifications(myContext, myRequest));
139   }
140
141   @Override
142   @CalledInAwt
143   protected void onDispose() {
144     super.onDispose();
145     EditorFactory.getInstance().releaseEditor(myEditor);
146   }
147
148   @Override
149   @CalledInAwt
150   protected void processContextHints() {
151     super.processContextHints();
152     Side side = DiffUtil.getUserData(myRequest, myContext, DiffUserDataKeys.MASTER_SIDE);
153     if (side != null) myMasterSide = side;
154
155     myInitialScrollHelper.processContext(myRequest);
156   }
157
158   @Override
159   @CalledInAwt
160   protected void updateContextHints() {
161     super.updateContextHints();
162     myInitialScrollHelper.updateContext(myRequest);
163     myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
164   }
165
166   @CalledInAwt
167   protected void updateEditorCanBeTyped() {
168     myEditor.setViewer(mySuppressEditorTyping || !isEditable(myMasterSide, true));
169   }
170
171   private void installTypingSupport() {
172     if (!isEditable(myMasterSide, false)) return;
173
174     updateEditorCanBeTyped();
175     myEditor.getColorsScheme().setColor(EditorColors.READONLY_FRAGMENT_BACKGROUND_COLOR, null); // guarded blocks
176     EditorActionManager.getInstance().setReadonlyFragmentModificationHandler(myDocument, new MyReadonlyFragmentModificationHandler());
177     myDocument.putUserData(UndoManager.ORIGINAL_DOCUMENT, getDocument(myMasterSide)); // use undo of master document
178
179     myDocument.addDocumentListener(new MyOnesideDocumentListener());
180   }
181
182   @CalledInAwt
183   @NotNull
184   public List<AnAction> createToolbarActions() {
185     List<AnAction> group = new ArrayList<AnAction>();
186
187     // TODO: allow to choose myMasterSide
188     group.add(new MyIgnorePolicySettingAction());
189     group.add(new MyHighlightPolicySettingAction());
190     group.add(new MyToggleExpandByDefaultAction());
191     group.add(new MyReadOnlyLockAction());
192     group.add(myEditorSettingsAction);
193
194     group.add(Separator.getInstance());
195     group.addAll(super.createToolbarActions());
196
197     return group;
198   }
199
200   @CalledInAwt
201   @NotNull
202   public List<AnAction> createPopupActions() {
203     List<AnAction> group = new ArrayList<AnAction>();
204
205     group.add(Separator.getInstance());
206     group.add(new MyIgnorePolicySettingAction().getPopupGroup());
207     group.add(Separator.getInstance());
208     group.add(new MyHighlightPolicySettingAction().getPopupGroup());
209     group.add(Separator.getInstance());
210     group.add(new MyToggleExpandByDefaultAction());
211
212     group.add(Separator.getInstance());
213     group.addAll(super.createPopupActions());
214
215     return group;
216   }
217
218   @NotNull
219   protected List<AnAction> createEditorPopupActions() {
220     List<AnAction> group = new ArrayList<AnAction>();
221
222     group.add(new ReplaceSelectedChangesAction(Side.LEFT, false));
223     group.add(new AppendSelectedChangesAction(Side.LEFT, false));
224     group.add(new ReplaceSelectedChangesAction(Side.RIGHT, false));
225     group.add(new AppendSelectedChangesAction(Side.RIGHT, false));
226     group.add(new RevertSelectedChangesAction(Side.LEFT));
227     group.add(new RevertSelectedChangesAction(Side.RIGHT));
228
229     group.add(Separator.getInstance());
230     group.addAll(TextDiffViewerUtil.createEditorPopupActions());
231
232     return group;
233   }
234
235   @CalledInAwt
236   protected void installEditorListeners() {
237     new TextDiffViewerUtil.EditorActionsPopup(createEditorPopupActions()).install(getEditors());
238   }
239
240   //
241   // Diff
242   //
243
244   @Override
245   @CalledInAwt
246   protected void onSlowRediff() {
247     super.onSlowRediff();
248     myStatusPanel.setBusy(true);
249   }
250
251   @Override
252   @NotNull
253   protected Runnable performRediff(@NotNull final ProgressIndicator indicator) {
254     try {
255       indicator.checkCanceled();
256
257       final Document document1 = getContent1().getDocument();
258       final Document document2 = getContent2().getDocument();
259
260       final CharSequence[] texts = ApplicationManager.getApplication().runReadAction(new Computable<CharSequence[]>() {
261         @Override
262         public CharSequence[] compute() {
263           return new CharSequence[]{document1.getImmutableCharSequence(), document2.getImmutableCharSequence()};
264         }
265       });
266
267       final boolean innerFragments = getDiffConfig().innerFragments;
268       final List<LineFragment> fragments = DiffUtil.compare(texts[0], texts[1], getDiffConfig(), indicator);
269
270       final DocumentContent content1 = getContent1();
271       final DocumentContent content2 = getContent2();
272
273       indicator.checkCanceled();
274       TwosideDocumentData data = ApplicationManager.getApplication().runReadAction(new Computable<TwosideDocumentData>() {
275         @Override
276         public TwosideDocumentData compute() {
277           indicator.checkCanceled();
278           UnifiedFragmentBuilder builder = new UnifiedFragmentBuilder(fragments, document1, document2, myMasterSide);
279           builder.exec();
280
281           indicator.checkCanceled();
282
283           EditorHighlighter highlighter = buildHighlighter(myProject, content1, content2,
284                                                            texts[0], texts[1], builder.getRanges(),
285                                                            builder.getText().length());
286
287           UnifiedEditorRangeHighlighter rangeHighlighter = new UnifiedEditorRangeHighlighter(myProject, document1, document2,
288                                                                                              builder.getRanges());
289
290           return new TwosideDocumentData(builder, highlighter, rangeHighlighter);
291         }
292       });
293       UnifiedFragmentBuilder builder = data.getBuilder();
294
295       FileType fileType = content2.getContentType() == null ? content1.getContentType() : content2.getContentType();
296
297       LineNumberConvertor convertor = builder.getConvertor();
298       List<LineRange> changedLines = builder.getChangedLines();
299       boolean isEqual = builder.isEqual();
300
301       CombinedEditorData editorData = new CombinedEditorData(builder.getText(), data.getHighlighter(), data.getRangeHighlighter(), fileType,
302                                                              convertor.createConvertor1(), convertor.createConvertor2());
303
304       return apply(editorData, builder.getBlocks(), convertor, changedLines, isEqual, innerFragments);
305     }
306     catch (DiffTooBigException e) {
307       return new Runnable() {
308         @Override
309         public void run() {
310           clearDiffPresentation();
311           myPanel.setTooBigContent();
312         }
313       };
314     }
315     catch (ProcessCanceledException e) {
316       throw e;
317     }
318     catch (Throwable e) {
319       LOG.error(e);
320       return new Runnable() {
321         @Override
322         public void run() {
323           clearDiffPresentation();
324           myPanel.setErrorContent();
325         }
326       };
327     }
328   }
329
330   private void clearDiffPresentation() {
331     myPanel.resetNotifications();
332     myStatusPanel.setBusy(false);
333     destroyChangedBlockData();
334
335     myStateIsOutOfDate = false;
336     mySuppressEditorTyping = false;
337     updateEditorCanBeTyped();
338   }
339
340   @CalledInAwt
341   protected void markSuppressEditorTyping() {
342     mySuppressEditorTyping = true;
343     updateEditorCanBeTyped();
344   }
345
346   @CalledInAwt
347   protected void markStateIsOutOfDate() {
348     myStateIsOutOfDate = true;
349     if (myChangedBlockData != null) {
350       for (UnifiedDiffChange diffChange : myChangedBlockData.getDiffChanges()) {
351         diffChange.updateGutterActions();
352       }
353     }
354   }
355
356   @Nullable
357   private EditorHighlighter buildHighlighter(@Nullable Project project,
358                                              @NotNull DocumentContent content1,
359                                              @NotNull DocumentContent content2,
360                                              @NotNull CharSequence text1,
361                                              @NotNull CharSequence text2,
362                                              @NotNull List<HighlightRange> ranges,
363                                              int textLength) {
364     EditorHighlighter highlighter1 = DiffUtil.initEditorHighlighter(project, content1, text1);
365     EditorHighlighter highlighter2 = DiffUtil.initEditorHighlighter(project, content2, text2);
366
367     if (highlighter1 == null && highlighter2 == null) return null;
368     if (highlighter1 == null) highlighter1 = DiffUtil.initEmptyEditorHighlighter(text1);
369     if (highlighter2 == null) highlighter2 = DiffUtil.initEmptyEditorHighlighter(text2);
370
371     return new UnifiedEditorHighlighter(myDocument, highlighter1, highlighter2, ranges, textLength);
372   }
373
374   @NotNull
375   private Runnable apply(@NotNull final CombinedEditorData data,
376                          @NotNull final List<ChangedBlock> blocks,
377                          @NotNull final LineNumberConvertor convertor,
378                          @NotNull final List<LineRange> changedLines,
379                          final boolean isEqual, final boolean innerFragments) {
380     return new Runnable() {
381       @Override
382       public void run() {
383         myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
384
385         clearDiffPresentation();
386         if (isEqual) myPanel.addNotification(DiffNotifications.EQUAL_CONTENTS);
387
388         TIntFunction separatorLines = myFoldingModel.getLineNumberConvertor();
389         myEditor.getGutterComponentEx().setLineNumberConvertor(mergeConverters(data.getLineConvertor1(), separatorLines),
390                                                                mergeConverters(data.getLineConvertor2(), separatorLines));
391
392         ApplicationManager.getApplication().runWriteAction(new Runnable() {
393           @Override
394           public void run() {
395             myDuringOnesideDocumentModification = true;
396             try {
397               myDocument.setText(data.getText());
398             }
399             finally {
400               myDuringOnesideDocumentModification = false;
401             }
402           }
403         });
404
405         if (data.getHighlighter() != null) myEditor.setHighlighter(data.getHighlighter());
406         DiffUtil.setEditorCodeStyle(myProject, myEditor, data.getFileType());
407
408         if (data.getRangeHighlighter() != null) data.getRangeHighlighter().apply(myProject, myDocument);
409
410
411         ArrayList<UnifiedDiffChange> diffChanges = new ArrayList<UnifiedDiffChange>(blocks.size());
412         for (ChangedBlock block : blocks) {
413           diffChanges.add(new UnifiedDiffChange(UnifiedDiffViewer.this, block, innerFragments));
414         }
415
416         List<RangeMarker> guarderRangeBlocks = new ArrayList<RangeMarker>();
417         if (!myEditor.isViewer()) {
418           for (ChangedBlock block : blocks) {
419             int start = myMasterSide.select(block.getStartOffset2(), block.getStartOffset1());
420             int end = myMasterSide.select(block.getEndOffset2() - 1, block.getEndOffset1() - 1);
421             if (start >= end) continue;
422             guarderRangeBlocks.add(createGuardedBlock(start, end));
423           }
424           int textLength = myDocument.getTextLength(); // there are 'fake' newline at the very end
425           guarderRangeBlocks.add(createGuardedBlock(textLength, textLength));
426         }
427
428         myChangedBlockData = new ChangedBlockData(diffChanges, guarderRangeBlocks, convertor);
429
430         myFoldingModel.install(changedLines, myRequest, getFoldingModelSettings());
431
432         myInitialScrollHelper.onRediff();
433
434         myStatusPanel.update();
435         myPanel.setGoodContent();
436       }
437     };
438   }
439
440   @NotNull
441   private RangeMarker createGuardedBlock(int start, int end) {
442     RangeMarker block = myDocument.createGuardedBlock(start, end);
443     block.setGreedyToLeft(true);
444     block.setGreedyToRight(true);
445     return block;
446   }
447
448   @Contract("!null, _ -> !null")
449   private static TIntFunction mergeConverters(@NotNull final TIntFunction convertor, @NotNull final TIntFunction separatorLines) {
450     return new TIntFunction() {
451       @Override
452       public int execute(int value) {
453         return convertor.execute(separatorLines.execute(value));
454       }
455     };
456   }
457
458   /*
459    * This convertor returns -1 if exact matching is impossible
460    */
461   @CalledInAwt
462   public int transferLineToOnesideStrict(@NotNull Side side, int line) {
463     if (myChangedBlockData == null) return -1;
464
465     LineNumberConvertor lineConvertor = myChangedBlockData.getLineNumberConvertor();
466     return side.isLeft() ? lineConvertor.convertInv1(line) : lineConvertor.convertInv2(line);
467   }
468
469   /*
470    * This convertor returns -1 if exact matching is impossible
471    */
472   @CalledInAwt
473   public int transferLineFromOnesideStrict(@NotNull Side side, int line) {
474     if (myChangedBlockData == null) return -1;
475
476     LineNumberConvertor lineConvertor = myChangedBlockData.getLineNumberConvertor();
477     return side.isLeft() ? lineConvertor.convert1(line) : lineConvertor.convert2(line);
478   }
479
480   /*
481    * This convertor returns 'good enough' position, even if exact matching is impossible
482    */
483   @CalledInAwt
484   public int transferLineToOneside(@NotNull Side side, int line) {
485     if (myChangedBlockData == null) return line;
486
487     LineNumberConvertor lineConvertor = myChangedBlockData.getLineNumberConvertor();
488     return side.isLeft() ? lineConvertor.convertApproximateInv1(line) : lineConvertor.convertApproximateInv2(line);
489   }
490
491   /*
492    * This convertor returns 'good enough' position, even if exact matching is impossible
493    */
494   @CalledInAwt
495   @NotNull
496   public Pair<int[], Side> transferLineFromOneside(int line) {
497     int[] lines = new int[2];
498
499     if (myChangedBlockData == null) {
500       lines[0] = line;
501       lines[1] = line;
502       return Pair.create(lines, myMasterSide);
503     }
504
505     LineNumberConvertor lineConvertor = myChangedBlockData.getLineNumberConvertor();
506
507     Side side = myMasterSide;
508     lines[0] = lineConvertor.convert1(line);
509     lines[1] = lineConvertor.convert2(line);
510
511     if (lines[0] == -1 && lines[1] == -1) {
512       lines[0] = lineConvertor.convertApproximate1(line);
513       lines[1] = lineConvertor.convertApproximate2(line);
514     }
515     else if (lines[0] == -1) {
516       lines[0] = lineConvertor.convertApproximate1(line);
517       side = Side.RIGHT;
518     }
519     else if (lines[1] == -1) {
520       lines[1] = lineConvertor.convertApproximate2(line);
521       side = Side.LEFT;
522     }
523
524     return Pair.create(lines, side);
525   }
526
527   @CalledInAwt
528   private void destroyChangedBlockData() {
529     if (myChangedBlockData == null) return;
530
531     for (UnifiedDiffChange change : myChangedBlockData.getDiffChanges()) {
532       change.destroyHighlighter();
533     }
534     for (RangeMarker block : myChangedBlockData.getGuardedRangeBlocks()) {
535       myDocument.removeGuardedBlock(block);
536     }
537     myChangedBlockData = null;
538
539     UnifiedEditorRangeHighlighter.erase(myProject, myDocument);
540
541     myFoldingModel.destroy();
542
543     myStatusPanel.update();
544   }
545
546   //
547   // Typing
548   //
549
550   private class MyOnesideDocumentListener extends DocumentAdapter {
551     @Override
552     public void beforeDocumentChange(DocumentEvent e) {
553       if (myDuringOnesideDocumentModification) return;
554       if (myChangedBlockData == null) {
555         LOG.warn("oneside beforeDocumentChange - myChangedBlockData == null");
556         return;
557       }
558       // TODO: modify Document guard range logic - we can handle case, when whole read-only block is modified (ex: my replacing selection).
559
560       try {
561         myDuringTwosideDocumentModification = true;
562
563         Document twosideDocument = getDocument(myMasterSide);
564
565         LineCol onesideStartPosition = LineCol.fromOffset(myDocument, e.getOffset());
566         LineCol onesideEndPosition = LineCol.fromOffset(myDocument, e.getOffset() + e.getOldLength());
567
568         int line1 = onesideStartPosition.line;
569         int line2 = onesideEndPosition.line + 1;
570         int shift = DiffUtil.countLinesShift(e);
571
572         int twosideStartLine = transferLineFromOnesideStrict(myMasterSide, onesideStartPosition.line);
573         int twosideEndLine = transferLineFromOnesideStrict(myMasterSide, onesideEndPosition.line);
574         if (twosideStartLine == -1 || twosideEndLine == -1) {
575           // this should never happen
576           logDebugInfo(e, onesideStartPosition, onesideEndPosition, twosideStartLine, twosideEndLine);
577           markSuppressEditorTyping();
578           return;
579         }
580
581         int twosideStartOffset = twosideDocument.getLineStartOffset(twosideStartLine) + onesideStartPosition.column;
582         int twosideEndOffset = twosideDocument.getLineStartOffset(twosideEndLine) + onesideEndPosition.column;
583         twosideDocument.replaceString(twosideStartOffset, twosideEndOffset, e.getNewFragment());
584
585         for (UnifiedDiffChange change : myChangedBlockData.getDiffChanges()) {
586           change.processChange(line1, line2, shift);
587         }
588
589         LineNumberConvertor lineNumberConvertor = myChangedBlockData.getLineNumberConvertor();
590         lineNumberConvertor.handleOnesideChange(line1, line2, shift, myMasterSide);
591       }
592       finally {
593         // TODO: we can avoid marking state out-of-date in some simple cases (like in SimpleDiffViewer)
594         // but this will greatly increase complexity, so let's wait if it's actually required by users
595         markStateIsOutOfDate();
596
597         myFoldingModel.onDocumentChanged(e);
598         scheduleRediff();
599
600         myDuringTwosideDocumentModification = false;
601       }
602     }
603
604     private void logDebugInfo(DocumentEvent e,
605                               LineCol onesideStartPosition, LineCol onesideEndPosition,
606                               int twosideStartLine, int twosideEndLine) {
607       StringBuilder info = new StringBuilder();
608       Document document1 = getDocument(Side.LEFT);
609       Document document2 = getDocument(Side.RIGHT);
610       info.append("==== UnifiedDiffViewer Debug Info ====");
611       info.append("myMasterSide - ").append(myMasterSide).append('\n');
612       info.append("myLeftDocument.length() - ").append(document1.getTextLength()).append('\n');
613       info.append("myRightDocument.length() - ").append(document2.getTextLength()).append('\n');
614       info.append("myDocument.length() - ").append(myDocument.getTextLength()).append('\n');
615       info.append("e.getOffset() - ").append(e.getOffset()).append('\n');
616       info.append("e.getNewLength() - ").append(e.getNewLength()).append('\n');
617       info.append("e.getOldLength() - ").append(e.getOldLength()).append('\n');
618       info.append("onesideStartPosition - ").append(onesideStartPosition).append('\n');
619       info.append("onesideEndPosition - ").append(onesideEndPosition).append('\n');
620       info.append("twosideStartLine - ").append(twosideStartLine).append('\n');
621       info.append("twosideEndLine - ").append(twosideEndLine).append('\n');
622       Pair<int[], Side> pair1 = transferLineFromOneside(onesideStartPosition.line);
623       Pair<int[], Side> pair2 = transferLineFromOneside(onesideEndPosition.line);
624       info.append("non-strict transferStartLine - ").append(pair1.first[0]).append("-").append(pair1.first[1])
625         .append(":").append(pair1.second).append('\n');
626       info.append("non-strict transferEndLine - ").append(pair2.first[0]).append("-").append(pair2.first[1])
627         .append(":").append(pair2.second).append('\n');
628       info.append("---- UnifiedDiffViewer Debug Info ----");
629
630       LOG.warn(info.toString());
631     }
632   }
633
634   @Override
635   protected void onDocumentChange(@NotNull DocumentEvent e) {
636     if (myDuringTwosideDocumentModification) return;
637
638     markStateIsOutOfDate();
639     markSuppressEditorTyping();
640
641     myFoldingModel.onDocumentChanged(e);
642     scheduleRediff();
643   }
644
645   //
646   // Modification operations
647   //
648
649   private abstract class ApplySelectedChangesActionBase extends AnAction implements DumbAware {
650     @NotNull protected final Side myModifiedSide;
651     private final boolean myShortcut;
652
653     public ApplySelectedChangesActionBase(@NotNull Side modifiedSide, boolean shortcut) {
654       myModifiedSide = modifiedSide;
655       myShortcut = shortcut;
656     }
657
658     @Override
659     public void update(@NotNull AnActionEvent e) {
660       if (myShortcut) {
661         // consume shortcut even if there are nothing to do - avoid calling some other action
662         e.getPresentation().setEnabledAndVisible(true);
663         return;
664       }
665
666       Editor editor = e.getData(CommonDataKeys.EDITOR);
667       if (editor != getEditor()) {
668         e.getPresentation().setEnabledAndVisible(false);
669         return;
670       }
671
672       if (!isEditable(myModifiedSide, true) || isStateIsOutOfDate()) {
673         e.getPresentation().setEnabledAndVisible(false);
674         return;
675       }
676
677       e.getPresentation().setVisible(true);
678       e.getPresentation().setEnabled(isSomeChangeSelected());
679     }
680
681     @Override
682     public void actionPerformed(@NotNull final AnActionEvent e) {
683       final List<UnifiedDiffChange> selectedChanges = getSelectedChanges();
684       if (selectedChanges.isEmpty()) return;
685
686       if (!isEditable(myModifiedSide, true)) return;
687       if (isStateIsOutOfDate()) return;
688
689       String title = e.getPresentation().getText() + " selected changes";
690       DiffUtil.executeWriteCommand(getDocument(myModifiedSide), e.getProject(), title, new Runnable() {
691         @Override
692         public void run() {
693           // state is invalidated during apply(), but changes are in reverse order, so they should not conflict with each other
694           apply(selectedChanges);
695           scheduleRediff();
696         }
697       });
698     }
699
700     protected boolean isSomeChangeSelected() {
701       if (myChangedBlockData == null) return false;
702       List<UnifiedDiffChange> changes = myChangedBlockData.getDiffChanges();
703       if (changes.isEmpty()) return false;
704
705       List<Caret> carets = getEditor().getCaretModel().getAllCarets();
706       if (carets.size() != 1) return true;
707       Caret caret = carets.get(0);
708       if (caret.hasSelection()) return true;
709       int line = getEditor().getDocument().getLineNumber(getEditor().getExpectedCaretOffset());
710
711       for (UnifiedDiffChange change : changes) {
712         if (DiffUtil.isSelectedByLine(line, change.getLine1(), change.getLine2())) return true;
713       }
714       return false;
715     }
716
717     @CalledWithWriteLock
718     protected abstract void apply(@NotNull List<UnifiedDiffChange> changes);
719   }
720
721   private class ReplaceSelectedChangesAction extends ApplySelectedChangesActionBase {
722     public ReplaceSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
723       super(focusedSide.other(), shortcut);
724
725       setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.ApplyLeftSide", "Diff.ApplyRightSide")).getShortcutSet());
726       getTemplatePresentation().setText("Replace");
727       getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRight, AllIcons.Diff.Arrow));
728     }
729
730     @Override
731     protected void apply(@NotNull List<UnifiedDiffChange> changes) {
732       for (UnifiedDiffChange change : changes) {
733         replaceChange(change, myModifiedSide.other());
734       }
735     }
736   }
737
738   private class AppendSelectedChangesAction extends ApplySelectedChangesActionBase {
739     public AppendSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
740       super(focusedSide.other(), shortcut);
741
742       setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.AppendLeftSide", "Diff.AppendRightSide")).getShortcutSet());
743       getTemplatePresentation().setText("Insert");
744       getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRightDown, AllIcons.Diff.ArrowLeftDown));
745     }
746
747     @Override
748     protected void apply(@NotNull List<UnifiedDiffChange> changes) {
749       for (UnifiedDiffChange change : changes) {
750         appendChange(change, myModifiedSide.other());
751       }
752     }
753   }
754
755   private class RevertSelectedChangesAction extends ApplySelectedChangesActionBase {
756     public RevertSelectedChangesAction(@NotNull Side focusedSide) {
757       super(focusedSide, false);
758       getTemplatePresentation().setText("Revert");
759       getTemplatePresentation().setIcon(AllIcons.Diff.Remove);
760     }
761
762     @Override
763     protected void apply(@NotNull List<UnifiedDiffChange> changes) {
764       for (UnifiedDiffChange change : changes) {
765         replaceChange(change, myModifiedSide.other());
766       }
767     }
768   }
769
770   @CalledWithWriteLock
771   public void replaceChange(@NotNull UnifiedDiffChange change, @NotNull Side sourceSide) {
772     Side outputSide = sourceSide.other();
773
774     Document document1 = getDocument(Side.LEFT);
775     Document document2 = getDocument(Side.RIGHT);
776
777     LineFragment lineFragment = change.getLineFragment();
778
779     DiffUtil.applyModification(outputSide.select(document1, document2),
780                                outputSide.getStartLine(lineFragment), outputSide.getEndLine(lineFragment),
781                                sourceSide.select(document1, document2),
782                                sourceSide.getStartLine(lineFragment), sourceSide.getEndLine(lineFragment));
783
784     // no need to mark myStateIsOutOfDate - it will be made by DocumentListener
785     // TODO: we can apply change manually, without marking state out-of-date. But we'll have to schedule rediff anyway.
786   }
787
788   @CalledWithWriteLock
789   public void appendChange(@NotNull UnifiedDiffChange change, @NotNull final Side sourceSide) {
790     Side outputSide = sourceSide.other();
791
792     Document document1 = getDocument(Side.LEFT);
793     Document document2 = getDocument(Side.RIGHT);
794
795     LineFragment lineFragment = change.getLineFragment();
796     if (sourceSide.getStartLine(lineFragment) == sourceSide.getEndLine(lineFragment)) return;
797
798     DiffUtil.applyModification(outputSide.select(document1, document2),
799                                outputSide.getEndLine(lineFragment), outputSide.getEndLine(lineFragment),
800                                sourceSide.select(document1, document2),
801                                sourceSide.getStartLine(lineFragment), sourceSide.getEndLine(lineFragment));
802   }
803
804   //
805   // Impl
806   //
807
808
809   @NotNull
810   public TextDiffSettingsHolder.TextDiffSettings getTextSettings() {
811     return TextDiffViewerUtil.getTextSettings(myContext);
812   }
813
814   @NotNull
815   public FoldingModelSupport.Settings getFoldingModelSettings() {
816     return TextDiffViewerUtil.getFoldingModelSettings(myContext);
817   }
818
819   @NotNull
820   private DiffUtil.DiffConfig getDiffConfig() {
821     return new DiffUtil.DiffConfig(getIgnorePolicy(), getHighlightPolicy());
822   }
823
824   @NotNull
825   private HighlightPolicy getHighlightPolicy() {
826     HighlightPolicy policy = getTextSettings().getHighlightPolicy();
827     if (policy == HighlightPolicy.DO_NOT_HIGHLIGHT) return HighlightPolicy.BY_LINE;
828     return policy;
829   }
830
831   @NotNull
832   private IgnorePolicy getIgnorePolicy() {
833     IgnorePolicy policy = getTextSettings().getIgnorePolicy();
834     if (policy == IgnorePolicy.IGNORE_WHITESPACES_CHUNKS) return IgnorePolicy.IGNORE_WHITESPACES;
835     return policy;
836   }
837
838   //
839   // Getters
840   //
841
842
843   @NotNull
844   public Side getMasterSide() {
845     return myMasterSide;
846   }
847
848   @NotNull
849   public EditorEx getEditor() {
850     return myEditor;
851   }
852
853   @NotNull
854   protected List<? extends EditorEx> getEditors() {
855     return Collections.singletonList(myEditor);
856   }
857
858   @NotNull
859   protected List<? extends DocumentContent> getContents() {
860     //noinspection unchecked
861     return (List<? extends DocumentContent>)(List)myRequest.getContents();
862   }
863
864   @NotNull
865   protected DocumentContent getContent(@NotNull Side side) {
866     return side.select(getContents());
867   }
868
869   @NotNull
870   protected DocumentContent getContent1() {
871     return getContent(Side.LEFT);
872   }
873
874   @NotNull
875   protected DocumentContent getContent2() {
876     return getContent(Side.RIGHT);
877   }
878
879   @CalledInAwt
880   @Nullable
881   protected List<UnifiedDiffChange> getDiffChanges() {
882     return myChangedBlockData == null ? null : myChangedBlockData.getDiffChanges();
883   }
884
885   @NotNull
886   @Override
887   public JComponent getComponent() {
888     return myPanel;
889   }
890
891   @Nullable
892   @Override
893   public JComponent getPreferredFocusedComponent() {
894     if (!myPanel.isGoodContent()) return null;
895     return myEditor.getContentComponent();
896   }
897
898   @NotNull
899   @Override
900   protected JComponent getStatusPanel() {
901     return myStatusPanel;
902   }
903
904   @CalledInAwt
905   public boolean isEditable(@NotNull Side side, boolean respectReadOnlyLock) {
906     if (myReadOnlyLockSet && respectReadOnlyLock) return false;
907     if (side.select(myForceReadOnlyFlags)) return false;
908     return DiffUtil.canMakeWritable(getDocument(side));
909   }
910
911   @NotNull
912   public Document getDocument(@NotNull Side side) {
913     return getContent(side).getDocument();
914   }
915
916   protected boolean isStateIsOutOfDate() {
917     return myStateIsOutOfDate;
918   }
919
920   //
921   // Misc
922   //
923
924   @Nullable
925   @Override
926   protected OpenFileDescriptor getOpenFileDescriptor() {
927     return getOpenFileDescriptor(myEditor.getCaretModel().getOffset());
928   }
929
930   @CalledInAwt
931   @Nullable
932   protected UnifiedDiffChange getCurrentChange() {
933     if (myChangedBlockData == null) return null;
934     int caretLine = myEditor.getCaretModel().getLogicalPosition().line;
935
936     for (UnifiedDiffChange change : myChangedBlockData.getDiffChanges()) {
937       if (DiffUtil.isSelectedByLine(caretLine, change.getLine1(), change.getLine2())) return change;
938     }
939     return null;
940   }
941
942   @NotNull
943   @CalledInAwt
944   private List<UnifiedDiffChange> getSelectedChanges() {
945     if (myChangedBlockData == null) return Collections.emptyList();
946     final BitSet lines = DiffUtil.getSelectedLines(myEditor);
947     List<UnifiedDiffChange> changes = myChangedBlockData.getDiffChanges();
948
949     List<UnifiedDiffChange> affectedChanges = new ArrayList<UnifiedDiffChange>();
950     for (int i = changes.size() - 1; i >= 0; i--) {
951       UnifiedDiffChange change = changes.get(i);
952       int line1 = change.getLine1();
953       int line2 = change.getLine2();
954
955       if (DiffUtil.isSelectedByLine(lines, line1, line2)) {
956         affectedChanges.add(change);
957       }
958     }
959     return affectedChanges;
960   }
961
962   @CalledInAwt
963   @Nullable
964   protected OpenFileDescriptor getOpenFileDescriptor(int offset) {
965     LogicalPosition position = myEditor.offsetToLogicalPosition(offset);
966     Pair<int[], Side> pair = transferLineFromOneside(position.line);
967     int offset1 = DiffUtil.getOffset(getContent1().getDocument(), pair.first[0], position.column);
968     int offset2 = DiffUtil.getOffset(getContent2().getDocument(), pair.first[1], position.column);
969
970     // TODO: issue: non-optimal GoToSource position with caret on deleted block for "Compare with local"
971     //       we should transfer using calculated diff, not jump to "somehow related" position from old content's descriptor
972
973     OpenFileDescriptor descriptor1 = getContent1().getOpenFileDescriptor(offset1);
974     OpenFileDescriptor descriptor2 = getContent2().getOpenFileDescriptor(offset2);
975     if (descriptor1 == null) return descriptor2;
976     if (descriptor2 == null) return descriptor1;
977     return pair.second.select(descriptor1, descriptor2);
978   }
979
980   public static boolean canShowRequest(@NotNull DiffContext context, @NotNull DiffRequest request) {
981     return TwosideTextDiffViewer.canShowRequest(context, request);
982   }
983
984   //
985   // Actions
986   //
987
988   private class MyPrevNextDifferenceIterable extends PrevNextDifferenceIterableBase<UnifiedDiffChange> {
989     @NotNull
990     @Override
991     protected List<UnifiedDiffChange> getChanges() {
992       return ContainerUtil.notNullize(getDiffChanges());
993     }
994
995     @NotNull
996     @Override
997     protected EditorEx getEditor() {
998       return myEditor;
999     }
1000
1001     @Override
1002     protected int getStartLine(@NotNull UnifiedDiffChange change) {
1003       return change.getLine1();
1004     }
1005
1006     @Override
1007     protected int getEndLine(@NotNull UnifiedDiffChange change) {
1008       return change.getLine2();
1009     }
1010
1011     @Override
1012     protected void scrollToChange(@NotNull UnifiedDiffChange change) {
1013       DiffUtil.scrollEditor(myEditor, change.getLine1(), true);
1014     }
1015   }
1016
1017   private class MyOpenInEditorWithMouseAction extends OpenInEditorWithMouseAction {
1018     @Override
1019     protected OpenFileDescriptor getDescriptor(@NotNull Editor editor, int line) {
1020       if (editor != myEditor) return null;
1021
1022       return getOpenFileDescriptor(myEditor.logicalPositionToOffset(new LogicalPosition(line, 0)));
1023     }
1024   }
1025
1026   private class MyToggleExpandByDefaultAction extends TextDiffViewerUtil.ToggleExpandByDefaultAction {
1027     public MyToggleExpandByDefaultAction() {
1028       super(getTextSettings());
1029     }
1030
1031     @Override
1032     protected void expandAll(boolean expand) {
1033       myFoldingModel.expandAll(expand);
1034     }
1035   }
1036
1037   private class MyHighlightPolicySettingAction extends TextDiffViewerUtil.HighlightPolicySettingAction {
1038     public MyHighlightPolicySettingAction() {
1039       super(getTextSettings());
1040     }
1041
1042     @NotNull
1043     @Override
1044     protected HighlightPolicy getCurrentSetting() {
1045       return getHighlightPolicy();
1046     }
1047
1048     @NotNull
1049     @Override
1050     protected List<HighlightPolicy> getAvailableSettings() {
1051       ArrayList<HighlightPolicy> settings = ContainerUtil.newArrayList(HighlightPolicy.values());
1052       settings.remove(HighlightPolicy.DO_NOT_HIGHLIGHT);
1053       return settings;
1054     }
1055
1056     @Override
1057     protected void onSettingsChanged() {
1058       rediff();
1059     }
1060   }
1061
1062   private class MyIgnorePolicySettingAction extends TextDiffViewerUtil.IgnorePolicySettingAction {
1063     public MyIgnorePolicySettingAction() {
1064       super(getTextSettings());
1065     }
1066
1067     @NotNull
1068     @Override
1069     protected IgnorePolicy getCurrentSetting() {
1070       return getIgnorePolicy();
1071     }
1072
1073     @NotNull
1074     @Override
1075     protected List<IgnorePolicy> getAvailableSettings() {
1076       ArrayList<IgnorePolicy> settings = ContainerUtil.newArrayList(IgnorePolicy.values());
1077       settings.remove(IgnorePolicy.IGNORE_WHITESPACES_CHUNKS);
1078       return settings;
1079     }
1080
1081     @Override
1082     protected void onSettingsChanged() {
1083       rediff();
1084     }
1085   }
1086
1087   private class MyReadOnlyLockAction extends TextDiffViewerUtil.ReadOnlyLockAction {
1088     public MyReadOnlyLockAction() {
1089       super(getContext());
1090       init();
1091     }
1092
1093     @Override
1094     protected void doApply(boolean readOnly) {
1095       myReadOnlyLockSet = readOnly;
1096       if (myChangedBlockData != null) {
1097         for (UnifiedDiffChange unifiedDiffChange : myChangedBlockData.getDiffChanges()) {
1098           unifiedDiffChange.updateGutterActions();
1099         }
1100       }
1101       updateEditorCanBeTyped();
1102     }
1103
1104     @Override
1105     protected boolean canEdit() {
1106       return !myForceReadOnlyFlags[0] && DiffUtil.canMakeWritable(getContent1().getDocument()) ||
1107              !myForceReadOnlyFlags[1] && DiffUtil.canMakeWritable(getContent2().getDocument());
1108     }
1109   }
1110
1111   //
1112   // Scroll from annotate
1113   //
1114
1115   private class AllLinesIterator implements Iterator<Pair<Integer, CharSequence>> {
1116     @NotNull private final Side mySide;
1117     @NotNull private final Document myDocument;
1118     private int myLine = 0;
1119
1120     private AllLinesIterator(@NotNull Side side) {
1121       mySide = side;
1122
1123       myDocument = getContent(mySide).getDocument();
1124     }
1125
1126     @Override
1127     public boolean hasNext() {
1128       return myLine < getLineCount(myDocument);
1129     }
1130
1131     @Override
1132     public Pair<Integer, CharSequence> next() {
1133       int offset1 = myDocument.getLineStartOffset(myLine);
1134       int offset2 = myDocument.getLineEndOffset(myLine);
1135
1136       CharSequence text = myDocument.getImmutableCharSequence().subSequence(offset1, offset2);
1137
1138       Pair<Integer, CharSequence> pair = new Pair<Integer, CharSequence>(myLine, text);
1139       myLine++;
1140
1141       return pair;
1142     }
1143
1144     @Override
1145     public void remove() {
1146       throw new UnsupportedOperationException();
1147     }
1148   }
1149
1150   private class ChangedLinesIterator extends BufferedLineIterator {
1151     @NotNull private final Side mySide;
1152     @NotNull private final List<UnifiedDiffChange> myChanges;
1153
1154     private int myIndex = 0;
1155
1156     private ChangedLinesIterator(@NotNull Side side, @NotNull List<UnifiedDiffChange> changes) {
1157       mySide = side;
1158       myChanges = changes;
1159       init();
1160     }
1161
1162     @Override
1163     public boolean hasNextBlock() {
1164       return myIndex < myChanges.size();
1165     }
1166
1167     @Override
1168     public void loadNextBlock() {
1169       LOG.assertTrue(!myStateIsOutOfDate);
1170
1171       UnifiedDiffChange change = myChanges.get(myIndex);
1172       myIndex++;
1173
1174       LineFragment lineFragment = change.getLineFragment();
1175
1176       int insertedStart = lineFragment.getStartOffset2();
1177       int insertedEnd = lineFragment.getEndOffset2();
1178       CharSequence insertedText = getContent(mySide).getDocument().getCharsSequence().subSequence(insertedStart, insertedEnd);
1179
1180       int lineNumber = lineFragment.getStartLine2();
1181
1182       LineTokenizer tokenizer = new LineTokenizer(insertedText.toString());
1183       for (String line : tokenizer.execute()) {
1184         addLine(lineNumber, line);
1185         lineNumber++;
1186       }
1187     }
1188   }
1189
1190   //
1191   // Helpers
1192   //
1193
1194   @Nullable
1195   @Override
1196   public Object getData(@NonNls String dataId) {
1197     if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
1198       return myPrevNextDifferenceIterable;
1199     }
1200     else if (CommonDataKeys.VIRTUAL_FILE.is(dataId)) {
1201       return DiffUtil.getVirtualFile(myRequest, myMasterSide);
1202     }
1203     else if (DiffDataKeys.CURRENT_EDITOR.is(dataId)) {
1204       return myEditor;
1205     }
1206     else if (DiffDataKeys.CURRENT_CHANGE_RANGE.is(dataId)) {
1207       UnifiedDiffChange change = getCurrentChange();
1208       if (change != null) {
1209         return new LineRange(change.getLine1(), change.getLine2());
1210       }
1211     }
1212     return super.getData(dataId);
1213   }
1214
1215   private class MyStatusPanel extends StatusPanel {
1216     @Override
1217     protected int getChangesCount() {
1218       return myChangedBlockData == null ? 0 : myChangedBlockData.getDiffChanges().size();
1219     }
1220   }
1221
1222   private static class TwosideDocumentData {
1223     @NotNull private final UnifiedFragmentBuilder myBuilder;
1224     @Nullable private final EditorHighlighter myHighlighter;
1225     @Nullable private final UnifiedEditorRangeHighlighter myRangeHighlighter;
1226
1227     public TwosideDocumentData(@NotNull UnifiedFragmentBuilder builder,
1228                                @Nullable EditorHighlighter highlighter,
1229                                @Nullable UnifiedEditorRangeHighlighter rangeHighlighter) {
1230       myBuilder = builder;
1231       myHighlighter = highlighter;
1232       myRangeHighlighter = rangeHighlighter;
1233     }
1234
1235     @NotNull
1236     public UnifiedFragmentBuilder getBuilder() {
1237       return myBuilder;
1238     }
1239
1240     @Nullable
1241     public EditorHighlighter getHighlighter() {
1242       return myHighlighter;
1243     }
1244
1245     @Nullable
1246     public UnifiedEditorRangeHighlighter getRangeHighlighter() {
1247       return myRangeHighlighter;
1248     }
1249   }
1250
1251   private static class ChangedBlockData {
1252     @NotNull private final List<UnifiedDiffChange> myDiffChanges;
1253     @NotNull private final List<RangeMarker> myGuardedRangeBlocks;
1254     @NotNull private final LineNumberConvertor myLineNumberConvertor;
1255
1256     public ChangedBlockData(@NotNull List<UnifiedDiffChange> diffChanges,
1257                             @NotNull List<RangeMarker> guarderRangeBlocks,
1258                             @NotNull LineNumberConvertor lineNumberConvertor) {
1259       myDiffChanges = diffChanges;
1260       myGuardedRangeBlocks = guarderRangeBlocks;
1261       myLineNumberConvertor = lineNumberConvertor;
1262     }
1263
1264     @NotNull
1265     public List<UnifiedDiffChange> getDiffChanges() {
1266       return myDiffChanges;
1267     }
1268
1269     @NotNull
1270     public List<RangeMarker> getGuardedRangeBlocks() {
1271       return myGuardedRangeBlocks;
1272     }
1273
1274     @NotNull
1275     public LineNumberConvertor getLineNumberConvertor() {
1276       return myLineNumberConvertor;
1277     }
1278   }
1279
1280   private static class CombinedEditorData {
1281     @NotNull private final CharSequence myText;
1282     @Nullable private final EditorHighlighter myHighlighter;
1283     @Nullable private final UnifiedEditorRangeHighlighter myRangeHighlighter;
1284     @Nullable private final FileType myFileType;
1285     @NotNull private final TIntFunction myLineConvertor1;
1286     @NotNull private final TIntFunction myLineConvertor2;
1287
1288     public CombinedEditorData(@NotNull CharSequence text,
1289                               @Nullable EditorHighlighter highlighter,
1290                               @Nullable UnifiedEditorRangeHighlighter rangeHighlighter,
1291                               @Nullable FileType fileType,
1292                               @NotNull TIntFunction convertor1,
1293                               @NotNull TIntFunction convertor2) {
1294       myText = text;
1295       myHighlighter = highlighter;
1296       myRangeHighlighter = rangeHighlighter;
1297       myFileType = fileType;
1298       myLineConvertor1 = convertor1;
1299       myLineConvertor2 = convertor2;
1300     }
1301
1302     @NotNull
1303     public CharSequence getText() {
1304       return myText;
1305     }
1306
1307     @Nullable
1308     public EditorHighlighter getHighlighter() {
1309       return myHighlighter;
1310     }
1311
1312     @Nullable
1313     public UnifiedEditorRangeHighlighter getRangeHighlighter() {
1314       return myRangeHighlighter;
1315     }
1316
1317     @Nullable
1318     public FileType getFileType() {
1319       return myFileType;
1320     }
1321
1322     @NotNull
1323     public TIntFunction getLineConvertor1() {
1324       return myLineConvertor1;
1325     }
1326
1327     @NotNull
1328     public TIntFunction getLineConvertor2() {
1329       return myLineConvertor2;
1330     }
1331   }
1332
1333   private class MyInitialScrollHelper extends InitialScrollPositionSupport.TwosideInitialScrollHelper {
1334     @NotNull
1335     @Override
1336     protected List<? extends Editor> getEditors() {
1337       return UnifiedDiffViewer.this.getEditors();
1338     }
1339
1340     @Override
1341     protected void disableSyncScroll(boolean value) {
1342     }
1343
1344     @Override
1345     public void onSlowRediff() {
1346       // Will not happen for initial rediff
1347     }
1348
1349     @Nullable
1350     @Override
1351     protected LogicalPosition[] getCaretPositions() {
1352       LogicalPosition position = myEditor.getCaretModel().getLogicalPosition();
1353       Pair<int[], Side> pair = transferLineFromOneside(position.line);
1354       LogicalPosition[] carets = new LogicalPosition[2];
1355       carets[0] = getPosition(pair.first[0], position.column);
1356       carets[1] = getPosition(pair.first[1], position.column);
1357       return carets;
1358     }
1359
1360     @Override
1361     protected boolean doScrollToPosition() {
1362       if (myCaretPosition == null) return false;
1363
1364       LogicalPosition twosidePosition = myMasterSide.selectNotNull(myCaretPosition);
1365       int onesideLine = transferLineToOneside(myMasterSide, twosidePosition.line);
1366       LogicalPosition position = new LogicalPosition(onesideLine, twosidePosition.column);
1367
1368       myEditor.getCaretModel().moveToLogicalPosition(position);
1369
1370       if (myEditorsPosition != null && myEditorsPosition.isSame(position)) {
1371         DiffUtil.scrollToPoint(myEditor, myEditorsPosition.myPoints[0], false);
1372       }
1373       else {
1374         DiffUtil.scrollToCaret(myEditor, false);
1375       }
1376       return true;
1377     }
1378
1379     @NotNull
1380     private LogicalPosition getPosition(int line, int column) {
1381       if (line == -1) return new LogicalPosition(0, 0);
1382       return new LogicalPosition(line, column);
1383     }
1384
1385     private void doScrollToLine(@NotNull Side side, @NotNull LogicalPosition position) {
1386       int onesideLine = transferLineToOneside(side, position.line);
1387       DiffUtil.scrollEditor(myEditor, onesideLine, position.column, false);
1388     }
1389
1390     @Override
1391     protected boolean doScrollToLine() {
1392       if (myScrollToLine == null) return false;
1393       doScrollToLine(myScrollToLine.first, new LogicalPosition(myScrollToLine.second, 0));
1394       return true;
1395     }
1396
1397     private boolean doScrollToChange(@NotNull ScrollToPolicy scrollToChangePolicy) {
1398       if (myChangedBlockData == null) return false;
1399       List<UnifiedDiffChange> changes = myChangedBlockData.getDiffChanges();
1400
1401       UnifiedDiffChange targetChange = scrollToChangePolicy.select(changes);
1402       if (targetChange == null) return false;
1403
1404       DiffUtil.scrollEditor(myEditor, targetChange.getLine1(), false);
1405       return true;
1406     }
1407
1408     @Override
1409     protected boolean doScrollToChange() {
1410       if (myScrollToChange == null) return false;
1411       return doScrollToChange(myScrollToChange);
1412     }
1413
1414     @Override
1415     protected boolean doScrollToFirstChange() {
1416       return doScrollToChange(ScrollToPolicy.FIRST_CHANGE);
1417     }
1418
1419     @Override
1420     protected boolean doScrollToContext() {
1421       if (myNavigationContext == null) return false;
1422       if (myChangedBlockData == null) return false;
1423
1424       ChangedLinesIterator changedLinesIterator = new ChangedLinesIterator(Side.RIGHT, myChangedBlockData.getDiffChanges());
1425       NavigationContextChecker checker = new NavigationContextChecker(changedLinesIterator, myNavigationContext);
1426       int line = checker.contextMatchCheck();
1427       if (line == -1) {
1428         // this will work for the case, when spaces changes are ignored, and corresponding fragments are not reported as changed
1429         // just try to find target line  -> +-
1430         AllLinesIterator allLinesIterator = new AllLinesIterator(Side.RIGHT);
1431         NavigationContextChecker checker2 = new NavigationContextChecker(allLinesIterator, myNavigationContext);
1432         line = checker2.contextMatchCheck();
1433       }
1434       if (line == -1) return false;
1435
1436       doScrollToLine(Side.RIGHT, new LogicalPosition(line, 0));
1437       return true;
1438     }
1439   }
1440
1441   private static class MyFoldingModel extends FoldingModelSupport {
1442     public MyFoldingModel(@NotNull EditorEx editor, @NotNull Disposable disposable) {
1443       super(new EditorEx[]{editor}, disposable);
1444     }
1445
1446     public void install(@Nullable List<LineRange> changedLines,
1447                         @NotNull UserDataHolder context,
1448                         @NotNull FoldingModelSupport.Settings settings) {
1449       Iterator<int[]> it = map(changedLines, new Function<LineRange, int[]>() {
1450         @Override
1451         public int[] fun(LineRange line) {
1452           return new int[]{
1453             line.start,
1454             line.end};
1455         }
1456       });
1457       install(it, context, settings);
1458     }
1459
1460     @NotNull
1461     public TIntFunction getLineNumberConvertor() {
1462       return getLineConvertor(0);
1463     }
1464   }
1465
1466   private static class MyReadonlyFragmentModificationHandler implements ReadonlyFragmentModificationHandler {
1467     @Override
1468     public void handle(ReadOnlyFragmentModificationException e) {
1469       // do nothing
1470     }
1471   }
1472 }