d54b5158f70f2cd2c0b755dafdbcddd80be680fd
[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   protected 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   protected 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   protected 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   protected 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   @NotNull
843   public EditorEx getEditor() {
844     return myEditor;
845   }
846
847   @NotNull
848   protected List<? extends EditorEx> getEditors() {
849     return Collections.singletonList(myEditor);
850   }
851
852   @NotNull
853   protected List<? extends DocumentContent> getContents() {
854     //noinspection unchecked
855     return (List<? extends DocumentContent>)(List)myRequest.getContents();
856   }
857
858   @NotNull
859   protected DocumentContent getContent(@NotNull Side side) {
860     return side.select(getContents());
861   }
862
863   @NotNull
864   protected DocumentContent getContent1() {
865     return getContent(Side.LEFT);
866   }
867
868   @NotNull
869   protected DocumentContent getContent2() {
870     return getContent(Side.RIGHT);
871   }
872
873   @CalledInAwt
874   @Nullable
875   protected List<UnifiedDiffChange> getDiffChanges() {
876     return myChangedBlockData == null ? null : myChangedBlockData.getDiffChanges();
877   }
878
879   @NotNull
880   @Override
881   public JComponent getComponent() {
882     return myPanel;
883   }
884
885   @Nullable
886   @Override
887   public JComponent getPreferredFocusedComponent() {
888     if (!myPanel.isGoodContent()) return null;
889     return myEditor.getContentComponent();
890   }
891
892   @NotNull
893   @Override
894   protected JComponent getStatusPanel() {
895     return myStatusPanel;
896   }
897
898   @CalledInAwt
899   public boolean isEditable(@NotNull Side side, boolean respectReadOnlyLock) {
900     if (myReadOnlyLockSet && respectReadOnlyLock) return false;
901     if (side.select(myForceReadOnlyFlags)) return false;
902     return DiffUtil.canMakeWritable(getDocument(side));
903   }
904
905   @NotNull
906   public Document getDocument(@NotNull Side side) {
907     return getContent(side).getDocument();
908   }
909
910   protected boolean isStateIsOutOfDate() {
911     return myStateIsOutOfDate;
912   }
913
914   //
915   // Misc
916   //
917
918   @Nullable
919   @Override
920   protected OpenFileDescriptor getOpenFileDescriptor() {
921     return getOpenFileDescriptor(myEditor.getCaretModel().getOffset());
922   }
923
924   @CalledInAwt
925   @Nullable
926   protected UnifiedDiffChange getCurrentChange() {
927     if (myChangedBlockData == null) return null;
928     int caretLine = myEditor.getCaretModel().getLogicalPosition().line;
929
930     for (UnifiedDiffChange change : myChangedBlockData.getDiffChanges()) {
931       if (DiffUtil.isSelectedByLine(caretLine, change.getLine1(), change.getLine2())) return change;
932     }
933     return null;
934   }
935
936   @NotNull
937   @CalledInAwt
938   private List<UnifiedDiffChange> getSelectedChanges() {
939     if (myChangedBlockData == null) return Collections.emptyList();
940     final BitSet lines = DiffUtil.getSelectedLines(myEditor);
941     List<UnifiedDiffChange> changes = myChangedBlockData.getDiffChanges();
942
943     List<UnifiedDiffChange> affectedChanges = new ArrayList<UnifiedDiffChange>();
944     for (int i = changes.size() - 1; i >= 0; i--) {
945       UnifiedDiffChange change = changes.get(i);
946       int line1 = change.getLine1();
947       int line2 = change.getLine2();
948
949       if (DiffUtil.isSelectedByLine(lines, line1, line2)) {
950         affectedChanges.add(change);
951       }
952     }
953     return affectedChanges;
954   }
955
956   @CalledInAwt
957   @Nullable
958   protected OpenFileDescriptor getOpenFileDescriptor(int offset) {
959     LogicalPosition position = myEditor.offsetToLogicalPosition(offset);
960     Pair<int[], Side> pair = transferLineFromOneside(position.line);
961     int offset1 = DiffUtil.getOffset(getContent1().getDocument(), pair.first[0], position.column);
962     int offset2 = DiffUtil.getOffset(getContent2().getDocument(), pair.first[1], position.column);
963
964     // TODO: issue: non-optimal GoToSource position with caret on deleted block for "Compare with local"
965     //       we should transfer using calculated diff, not jump to "somehow related" position from old content's descriptor
966
967     OpenFileDescriptor descriptor1 = getContent1().getOpenFileDescriptor(offset1);
968     OpenFileDescriptor descriptor2 = getContent2().getOpenFileDescriptor(offset2);
969     if (descriptor1 == null) return descriptor2;
970     if (descriptor2 == null) return descriptor1;
971     return pair.second.select(descriptor1, descriptor2);
972   }
973
974   public static boolean canShowRequest(@NotNull DiffContext context, @NotNull DiffRequest request) {
975     return TwosideTextDiffViewer.canShowRequest(context, request);
976   }
977
978   //
979   // Actions
980   //
981
982   private class MyPrevNextDifferenceIterable extends PrevNextDifferenceIterableBase<UnifiedDiffChange> {
983     @NotNull
984     @Override
985     protected List<UnifiedDiffChange> getChanges() {
986       return ContainerUtil.notNullize(getDiffChanges());
987     }
988
989     @NotNull
990     @Override
991     protected EditorEx getEditor() {
992       return myEditor;
993     }
994
995     @Override
996     protected int getStartLine(@NotNull UnifiedDiffChange change) {
997       return change.getLine1();
998     }
999
1000     @Override
1001     protected int getEndLine(@NotNull UnifiedDiffChange change) {
1002       return change.getLine2();
1003     }
1004
1005     @Override
1006     protected void scrollToChange(@NotNull UnifiedDiffChange change) {
1007       DiffUtil.scrollEditor(myEditor, change.getLine1(), true);
1008     }
1009   }
1010
1011   private class MyOpenInEditorWithMouseAction extends OpenInEditorWithMouseAction {
1012     @Override
1013     protected OpenFileDescriptor getDescriptor(@NotNull Editor editor, int line) {
1014       if (editor != myEditor) return null;
1015
1016       return getOpenFileDescriptor(myEditor.logicalPositionToOffset(new LogicalPosition(line, 0)));
1017     }
1018   }
1019
1020   private class MyToggleExpandByDefaultAction extends TextDiffViewerUtil.ToggleExpandByDefaultAction {
1021     public MyToggleExpandByDefaultAction() {
1022       super(getTextSettings());
1023     }
1024
1025     @Override
1026     protected void expandAll(boolean expand) {
1027       myFoldingModel.expandAll(expand);
1028     }
1029   }
1030
1031   private class MyHighlightPolicySettingAction extends TextDiffViewerUtil.HighlightPolicySettingAction {
1032     public MyHighlightPolicySettingAction() {
1033       super(getTextSettings());
1034     }
1035
1036     @NotNull
1037     @Override
1038     protected HighlightPolicy getCurrentSetting() {
1039       return getHighlightPolicy();
1040     }
1041
1042     @NotNull
1043     @Override
1044     protected List<HighlightPolicy> getAvailableSettings() {
1045       ArrayList<HighlightPolicy> settings = ContainerUtil.newArrayList(HighlightPolicy.values());
1046       settings.remove(HighlightPolicy.DO_NOT_HIGHLIGHT);
1047       return settings;
1048     }
1049
1050     @Override
1051     protected void onSettingsChanged() {
1052       rediff();
1053     }
1054   }
1055
1056   private class MyIgnorePolicySettingAction extends TextDiffViewerUtil.IgnorePolicySettingAction {
1057     public MyIgnorePolicySettingAction() {
1058       super(getTextSettings());
1059     }
1060
1061     @NotNull
1062     @Override
1063     protected IgnorePolicy getCurrentSetting() {
1064       return getIgnorePolicy();
1065     }
1066
1067     @NotNull
1068     @Override
1069     protected List<IgnorePolicy> getAvailableSettings() {
1070       ArrayList<IgnorePolicy> settings = ContainerUtil.newArrayList(IgnorePolicy.values());
1071       settings.remove(IgnorePolicy.IGNORE_WHITESPACES_CHUNKS);
1072       return settings;
1073     }
1074
1075     @Override
1076     protected void onSettingsChanged() {
1077       rediff();
1078     }
1079   }
1080
1081   private class MyReadOnlyLockAction extends TextDiffViewerUtil.ReadOnlyLockAction {
1082     public MyReadOnlyLockAction() {
1083       super(getContext());
1084       init();
1085     }
1086
1087     @Override
1088     protected void doApply(boolean readOnly) {
1089       myReadOnlyLockSet = readOnly;
1090       if (myChangedBlockData != null) {
1091         for (UnifiedDiffChange unifiedDiffChange : myChangedBlockData.getDiffChanges()) {
1092           unifiedDiffChange.updateGutterActions();
1093         }
1094       }
1095       updateEditorCanBeTyped();
1096     }
1097
1098     @Override
1099     protected boolean canEdit() {
1100       return !myForceReadOnlyFlags[0] && DiffUtil.canMakeWritable(getContent1().getDocument()) ||
1101              !myForceReadOnlyFlags[1] && DiffUtil.canMakeWritable(getContent2().getDocument());
1102     }
1103   }
1104
1105   //
1106   // Scroll from annotate
1107   //
1108
1109   private class AllLinesIterator implements Iterator<Pair<Integer, CharSequence>> {
1110     @NotNull private final Side mySide;
1111     @NotNull private final Document myDocument;
1112     private int myLine = 0;
1113
1114     private AllLinesIterator(@NotNull Side side) {
1115       mySide = side;
1116
1117       myDocument = getContent(mySide).getDocument();
1118     }
1119
1120     @Override
1121     public boolean hasNext() {
1122       return myLine < getLineCount(myDocument);
1123     }
1124
1125     @Override
1126     public Pair<Integer, CharSequence> next() {
1127       int offset1 = myDocument.getLineStartOffset(myLine);
1128       int offset2 = myDocument.getLineEndOffset(myLine);
1129
1130       CharSequence text = myDocument.getImmutableCharSequence().subSequence(offset1, offset2);
1131
1132       Pair<Integer, CharSequence> pair = new Pair<Integer, CharSequence>(myLine, text);
1133       myLine++;
1134
1135       return pair;
1136     }
1137
1138     @Override
1139     public void remove() {
1140       throw new UnsupportedOperationException();
1141     }
1142   }
1143
1144   private class ChangedLinesIterator extends BufferedLineIterator {
1145     @NotNull private final Side mySide;
1146     @NotNull private final List<UnifiedDiffChange> myChanges;
1147
1148     private int myIndex = 0;
1149
1150     private ChangedLinesIterator(@NotNull Side side, @NotNull List<UnifiedDiffChange> changes) {
1151       mySide = side;
1152       myChanges = changes;
1153       init();
1154     }
1155
1156     @Override
1157     public boolean hasNextBlock() {
1158       return myIndex < myChanges.size();
1159     }
1160
1161     @Override
1162     public void loadNextBlock() {
1163       LOG.assertTrue(!myStateIsOutOfDate);
1164
1165       UnifiedDiffChange change = myChanges.get(myIndex);
1166       myIndex++;
1167
1168       LineFragment lineFragment = change.getLineFragment();
1169
1170       int insertedStart = lineFragment.getStartOffset2();
1171       int insertedEnd = lineFragment.getEndOffset2();
1172       CharSequence insertedText = getContent(mySide).getDocument().getCharsSequence().subSequence(insertedStart, insertedEnd);
1173
1174       int lineNumber = lineFragment.getStartLine2();
1175
1176       LineTokenizer tokenizer = new LineTokenizer(insertedText.toString());
1177       for (String line : tokenizer.execute()) {
1178         addLine(lineNumber, line);
1179         lineNumber++;
1180       }
1181     }
1182   }
1183
1184   //
1185   // Helpers
1186   //
1187
1188   @Nullable
1189   @Override
1190   public Object getData(@NonNls String dataId) {
1191     if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
1192       return myPrevNextDifferenceIterable;
1193     }
1194     else if (CommonDataKeys.VIRTUAL_FILE.is(dataId)) {
1195       return DiffUtil.getVirtualFile(myRequest, myMasterSide);
1196     }
1197     else if (DiffDataKeys.CURRENT_EDITOR.is(dataId)) {
1198       return myEditor;
1199     }
1200     else if (DiffDataKeys.CURRENT_CHANGE_RANGE.is(dataId)) {
1201       UnifiedDiffChange change = getCurrentChange();
1202       if (change != null) {
1203         return new LineRange(change.getLine1(), change.getLine2());
1204       }
1205     }
1206     return super.getData(dataId);
1207   }
1208
1209   private class MyStatusPanel extends StatusPanel {
1210     @Override
1211     protected int getChangesCount() {
1212       return myChangedBlockData == null ? 0 : myChangedBlockData.getDiffChanges().size();
1213     }
1214   }
1215
1216   private static class TwosideDocumentData {
1217     @NotNull private final UnifiedFragmentBuilder myBuilder;
1218     @Nullable private final EditorHighlighter myHighlighter;
1219     @Nullable private final UnifiedEditorRangeHighlighter myRangeHighlighter;
1220
1221     public TwosideDocumentData(@NotNull UnifiedFragmentBuilder builder,
1222                                @Nullable EditorHighlighter highlighter,
1223                                @Nullable UnifiedEditorRangeHighlighter rangeHighlighter) {
1224       myBuilder = builder;
1225       myHighlighter = highlighter;
1226       myRangeHighlighter = rangeHighlighter;
1227     }
1228
1229     @NotNull
1230     public UnifiedFragmentBuilder getBuilder() {
1231       return myBuilder;
1232     }
1233
1234     @Nullable
1235     public EditorHighlighter getHighlighter() {
1236       return myHighlighter;
1237     }
1238
1239     @Nullable
1240     public UnifiedEditorRangeHighlighter getRangeHighlighter() {
1241       return myRangeHighlighter;
1242     }
1243   }
1244
1245   private static class ChangedBlockData {
1246     @NotNull private final List<UnifiedDiffChange> myDiffChanges;
1247     @NotNull private final List<RangeMarker> myGuardedRangeBlocks;
1248     @NotNull private final LineNumberConvertor myLineNumberConvertor;
1249
1250     public ChangedBlockData(@NotNull List<UnifiedDiffChange> diffChanges,
1251                             @NotNull List<RangeMarker> guarderRangeBlocks,
1252                             @NotNull LineNumberConvertor lineNumberConvertor) {
1253       myDiffChanges = diffChanges;
1254       myGuardedRangeBlocks = guarderRangeBlocks;
1255       myLineNumberConvertor = lineNumberConvertor;
1256     }
1257
1258     @NotNull
1259     public List<UnifiedDiffChange> getDiffChanges() {
1260       return myDiffChanges;
1261     }
1262
1263     @NotNull
1264     public List<RangeMarker> getGuardedRangeBlocks() {
1265       return myGuardedRangeBlocks;
1266     }
1267
1268     @NotNull
1269     public LineNumberConvertor getLineNumberConvertor() {
1270       return myLineNumberConvertor;
1271     }
1272   }
1273
1274   private static class CombinedEditorData {
1275     @NotNull private final CharSequence myText;
1276     @Nullable private final EditorHighlighter myHighlighter;
1277     @Nullable private final UnifiedEditorRangeHighlighter myRangeHighlighter;
1278     @Nullable private final FileType myFileType;
1279     @NotNull private final TIntFunction myLineConvertor1;
1280     @NotNull private final TIntFunction myLineConvertor2;
1281
1282     public CombinedEditorData(@NotNull CharSequence text,
1283                               @Nullable EditorHighlighter highlighter,
1284                               @Nullable UnifiedEditorRangeHighlighter rangeHighlighter,
1285                               @Nullable FileType fileType,
1286                               @NotNull TIntFunction convertor1,
1287                               @NotNull TIntFunction convertor2) {
1288       myText = text;
1289       myHighlighter = highlighter;
1290       myRangeHighlighter = rangeHighlighter;
1291       myFileType = fileType;
1292       myLineConvertor1 = convertor1;
1293       myLineConvertor2 = convertor2;
1294     }
1295
1296     @NotNull
1297     public CharSequence getText() {
1298       return myText;
1299     }
1300
1301     @Nullable
1302     public EditorHighlighter getHighlighter() {
1303       return myHighlighter;
1304     }
1305
1306     @Nullable
1307     public UnifiedEditorRangeHighlighter getRangeHighlighter() {
1308       return myRangeHighlighter;
1309     }
1310
1311     @Nullable
1312     public FileType getFileType() {
1313       return myFileType;
1314     }
1315
1316     @NotNull
1317     public TIntFunction getLineConvertor1() {
1318       return myLineConvertor1;
1319     }
1320
1321     @NotNull
1322     public TIntFunction getLineConvertor2() {
1323       return myLineConvertor2;
1324     }
1325   }
1326
1327   private class MyInitialScrollHelper extends InitialScrollPositionSupport.TwosideInitialScrollHelper {
1328     @NotNull
1329     @Override
1330     protected List<? extends Editor> getEditors() {
1331       return UnifiedDiffViewer.this.getEditors();
1332     }
1333
1334     @Override
1335     protected void disableSyncScroll(boolean value) {
1336     }
1337
1338     @Override
1339     public void onSlowRediff() {
1340       // Will not happen for initial rediff
1341     }
1342
1343     @Nullable
1344     @Override
1345     protected LogicalPosition[] getCaretPositions() {
1346       LogicalPosition position = myEditor.getCaretModel().getLogicalPosition();
1347       Pair<int[], Side> pair = transferLineFromOneside(position.line);
1348       LogicalPosition[] carets = new LogicalPosition[2];
1349       carets[0] = getPosition(pair.first[0], position.column);
1350       carets[1] = getPosition(pair.first[1], position.column);
1351       return carets;
1352     }
1353
1354     @Override
1355     protected boolean doScrollToPosition() {
1356       if (myCaretPosition == null) return false;
1357
1358       LogicalPosition twosidePosition = myMasterSide.selectNotNull(myCaretPosition);
1359       int onesideLine = transferLineToOneside(myMasterSide, twosidePosition.line);
1360       LogicalPosition position = new LogicalPosition(onesideLine, twosidePosition.column);
1361
1362       myEditor.getCaretModel().moveToLogicalPosition(position);
1363
1364       if (myEditorsPosition != null && myEditorsPosition.isSame(position)) {
1365         DiffUtil.scrollToPoint(myEditor, myEditorsPosition.myPoints[0], false);
1366       }
1367       else {
1368         DiffUtil.scrollToCaret(myEditor, false);
1369       }
1370       return true;
1371     }
1372
1373     @NotNull
1374     private LogicalPosition getPosition(int line, int column) {
1375       if (line == -1) return new LogicalPosition(0, 0);
1376       return new LogicalPosition(line, column);
1377     }
1378
1379     private void doScrollToLine(@NotNull Side side, @NotNull LogicalPosition position) {
1380       int onesideLine = transferLineToOneside(side, position.line);
1381       DiffUtil.scrollEditor(myEditor, onesideLine, position.column, false);
1382     }
1383
1384     @Override
1385     protected boolean doScrollToLine() {
1386       if (myScrollToLine == null) return false;
1387       doScrollToLine(myScrollToLine.first, new LogicalPosition(myScrollToLine.second, 0));
1388       return true;
1389     }
1390
1391     private boolean doScrollToChange(@NotNull ScrollToPolicy scrollToChangePolicy) {
1392       if (myChangedBlockData == null) return false;
1393       List<UnifiedDiffChange> changes = myChangedBlockData.getDiffChanges();
1394
1395       UnifiedDiffChange targetChange = scrollToChangePolicy.select(changes);
1396       if (targetChange == null) return false;
1397
1398       DiffUtil.scrollEditor(myEditor, targetChange.getLine1(), false);
1399       return true;
1400     }
1401
1402     @Override
1403     protected boolean doScrollToChange() {
1404       if (myScrollToChange == null) return false;
1405       return doScrollToChange(myScrollToChange);
1406     }
1407
1408     @Override
1409     protected boolean doScrollToFirstChange() {
1410       return doScrollToChange(ScrollToPolicy.FIRST_CHANGE);
1411     }
1412
1413     @Override
1414     protected boolean doScrollToContext() {
1415       if (myNavigationContext == null) return false;
1416       if (myChangedBlockData == null) return false;
1417
1418       ChangedLinesIterator changedLinesIterator = new ChangedLinesIterator(Side.RIGHT, myChangedBlockData.getDiffChanges());
1419       NavigationContextChecker checker = new NavigationContextChecker(changedLinesIterator, myNavigationContext);
1420       int line = checker.contextMatchCheck();
1421       if (line == -1) {
1422         // this will work for the case, when spaces changes are ignored, and corresponding fragments are not reported as changed
1423         // just try to find target line  -> +-
1424         AllLinesIterator allLinesIterator = new AllLinesIterator(Side.RIGHT);
1425         NavigationContextChecker checker2 = new NavigationContextChecker(allLinesIterator, myNavigationContext);
1426         line = checker2.contextMatchCheck();
1427       }
1428       if (line == -1) return false;
1429
1430       doScrollToLine(Side.RIGHT, new LogicalPosition(line, 0));
1431       return true;
1432     }
1433   }
1434
1435   private static class MyFoldingModel extends FoldingModelSupport {
1436     public MyFoldingModel(@NotNull EditorEx editor, @NotNull Disposable disposable) {
1437       super(new EditorEx[]{editor}, disposable);
1438     }
1439
1440     public void install(@Nullable List<LineRange> changedLines,
1441                         @NotNull UserDataHolder context,
1442                         @NotNull FoldingModelSupport.Settings settings) {
1443       Iterator<int[]> it = map(changedLines, new Function<LineRange, int[]>() {
1444         @Override
1445         public int[] fun(LineRange line) {
1446           return new int[]{
1447             line.start,
1448             line.end};
1449         }
1450       });
1451       install(it, context, settings);
1452     }
1453
1454     @NotNull
1455     public TIntFunction getLineNumberConvertor() {
1456       return getLineConvertor(0);
1457     }
1458   }
1459
1460   private static class MyReadonlyFragmentModificationHandler implements ReadonlyFragmentModificationHandler {
1461     @Override
1462     public void handle(ReadOnlyFragmentModificationException e) {
1463       // do nothing
1464     }
1465   }
1466 }