e421492bf272955265032d62809dc082738d8560
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / diff / impl / DiffPanelImpl.java
1 /*
2  * Copyright 2000-2012 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.openapi.diff.impl;
17
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.actions.EditSourceAction;
20 import com.intellij.idea.ActionsBundle;
21 import com.intellij.openapi.Disposable;
22 import com.intellij.openapi.actionSystem.*;
23 import com.intellij.openapi.application.Application;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.ModalityState;
26 import com.intellij.openapi.components.ServiceManager;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.diff.*;
29 import com.intellij.openapi.diff.actions.MergeActionGroup;
30 import com.intellij.openapi.diff.actions.ToggleAutoScrollAction;
31 import com.intellij.openapi.diff.ex.DiffPanelEx;
32 import com.intellij.openapi.diff.ex.DiffPanelOptions;
33 import com.intellij.openapi.diff.impl.external.DiffManagerImpl;
34 import com.intellij.openapi.diff.impl.fragments.Fragment;
35 import com.intellij.openapi.diff.impl.fragments.FragmentList;
36 import com.intellij.openapi.diff.impl.highlighting.DiffPanelState;
37 import com.intellij.openapi.diff.impl.highlighting.FragmentSide;
38 import com.intellij.openapi.diff.impl.processing.HighlightMode;
39 import com.intellij.openapi.diff.impl.processing.HorizontalDiffSplitter;
40 import com.intellij.openapi.diff.impl.settings.DiffMergeEditorSetting;
41 import com.intellij.openapi.diff.impl.settings.DiffMergeSettings;
42 import com.intellij.openapi.diff.impl.settings.DiffMergeSettingsAction;
43 import com.intellij.openapi.diff.impl.settings.DiffToolSettings;
44 import com.intellij.openapi.diff.impl.splitter.DiffDividerPaint;
45 import com.intellij.openapi.diff.impl.splitter.LineBlocks;
46 import com.intellij.openapi.diff.impl.util.*;
47 import com.intellij.openapi.editor.Document;
48 import com.intellij.openapi.editor.Editor;
49 import com.intellij.openapi.editor.ScrollType;
50 import com.intellij.openapi.editor.ScrollingModel;
51 import com.intellij.openapi.editor.event.VisibleAreaEvent;
52 import com.intellij.openapi.editor.event.VisibleAreaListener;
53 import com.intellij.openapi.editor.ex.EditorEx;
54 import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
55 import com.intellij.openapi.editor.ex.EditorMarkupModel;
56 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
57 import com.intellij.openapi.fileTypes.FileType;
58 import com.intellij.openapi.project.Project;
59 import com.intellij.openapi.util.Disposer;
60 import com.intellij.openapi.util.Getter;
61 import com.intellij.openapi.util.Pair;
62 import com.intellij.openapi.util.TextRange;
63 import com.intellij.openapi.vfs.LocalFileSystem;
64 import com.intellij.openapi.vfs.VirtualFile;
65 import com.intellij.openapi.wm.IdeFocusManager;
66 import com.intellij.pom.Navigatable;
67 import com.intellij.ui.EditorNotificationPanel;
68 import com.intellij.ui.JBColor;
69 import com.intellij.ui.PopupHandler;
70 import com.intellij.ui.border.CustomLineBorder;
71 import com.intellij.ui.components.JBLabel;
72 import com.intellij.util.LineSeparator;
73 import com.intellij.util.containers.CacheOneStepIterator;
74 import com.intellij.util.containers.Convertor;
75 import com.intellij.util.diff.FilesTooBigForDiffException;
76 import com.intellij.util.ui.PlatformColors;
77 import com.intellij.util.ui.UIUtil;
78 import org.jetbrains.annotations.NotNull;
79 import org.jetbrains.annotations.Nullable;
80
81 import javax.swing.*;
82 import java.awt.*;
83 import java.awt.event.MouseListener;
84 import java.util.Arrays;
85 import java.util.Iterator;
86 import java.util.LinkedList;
87 import java.util.List;
88
89 public class DiffPanelImpl implements DiffPanelEx, ContentChangeListener, TwoSidesContainer {
90   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.DiffPanelImpl");
91
92   private final DiffSplitterI mySplitter;
93   private final DiffPanelOuterComponent myPanel;
94
95   private final Window myOwnerWindow;
96   private final DiffPanelOptions myOptions;
97
98   private final DiffPanelState myData;
99
100   private final Rediffers myDiffUpdater;
101   private final DiffSideView myLeftSide;
102   private final DiffSideView myRightSide;
103   private DiffSideView myCurrentSide;
104   private LineBlocks myLineBlocks = LineBlocks.EMPTY;
105   private final SyncScrollSupport myScrollSupport = new SyncScrollSupport();
106   private final FontSizeSynchronizer myFontSizeSynchronizer = new FontSizeSynchronizer();
107   private DiffRequest myDiffRequest;
108   private boolean myIsRequestFocus = true;
109   private boolean myIsSyncScroll;
110
111   private DiffRequest.ToolbarAddons createToolbar() {
112     return new DiffRequest.ToolbarAddons() {
113       public void customize(DiffToolbar toolbar) {
114         ActionManager actionManager = ActionManager.getInstance();
115         toolbar.addAction(actionManager.getAction("DiffPanel.Toolbar"));
116         toolbar.addSeparator();
117         toolbar.addAction(new ToggleAutoScrollAction());
118         toolbar.addSeparator();
119         toolbar.addAction(actionManager.getAction("ContextHelp"));
120         toolbar.addAction(getEditSourceAction());
121         toolbar.addSeparator();
122         toolbar.addAction(new DiffMergeSettingsAction(Arrays.asList(getEditor1(), getEditor2()),
123                                                       ServiceManager.getService(myProject, DiffToolSettings.class)));
124       }
125
126       @NotNull
127       private AnAction getEditSourceAction() {
128         AnAction editSourceAction = new EditSourceAction();
129         editSourceAction.getTemplatePresentation().setIcon(AllIcons.Actions.EditSource);
130         editSourceAction.getTemplatePresentation().setText(ActionsBundle.actionText("EditSource"));
131         editSourceAction.getTemplatePresentation().setDescription(ActionsBundle.actionText("EditSource"));
132         editSourceAction.registerCustomShortcutSet(CommonShortcuts.getEditSource(), myPanel, DiffPanelImpl.this);
133         return editSourceAction;
134       }
135     };
136   }
137
138   private boolean myDisposed = false;
139   private final GenericDataProvider myDataProvider;
140   private final Project myProject;
141   private final boolean myIsHorizontal;
142   private final DiffTool myParentTool;
143   private EditorNotificationPanel myTopMessageDiffPanel;
144   private final VisibleAreaListener myVisibleAreaListener;
145
146   public DiffPanelImpl(final Window owner,
147                        Project project,
148                        boolean enableToolbar,
149                        boolean horizontal,
150                        int diffDividerPolygonsOffset,
151                        DiffTool parentTool) {
152     myProject = project;
153     myIsHorizontal = horizontal;
154     myParentTool = parentTool;
155     myOptions = new DiffPanelOptions(this);
156     myPanel = new DiffPanelOuterComponent(TextDiffType.DIFF_TYPES, null);
157     myPanel.disableToolbar(!enableToolbar);
158     if (enableToolbar) myPanel.resetToolbar();
159     myOwnerWindow = owner;
160     myIsSyncScroll = true;
161     final boolean v = !horizontal;
162     myLeftSide =  new DiffSideView(this, new CustomLineBorder(UIUtil.getBorderColor(), 1, 0, v ? 0 : 1, v ? 0 : 1));
163     myRightSide = new DiffSideView(this, new CustomLineBorder(UIUtil.getBorderColor(), v ? 0 : 1, v ? 0 : 1, 1, 0));
164     myLeftSide.becomeMaster();
165     myDiffUpdater = new Rediffers(this);
166
167     myData = createDiffPanelState(this);
168
169     if (horizontal) {
170       mySplitter = new DiffSplitter(myLeftSide.getComponent(), myRightSide.getComponent(),
171                                     new DiffDividerPaint(this, FragmentSide.SIDE1, diffDividerPolygonsOffset), myData);
172     }
173     else {
174       mySplitter = new HorizontalDiffSplitter(myLeftSide.getComponent(), myRightSide.getComponent());
175     }
176
177     myPanel.insertDiffComponent(mySplitter.getComponent(), new MyScrollingPanel());
178     myDataProvider = new MyGenericDataProvider(this);
179     myPanel.setDataProvider(myDataProvider);
180
181     final ComparisonPolicy comparisonPolicy = getComparisonPolicy();
182     final ComparisonPolicy defaultComparisonPolicy = DiffManagerImpl.getInstanceEx().getComparisonPolicy();
183     final HighlightMode highlightMode = getHighlightMode();
184     final HighlightMode defaultHighlightMode = DiffManagerImpl.getInstanceEx().getHighlightMode();
185
186     if (defaultComparisonPolicy != null && comparisonPolicy != defaultComparisonPolicy) {
187       setComparisonPolicy(defaultComparisonPolicy);
188     }
189     if (defaultHighlightMode != null && highlightMode != defaultHighlightMode) {
190       setHighlightMode(defaultHighlightMode);
191     }
192     myVisibleAreaListener = new VisibleAreaListener() {
193       @Override
194       public void visibleAreaChanged(VisibleAreaEvent e) {
195         Editor editor1 = getEditor1();
196         if (editor1 != null) {
197           editor1.getComponent().repaint();
198         }
199         Editor editor2 = getEditor2();
200         if (editor2 != null) {
201           editor2.getComponent().repaint();
202         }
203       }
204     };
205     registerActions();
206   }
207
208   private void registerActions() {
209     //control+tab switches editors
210     new AnAction(){
211       @Override
212       public void actionPerformed(AnActionEvent e) {
213         if (getEditor1() != null && getEditor2() != null) {
214           Editor focus = getEditor1().getContentComponent().hasFocus() ? getEditor2() : getEditor1();
215           IdeFocusManager.getGlobalInstance().requestFocus(focus.getContentComponent(), true);
216           focus.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
217         }
218       }
219     }.registerCustomShortcutSet(CustomShortcutSet.fromString("control TAB"), myPanel, this);
220   }
221
222   protected DiffPanelState createDiffPanelState(@NotNull Disposable parentDisposable) {
223     return new DiffPanelState(this, myProject, parentDisposable);
224   }
225
226   public boolean isHorisontal() {
227     return myIsHorizontal;
228   }
229
230   public DiffPanelState getDiffPanelState() {
231     return myData;
232   }
233
234   public void noSynchScroll() {
235     myIsSyncScroll = false;
236   }
237
238   public DiffSplitterI getSplitter() {
239     return mySplitter;
240   }
241
242   public void reset() {
243     //myUi.getContentManager().removeAllContents(false);
244     myPanel.setPreferredHeightGetter(null);
245   }
246
247   public void prefferedSizeByContents(final int maximumLines) {
248     if (getEditor1() == null && getEditor2() == null) return;
249
250     if (getEditor1() != null) {
251       getEditor1().getSettings().setAdditionalLinesCount(0);
252       getEditor1().getSettings().setAdditionalPageAtBottom(false);
253     }
254     if (getEditor2() != null) {
255       getEditor2().getSettings().setAdditionalLinesCount(0);
256       getEditor2().getSettings().setAdditionalPageAtBottom(false);
257     }
258     myPanel.setPrefferedWidth(20);
259     myPanel.setPreferredHeightGetter(new Getter<Integer>() {
260       @Override
261       public Integer get() {
262         final int size1 = getEditor1() == null ? 10 : getEditor1().getComponent().getPreferredSize().height;
263         final int size2 = getEditor2() == null ? 10 : getEditor2().getComponent().getPreferredSize().height;
264         final int lineHeight = getEditor1() == null ? getEditor2().getLineHeight() : getEditor1().getLineHeight();
265         final int size = Math.max(size1, size2);
266         if (maximumLines > 0) {
267           return Math.min(size, maximumLines * lineHeight);
268         }
269         return size;
270       }
271     });
272   }
273
274   @Nullable
275   public Editor getEditor1() {
276     return myLeftSide.getEditor();
277   }
278
279   @Nullable
280   public Editor getEditor2() {
281     if (myDisposed) LOG.error("Disposed");
282     Editor editor = myRightSide.getEditor();
283     if (editor != null) return editor;
284     if (myData.getContent2() == null) LOG.error("No content 2");
285     return editor;
286   }
287
288   public void setContents(DiffContent content1, DiffContent content2) {
289     LOG.assertTrue(content1 != null && content2 != null);
290     LOG.assertTrue(!myDisposed);
291     myData.setContents(content1, content2);
292     Project project = myData.getProject();
293     FileType[] types = DiffUtil.chooseContentTypes(new DiffContent[]{content1, content2});
294     VirtualFile baseFile = content1.getFile();
295     if (baseFile == null && myDiffRequest != null) {
296       String path = myDiffRequest.getWindowTitle();
297       if (path != null) baseFile = LocalFileSystem.getInstance().findFileByPath(path);
298     }
299     myLeftSide.setHighlighterFactory(createHighlighter(types[0], baseFile, project));
300     myRightSide.setHighlighterFactory(createHighlighter(types[1], baseFile, project));
301     setSplitterProportion(content1, content2);
302     rediff();
303     if (myIsRequestFocus) {
304       myPanel.requestScrollEditors();
305     }
306   }
307
308   private void setSplitterProportion(DiffContent content1, DiffContent content2) {
309     if (content1.isEmpty()) {
310       mySplitter.setProportion(0f);
311       mySplitter.setResizeEnabled(false);
312       return;
313     }
314     if (content2.isEmpty()) {
315       mySplitter.setProportion(1.0f);
316       mySplitter.setResizeEnabled(false);
317       return;
318     }
319     mySplitter.setProportion(0.5f);
320     mySplitter.setResizeEnabled(true);
321   }
322
323   public void removeStatusBar() {
324     myPanel.removeStatusBar();
325   }
326
327   public void enableToolbar(final boolean value) {
328     myPanel.disableToolbar(!value);
329   }
330
331   public void setLineNumberConvertors(final Convertor<Integer, Integer> old, final Convertor<Integer, Integer> newConvertor) {
332     if (getEditor1() != null) {
333       ((EditorGutterComponentEx) getEditor1().getGutter()).setLineNumberConvertor(old);
334     }
335     if (getEditor2() != null) {
336       ((EditorGutterComponentEx) getEditor2().getGutter()).setLineNumberConvertor(newConvertor);
337     }
338   }
339   // todo pay attention here
340   private static DiffHighlighterFactory createHighlighter(FileType contentType, VirtualFile file, Project project) {
341     return new DiffHighlighterFactoryImpl(contentType, file, project);
342   }
343
344   void rediff() {
345     try {
346       if (myTopMessageDiffPanel != null) {
347         myPanel.removeTopComponent(myTopMessageDiffPanel);
348       }
349       LineBlocks blocks = myData.updateEditors();
350       setLineBlocks(blocks);
351       if (blocks.getCount() == 0) {
352         if (myData.isContentsEqual()) {
353           setFileContentsAreIdentical();
354         }
355       }
356     }
357     catch (FilesTooBigForDiffException e) {
358       setTooBigFileErrorContents();
359     }
360   }
361
362   public void setTooBigFileErrorContents() {
363     setLineBlocks(LineBlocks.EMPTY);
364     myTopMessageDiffPanel = new CanNotCalculateDiffPanel();
365     myPanel.insertTopComponent(myTopMessageDiffPanel);
366   }
367
368   public void setPatchAppliedApproximately() {
369     if (!(myTopMessageDiffPanel instanceof CanNotCalculateDiffPanel)) {
370       myTopMessageDiffPanel = new DiffIsApproximate();
371       myPanel.insertTopComponent(myTopMessageDiffPanel);
372     }
373   }
374
375   public void setFileContentsAreIdentical() {
376     if (myTopMessageDiffPanel == null || myTopMessageDiffPanel instanceof FileContentsAreIdenticalDiffPanel) {
377       LineSeparator sep1 = myData.getContent1() == null ? null : myData.getContent1().getLineSeparator();
378       LineSeparator sep2 = myData.getContent2() == null ? null : myData.getContent2().getLineSeparator();
379
380       if (LineSeparator.knownAndDifferent(sep1, sep2)) {
381         myTopMessageDiffPanel = new LineSeparatorsOnlyDiffPanel();
382       }
383       else {
384         myTopMessageDiffPanel = new FileContentsAreIdenticalDiffPanel();
385       }
386       myPanel.insertTopComponent(myTopMessageDiffPanel);
387     }
388   }
389
390   public void setTitle1(String title) {
391     setTitle(title, true);
392   }
393
394   private void setTitle(String title, boolean left) {
395     Editor editor = left ? getEditor1() : getEditor2();
396     if (editor == null) return;
397     title = addReadOnly(title, editor);
398     JLabel label = new JLabel(title);
399     if (left) {
400       myLeftSide.setTitle(label);
401     }
402     else {
403       myRightSide.setTitle(label);
404     }
405   }
406
407   @Nullable
408   private static String addReadOnly(@Nullable String title, @Nullable Editor editor) {
409     if (editor == null || title == null) {
410       return title;
411     }
412     boolean readonly = editor.isViewer() || !editor.getDocument().isWritable();
413     if (readonly) {
414       title += " " + DiffBundle.message("diff.content.read.only.content.title.suffix");
415     }
416     return title;
417   }
418
419   public void setTitle2(String title) {
420     setTitle(title, false);
421   }
422
423   private void setLineBlocks(LineBlocks blocks) {
424     myLineBlocks = blocks;
425     mySplitter.redrawDiffs();
426     updateStatusBar();
427   }
428
429   public void invalidateDiff() {
430     setLineBlocks(LineBlocks.EMPTY);
431     myData.removeActions();
432   }
433
434   public FragmentList getFragments() {
435     return myData.getFragmentList();
436   }
437
438   private int[] getFragmentBeginnings() {
439     return getFragmentBeginnings(myCurrentSide.getSide());
440   }
441
442   int[] getFragmentBeginnings(FragmentSide side) {
443     return getLineBlocks().getBeginnings(side);
444   }
445
446   public void dispose() {
447     myDisposed = true;
448     myDiffUpdater.dispose();
449     Disposer.dispose(myScrollSupport);
450     Disposer.dispose(myData);
451     myPanel.cancelScrollEditors();
452     JComponent component = myPanel.getBottomComponent();
453     if (component instanceof Disposable) {
454       Disposer.dispose((Disposable)component);
455     }
456     myPanel.setBottomComponent(null);
457     myPanel.setDataProvider(null);
458     myPanel.setScrollingPanel(null);
459   }
460
461   public JComponent getComponent() {
462     return myPanel;
463   }
464
465   private void updateStatusBar() {
466     myPanel.setStatusBarText(getNumDifferencesText());
467   }
468
469   public String getNumDifferencesText() {
470     return DiffBundle.message("diff.count.differences.status.text", getLineBlocks().getCount());
471   }
472
473   public boolean hasDifferences() {
474     return getLineBlocks().getCount() > 0 || myTopMessageDiffPanel != null;
475   }
476
477   @Nullable
478   public JComponent getPreferredFocusedComponent() {
479     return myCurrentSide.getFocusableComponent();
480   }
481
482   public int getContentsNumber() {
483     return 2;
484   }
485
486   @Override
487   public boolean acceptsType(DiffViewerType type) {
488     return DiffViewerType.contents.equals(type);
489   }
490
491   public ComparisonPolicy getComparisonPolicy() {
492     return myData.getComparisonPolicy();
493   }
494
495   public void setComparisonPolicy(ComparisonPolicy comparisonPolicy) {
496     setComparisonPolicy(comparisonPolicy, true);
497   }
498
499   private void setComparisonPolicy(ComparisonPolicy policy, boolean notifyManager) {
500     myData.setComparisonPolicy(policy);
501     rediff();
502
503     if (notifyManager) {
504       DiffManagerImpl.getInstanceEx().setComparisonPolicy(policy);
505     }
506   }
507
508   @NotNull
509   public HighlightMode getHighlightMode() {
510     return myData.getHighlightMode();
511   }
512
513   public void setHighlightMode(@NotNull HighlightMode mode) {
514     setHighlightMode(mode, true);
515   }
516
517   public void setHighlightMode(@NotNull HighlightMode mode, boolean notifyManager) {
518     myData.setHighlightMode(mode);
519     rediff();
520
521     if (notifyManager) {
522       DiffManagerImpl.getInstanceEx().setHighlightMode(mode);
523     }
524   }
525
526   public void setAutoScrollEnabled(boolean enabled) {
527     myScrollSupport.setEnabled(enabled);
528   }
529
530   public boolean isAutoScrollEnabled() {
531     return myScrollSupport.isEnabled();
532   }
533
534   public Rediffers getDiffUpdater() {
535     return myDiffUpdater;
536   }
537
538   public void onContentChangedIn(EditorSource source) {
539     myDiffUpdater.contentRemoved(source);
540     final EditorEx editor = source.getEditor();
541     if (myIsHorizontal && source.getSide() == FragmentSide.SIDE1 && editor != null) {
542       editor.setVerticalScrollbarOrientation(EditorEx.VERTICAL_SCROLLBAR_LEFT);
543     }
544     DiffSideView viewSide = getSideView(source.getSide());
545     viewSide.setEditorSource(getProject(), source);
546     Disposer.dispose(myScrollSupport);
547     if (editor == null) {
548       if (!myDisposed) {
549         rediff();
550       }
551       return;
552     }
553
554     final MouseListener mouseListener = PopupHandler
555       .installUnknownPopupHandler(editor.getContentComponent(), new MergeActionGroup(this, source.getSide()), ActionManager.getInstance());
556     myDiffUpdater.contentAdded(source);
557     editor.getSettings().setLineNumbersShown(true);
558     editor.getSettings().setFoldingOutlineShown(false);
559     editor.getFoldingModel().setFoldingEnabled(false);
560     ((EditorMarkupModel)editor.getMarkupModel()).setErrorStripeVisible(true);
561
562     Editor editor1 = getEditor(FragmentSide.SIDE1);
563     Editor editor2 = getEditor(FragmentSide.SIDE2);
564     if (editor1 != null && editor2 != null && myIsSyncScroll) {
565       myScrollSupport.install(new EditingSides[]{this});
566     }
567
568     final VisibleAreaListener visibleAreaListener = mySplitter.getVisibleAreaListener();
569     final ScrollingModel scrollingModel = editor.getScrollingModel();
570     if (visibleAreaListener != null) {
571       scrollingModel.addVisibleAreaListener(visibleAreaListener);
572       scrollingModel.addVisibleAreaListener(myVisibleAreaListener);
573     }
574     myFontSizeSynchronizer.synchronize(editor);
575     source.addDisposable(new Disposable() {
576       public void dispose() {
577         myFontSizeSynchronizer.stopSynchronize(editor);
578       }
579     });
580     source.addDisposable(new Disposable() {
581       public void dispose() {
582         if (visibleAreaListener != null) {
583           scrollingModel.removeVisibleAreaListener(visibleAreaListener);
584           scrollingModel.removeVisibleAreaListener(myVisibleAreaListener);
585         }
586         editor.getContentComponent().removeMouseListener(mouseListener);
587       }
588     });
589   }
590
591   public void setCurrentSide(@NotNull DiffSideView viewSide) {
592     LOG.assertTrue(viewSide != myCurrentSide);
593     if (myCurrentSide != null) myCurrentSide.beSlave();
594     myCurrentSide = viewSide;
595   }
596
597   public DiffSideView getCurrentSide() { return myCurrentSide; }
598
599   public Project getProject() {
600     return myData.getProject();
601   }
602
603   public void showSource(OpenFileDescriptor descriptor) {
604     myOptions.showSource(descriptor);
605   }
606
607   public DiffPanelOptions getOptions() {
608     return myOptions;
609   }
610
611   public Editor getEditor(FragmentSide side) {
612     return getSideView(side).getEditor();
613   }
614
615   public DiffSideView getSideView(FragmentSide side) {
616     if (side == FragmentSide.SIDE1) {
617       return myLeftSide;
618     }
619     if (side == FragmentSide.SIDE2) return myRightSide;
620     throw new IllegalArgumentException(String.valueOf(side));
621   }
622
623   public LineBlocks getLineBlocks() { return myLineBlocks; }
624
625   static JComponent createComponentForTitle(@Nullable String title,
626                                             @Nullable final LineSeparator sep1,
627                                             @Nullable final LineSeparator sep2,
628                                             boolean left) {
629     if (sep1 != null && sep2 != null && !sep1.equals(sep2)) {
630       LineSeparator separator = left ? sep1 : sep2;
631       JPanel bottomPanel = new JPanel(new BorderLayout());
632       JLabel sepLabel = new JLabel(separator.name());
633       sepLabel.setForeground(separator.equals(LineSeparator.CRLF) ? JBColor.RED : PlatformColors.BLUE);
634       bottomPanel.add(sepLabel, left ? BorderLayout.EAST : BorderLayout.WEST);
635
636       JPanel panel = new JPanel(new BorderLayout());
637       panel.add(new JLabel(title == null ? "" : title));
638       panel.add(bottomPanel, BorderLayout.SOUTH);
639       return panel;
640     }
641     else {
642       return new JBLabel(title == null ? "" : title);
643     }
644   }
645
646   @Override
647   public boolean canShowRequest(DiffRequest request) {
648     return myParentTool != null && myParentTool.canShow(request);
649   }
650
651   public void setDiffRequest(DiffRequest data) {
652     myDiffRequest = data;
653     if (data.getHints().contains(DiffTool.HINT_DO_NOT_IGNORE_WHITESPACES)) {
654       setComparisonPolicy(ComparisonPolicy.DEFAULT, false);
655     }
656     myDataProvider.putData(myDiffRequest.getGenericData());
657
658     DiffContent content1 = data.getContents()[0];
659     DiffContent content2 = data.getContents()[1];
660
661     setContents(content1, content2);
662     setTitles(data);
663
664     setWindowTitle(myOwnerWindow, data.getWindowTitle());
665     myPanel.setToolbarActions(createToolbar());
666     data.customizeToolbar(myPanel.resetToolbar());
667     myPanel.registerToolbarActions();
668     initEditorSettings(getEditor1());
669     initEditorSettings(getEditor2());
670
671     final JComponent oldBottomComponent = myPanel.getBottomComponent();
672     if (oldBottomComponent instanceof Disposable) {
673       Disposer.dispose((Disposable)oldBottomComponent);
674     }
675     final JComponent newBottomComponent = data.getBottomComponent();
676     myPanel.setBottomComponent(newBottomComponent);
677
678
679     if (myIsRequestFocus) {
680       IdeFocusManager fm = IdeFocusManager.getInstance(myProject);
681       boolean isEditor1Focused = getEditor1() != null
682                                  && fm.getFocusedDescendantFor(getEditor1().getComponent()) != null;
683
684       boolean isEditor2Focused = myData.getContent2() != null
685                                  && getEditor2() != null
686                                  && fm.getFocusedDescendantFor(getEditor2().getComponent()) != null;
687
688       if (isEditor1Focused || isEditor2Focused) {
689         Editor e = isEditor2Focused ? getEditor2() : getEditor1();
690         if (e != null) {
691           fm.requestFocus(e.getContentComponent(), true);
692         }
693       }
694
695       myPanel.requestScrollEditors();
696     }
697   }
698
699   private static void initEditorSettings(@Nullable Editor editor) {
700     if (editor == null) {
701       return;
702     }
703     Project project = editor.getProject();
704     DiffMergeSettings settings = project == null ? null : ServiceManager.getService(project, DiffToolSettings.class);
705     for (DiffMergeEditorSetting property : DiffMergeEditorSetting.values()) {
706       property.apply(editor, settings == null ? property.getDefault() : settings.getPreference(property));
707     }
708     ((EditorEx)editor).getGutterComponentEx().setShowDefaultGutterPopup(false);
709   }
710
711   private void setTitles(@NotNull DiffRequest data) {
712     LineSeparator sep1 = data.getContents()[0].getLineSeparator();
713     LineSeparator sep2 = data.getContents()[1].getLineSeparator();
714
715     String title1 = addReadOnly(data.getContentTitles()[0], myLeftSide.getEditor());
716     String title2 = addReadOnly(data.getContentTitles()[1], myRightSide.getEditor());
717
718     setTitle1(createComponentForTitle(title1, sep1, sep2, true));
719     setTitle2(createComponentForTitle(title2, sep1, sep2, false));
720   }
721
722   private void setTitle1(JComponent title) {
723     myLeftSide.setTitle(title);
724   }
725
726   private void setTitle2(JComponent title) {
727     myRightSide.setTitle(title);
728   }
729
730   private static void setWindowTitle(Window window, String title) {
731     if (window instanceof JDialog) {
732       ((JDialog)window).setTitle(title);
733     }
734     else if (window instanceof JFrame) ((JFrame)window).setTitle(title);
735   }
736
737   @Nullable
738   public static DiffPanelImpl fromDataContext(DataContext dataContext) {
739     DiffViewer viewer = PlatformDataKeys.DIFF_VIEWER.getData(dataContext);
740     return viewer instanceof DiffPanelImpl ? (DiffPanelImpl)viewer : null;
741   }
742
743   public Window getOwnerWindow() {
744     return myOwnerWindow;
745   }
746
747   public void focusOppositeSide() {
748     if (myCurrentSide == myLeftSide) {
749       myRightSide.getEditor().getContentComponent().requestFocus();
750     }
751     else {
752       myLeftSide.getEditor().getContentComponent().requestFocus();
753     }
754   }
755
756   public void setRequestFocus(boolean isRequestFocus) {
757     myIsRequestFocus = isRequestFocus;
758   }
759
760   private class MyScrollingPanel implements DiffPanelOuterComponent.ScrollingPanel {
761
762     public void scrollEditors() {
763       getOptions().onNewContent(myCurrentSide);
764       final DiffNavigationContext scrollContext = myDiffRequest == null ? null :
765         (DiffNavigationContext) myDiffRequest.getGenericData().get(DiffTool.SCROLL_TO_LINE.getName());
766       if (scrollContext == null) {
767         scrollCurrentToFirstDiff();
768       } else {
769         final Document document = myRightSide.getEditor().getDocument();
770
771         final FragmentList fragmentList = getFragments();
772
773         final Application application = ApplicationManager.getApplication();
774         application.executeOnPooledThread(new Runnable() {
775           @Override
776           public void run() {
777             final ChangedLinesIterator changedLinesIterator = new ChangedLinesIterator(fragmentList.iterator(), document, false);
778             final CacheOneStepIterator<Pair<Integer, String>> cacheOneStepIterator =
779               new CacheOneStepIterator<Pair<Integer, String>>(changedLinesIterator);
780             final NavigationContextChecker checker = new NavigationContextChecker(cacheOneStepIterator, scrollContext);
781             int line = checker.contextMatchCheck();
782             if (line < 0) {
783               // this will work for the case, when spaces changes are ignored, and corresponding fragments are not reported as changed
784               // just try to find target line  -> +-
785               final ChangedLinesIterator changedLinesIterator2 = new ChangedLinesIterator(fragmentList.iterator(), document, true);
786               final CacheOneStepIterator<Pair<Integer, String>> cacheOneStepIterator2 =
787                 new CacheOneStepIterator<Pair<Integer, String>>(changedLinesIterator2);
788               final NavigationContextChecker checker2 = new NavigationContextChecker(cacheOneStepIterator2, scrollContext);
789               line = checker2.contextMatchCheck();
790             }
791             final int finalLine = line;
792             final ModalityState modalityState = myOwnerWindow == null ? ModalityState.NON_MODAL : ModalityState.stateForComponent(myOwnerWindow);
793             application.invokeLater(new Runnable() {
794               @Override
795               public void run() {
796                 if (finalLine >= 0) {
797                   final int line = myLineBlocks.transform(myRightSide.getSide(), finalLine);
798                   myLeftSide.scrollToFirstDiff(line);
799                 } else {
800                   scrollCurrentToFirstDiff();
801                 }
802               }
803             }, modalityState);
804           }
805         });
806       }
807     }
808
809     private void scrollCurrentToFirstDiff() {
810       int[] fragments = getFragmentBeginnings();
811       if (fragments.length > 0) myCurrentSide.scrollToFirstDiff(fragments[0]);
812     }
813   }
814
815   private static class ChangedLinesIterator implements Iterator<Pair<Integer, String>> {
816     private final Document myDocument;
817     private final boolean myIgnoreFragmentsType;
818     private final Iterator<Fragment> myFragmentsIterator;
819     private final List<Pair<Integer, String>> myBuffer;
820
821     private ChangedLinesIterator(Iterator<Fragment> fragmentsIterator, Document document, final boolean ignoreFragmentsType) {
822       myFragmentsIterator = fragmentsIterator;
823       myDocument = document;
824       myIgnoreFragmentsType = ignoreFragmentsType;
825       myBuffer = new LinkedList<Pair<Integer, String>>();
826     }
827
828     @Override
829     public boolean hasNext() {
830       return !myBuffer.isEmpty() || myFragmentsIterator.hasNext();
831     }
832
833     @Override
834     public Pair<Integer, String> next() {
835       if (! myBuffer.isEmpty()) {
836         return myBuffer.remove(0);
837       }
838
839       Fragment fragment = null;
840       while (myFragmentsIterator.hasNext()) {
841         fragment = myFragmentsIterator.next();
842         final TextDiffTypeEnum type = fragment.getType();
843         if (! myIgnoreFragmentsType && (type == null || TextDiffTypeEnum.DELETED.equals(type) || TextDiffTypeEnum.NONE.equals(type))) continue;
844         break;
845       }
846       if (fragment == null) return null;
847
848       final TextRange textRange = fragment.getRange(FragmentSide.SIDE2);
849       ApplicationManager.getApplication().runReadAction(new Runnable() {
850         @Override
851         public void run() {
852           final int startLine = myDocument.getLineNumber(textRange.getStartOffset());
853           final int endFragmentOffset = textRange.getEndOffset();
854           final int endLine = myDocument.getLineNumber(endFragmentOffset);
855           for (int i = startLine; i <= endLine; i++) {
856             int lineEndOffset = myDocument.getLineEndOffset(i);
857             final int lineStartOffset = myDocument.getLineStartOffset(i);
858             if (lineEndOffset > endFragmentOffset && endFragmentOffset == lineStartOffset) {
859               lineEndOffset = endFragmentOffset;
860             }
861             if (lineStartOffset > lineEndOffset) continue;
862             String text = myDocument.getText().substring(lineStartOffset, lineEndOffset);
863             myBuffer.add(new Pair<Integer, String>(i, text));
864           }
865         }
866       });
867       if (myBuffer.isEmpty()) return null;
868       return myBuffer.remove(0);
869     }
870
871     @Override
872     public void remove() {
873       throw new UnsupportedOperationException();
874     }
875   }
876
877   private static class NavigationContextChecker {
878     private final Iterator<Pair<Integer, String>> myChangedLinesIterator;
879     private final DiffNavigationContext myContext;
880
881     private NavigationContextChecker(Iterator<Pair<Integer, String>> changedLinesIterator, DiffNavigationContext context) {
882       myChangedLinesIterator = changedLinesIterator;
883       myContext = context;
884     }
885
886     public int contextMatchCheck() {
887       final Iterable<String> contextLines = myContext.getPreviousLinesIterable();
888
889       // we ignore spaces.. at least at start/end, since some version controls could ignore their changes when doing annotate
890       final Iterator<String> iterator = contextLines.iterator();
891       if (iterator.hasNext()) {
892         String contextLine = iterator.next().trim();
893
894         while (myChangedLinesIterator.hasNext()) {
895           final Pair<Integer, String> pair = myChangedLinesIterator.next();
896           if (pair.getSecond().trim().equals(contextLine)) {
897             if (! iterator.hasNext()) break;
898             contextLine = iterator.next().trim();
899           }
900         }
901         if (iterator.hasNext()) {
902           return -1;
903         }
904       }
905       if (! myChangedLinesIterator.hasNext()) return -1;
906
907       final String targetLine = myContext.getTargetString().trim();
908       while (myChangedLinesIterator.hasNext()) {
909         final Pair<Integer, String> pair = myChangedLinesIterator.next();
910         if (pair.getSecond().trim().equals(targetLine)) {
911           return pair.getFirst();
912         }
913       }
914       return -1;
915     }
916   }
917
918   private class MyGenericDataProvider extends GenericDataProvider {
919     private final DiffPanelImpl myDiffPanel;
920
921     private MyGenericDataProvider(DiffPanelImpl diffPanel) {
922       myDiffPanel = diffPanel;
923     }
924
925     private final FocusDiffSide myFocusDiffSide = new FocusDiffSide() {
926       public Editor getEditor() {
927         return myDiffPanel.getCurrentSide().getEditor();
928       }
929
930       public int[] getFragmentStartingLines() {
931         return myDiffPanel.getFragmentBeginnings();
932       }
933     };
934
935     @Override
936     public Object getData(String dataId) {
937       if (PlatformDataKeys.DIFF_VIEWER.is(dataId)) {
938         return myDiffPanel;
939       }
940       if (FocusDiffSide.DATA_KEY.is(dataId)) {
941         return myDiffPanel.myCurrentSide == null ? null : myFocusDiffSide;
942       }
943       if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
944         final DiffSideView currentSide = myDiffPanel.myCurrentSide;
945         if (currentSide != null) {
946           return new DiffNavigatable(currentSide);
947         }
948       }
949       if (PlatformDataKeys.HELP_ID.is(dataId)) {
950         return "reference.dialogs.diff.file";
951       }
952
953       return super.getData(dataId);
954     }
955   }
956
957   public static class FileContentsAreIdenticalDiffPanel extends EditorNotificationPanel {
958     public FileContentsAreIdenticalDiffPanel() {
959       myLabel.setText(DiffBundle.message("diff.contents.are.identical.message.text"));
960     }
961   }
962
963   public static class LineSeparatorsOnlyDiffPanel extends FileContentsAreIdenticalDiffPanel {
964     public LineSeparatorsOnlyDiffPanel() {
965       myLabel.setText(DiffBundle.message("diff.contents.have.differences.only.in.line.separators.message.text"));
966     }
967   }
968
969   public static class CanNotCalculateDiffPanel extends EditorNotificationPanel {
970     public CanNotCalculateDiffPanel() {
971       myLabel.setText("Can not calculate diff. File is too big and there are too many changes.");
972     }
973   }
974
975   public static class DiffIsApproximate extends EditorNotificationPanel {
976     public DiffIsApproximate() {
977       myLabel.setText("<html>Couldn't find context for patch. Some fragments were applied at the best possible place. <b>Please check carefully.</b></html>");
978     }
979   }
980
981   private class DiffNavigatable implements Navigatable {
982     private final DiffSideView mySide;
983
984     public DiffNavigatable(DiffSideView side) {
985       mySide = side;
986     }
987
988     @Override
989     public boolean canNavigateToSource() {
990       return false;
991     }
992
993     @Override
994     public boolean canNavigate() {
995       return true;
996     }
997
998     @Override
999     public void navigate(boolean requestFocus) {
1000       final OpenFileDescriptor descriptor = mySide.getCurrentOpenFileDescriptor();
1001       if (descriptor != null) {
1002         showSource(descriptor);
1003       }
1004     }
1005   }
1006 }