2 * Copyright 2000-2012 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.diff.impl;
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;
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;
89 public class DiffPanelImpl implements DiffPanelEx, ContentChangeListener, TwoSidesContainer {
90 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.DiffPanelImpl");
92 private final DiffSplitterI mySplitter;
93 private final DiffPanelOuterComponent myPanel;
95 private final Window myOwnerWindow;
96 private final DiffPanelOptions myOptions;
98 private final DiffPanelState myData;
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;
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)));
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;
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;
146 public DiffPanelImpl(final Window owner,
148 boolean enableToolbar,
150 int diffDividerPolygonsOffset,
151 DiffTool parentTool) {
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);
167 myData = createDiffPanelState(this);
170 mySplitter = new DiffSplitter(myLeftSide.getComponent(), myRightSide.getComponent(),
171 new DiffDividerPaint(this, FragmentSide.SIDE1, diffDividerPolygonsOffset), myData);
174 mySplitter = new HorizontalDiffSplitter(myLeftSide.getComponent(), myRightSide.getComponent());
177 myPanel.insertDiffComponent(mySplitter.getComponent(), new MyScrollingPanel());
178 myDataProvider = new MyGenericDataProvider(this);
179 myPanel.setDataProvider(myDataProvider);
181 final ComparisonPolicy comparisonPolicy = getComparisonPolicy();
182 final ComparisonPolicy defaultComparisonPolicy = DiffManagerImpl.getInstanceEx().getComparisonPolicy();
183 final HighlightMode highlightMode = getHighlightMode();
184 final HighlightMode defaultHighlightMode = DiffManagerImpl.getInstanceEx().getHighlightMode();
186 if (defaultComparisonPolicy != null && comparisonPolicy != defaultComparisonPolicy) {
187 setComparisonPolicy(defaultComparisonPolicy);
189 if (defaultHighlightMode != null && highlightMode != defaultHighlightMode) {
190 setHighlightMode(defaultHighlightMode);
192 myVisibleAreaListener = new VisibleAreaListener() {
194 public void visibleAreaChanged(VisibleAreaEvent e) {
195 Editor editor1 = getEditor1();
196 if (editor1 != null) {
197 editor1.getComponent().repaint();
199 Editor editor2 = getEditor2();
200 if (editor2 != null) {
201 editor2.getComponent().repaint();
208 private void registerActions() {
209 //control+tab switches editors
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);
219 }.registerCustomShortcutSet(CustomShortcutSet.fromString("control TAB"), myPanel, this);
222 protected DiffPanelState createDiffPanelState(@NotNull Disposable parentDisposable) {
223 return new DiffPanelState(this, myProject, parentDisposable);
226 public boolean isHorisontal() {
227 return myIsHorizontal;
230 public DiffPanelState getDiffPanelState() {
234 public void noSynchScroll() {
235 myIsSyncScroll = false;
238 public DiffSplitterI getSplitter() {
242 public void reset() {
243 //myUi.getContentManager().removeAllContents(false);
244 myPanel.setPreferredHeightGetter(null);
247 public void prefferedSizeByContents(final int maximumLines) {
248 if (getEditor1() == null && getEditor2() == null) return;
250 if (getEditor1() != null) {
251 getEditor1().getSettings().setAdditionalLinesCount(0);
252 getEditor1().getSettings().setAdditionalPageAtBottom(false);
254 if (getEditor2() != null) {
255 getEditor2().getSettings().setAdditionalLinesCount(0);
256 getEditor2().getSettings().setAdditionalPageAtBottom(false);
258 myPanel.setPrefferedWidth(20);
259 myPanel.setPreferredHeightGetter(new Getter<Integer>() {
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);
275 public Editor getEditor1() {
276 return myLeftSide.getEditor();
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");
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);
299 myLeftSide.setHighlighterFactory(createHighlighter(types[0], baseFile, project));
300 myRightSide.setHighlighterFactory(createHighlighter(types[1], baseFile, project));
301 setSplitterProportion(content1, content2);
303 if (myIsRequestFocus) {
304 myPanel.requestScrollEditors();
308 private void setSplitterProportion(DiffContent content1, DiffContent content2) {
309 if (content1.isEmpty()) {
310 mySplitter.setProportion(0f);
311 mySplitter.setResizeEnabled(false);
314 if (content2.isEmpty()) {
315 mySplitter.setProportion(1.0f);
316 mySplitter.setResizeEnabled(false);
319 mySplitter.setProportion(0.5f);
320 mySplitter.setResizeEnabled(true);
323 public void removeStatusBar() {
324 myPanel.removeStatusBar();
327 public void enableToolbar(final boolean value) {
328 myPanel.disableToolbar(!value);
331 public void setLineNumberConvertors(final Convertor<Integer, Integer> old, final Convertor<Integer, Integer> newConvertor) {
332 if (getEditor1() != null) {
333 ((EditorGutterComponentEx) getEditor1().getGutter()).setLineNumberConvertor(old);
335 if (getEditor2() != null) {
336 ((EditorGutterComponentEx) getEditor2().getGutter()).setLineNumberConvertor(newConvertor);
339 // todo pay attention here
340 private static DiffHighlighterFactory createHighlighter(FileType contentType, VirtualFile file, Project project) {
341 return new DiffHighlighterFactoryImpl(contentType, file, project);
346 if (myTopMessageDiffPanel != null) {
347 myPanel.removeTopComponent(myTopMessageDiffPanel);
349 LineBlocks blocks = myData.updateEditors();
350 setLineBlocks(blocks != null ? blocks : LineBlocks.EMPTY);
351 if (blocks != null && blocks.getCount() == 0) {
352 if (myData.isContentsEqual()) {
353 setFileContentsAreIdentical();
357 catch (FilesTooBigForDiffException e) {
358 setTooBigFileErrorContents();
362 public void setTooBigFileErrorContents() {
363 setLineBlocks(LineBlocks.EMPTY);
364 myTopMessageDiffPanel = new CanNotCalculateDiffPanel();
365 myPanel.insertTopComponent(myTopMessageDiffPanel);
368 public void setPatchAppliedApproximately() {
369 if (!(myTopMessageDiffPanel instanceof CanNotCalculateDiffPanel)) {
370 myTopMessageDiffPanel = new DiffIsApproximate();
371 myPanel.insertTopComponent(myTopMessageDiffPanel);
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();
380 if (LineSeparator.knownAndDifferent(sep1, sep2)) {
381 myTopMessageDiffPanel = new LineSeparatorsOnlyDiffPanel();
384 myTopMessageDiffPanel = new FileContentsAreIdenticalDiffPanel();
386 myPanel.insertTopComponent(myTopMessageDiffPanel);
390 public void setTitle1(String title) {
391 setTitle(title, true);
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);
400 myLeftSide.setTitle(label);
403 myRightSide.setTitle(label);
408 private static String addReadOnly(@Nullable String title, @Nullable Editor editor) {
409 if (editor == null || title == null) {
412 boolean readonly = editor.isViewer() || !editor.getDocument().isWritable();
414 title += " " + DiffBundle.message("diff.content.read.only.content.title.suffix");
419 public void setTitle2(String title) {
420 setTitle(title, false);
423 private void setLineBlocks(@NotNull LineBlocks blocks) {
424 myLineBlocks = blocks;
425 mySplitter.redrawDiffs();
429 public void invalidateDiff() {
430 setLineBlocks(LineBlocks.EMPTY);
431 myData.removeActions();
434 public FragmentList getFragments() {
435 return myData.getFragmentList();
438 private int[] getFragmentBeginnings() {
439 return getFragmentBeginnings(myCurrentSide.getSide());
442 int[] getFragmentBeginnings(FragmentSide side) {
443 return getLineBlocks().getBeginnings(side);
446 public void dispose() {
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);
456 myPanel.setBottomComponent(null);
457 myPanel.setDataProvider(null);
458 myPanel.setScrollingPanel(null);
461 public JComponent getComponent() {
465 private void updateStatusBar() {
466 myPanel.setStatusBarText(getNumDifferencesText());
469 public String getNumDifferencesText() {
470 return DiffBundle.message("diff.count.differences.status.text", getLineBlocks().getCount());
473 public boolean hasDifferences() {
474 return getLineBlocks().getCount() > 0 || myTopMessageDiffPanel != null;
478 public JComponent getPreferredFocusedComponent() {
479 return myCurrentSide.getFocusableComponent();
482 public int getContentsNumber() {
487 public boolean acceptsType(DiffViewerType type) {
488 return DiffViewerType.contents.equals(type);
491 public ComparisonPolicy getComparisonPolicy() {
492 return myData.getComparisonPolicy();
495 public void setComparisonPolicy(ComparisonPolicy comparisonPolicy) {
496 setComparisonPolicy(comparisonPolicy, true);
499 private void setComparisonPolicy(ComparisonPolicy policy, boolean notifyManager) {
500 myData.setComparisonPolicy(policy);
504 DiffManagerImpl.getInstanceEx().setComparisonPolicy(policy);
509 public HighlightMode getHighlightMode() {
510 return myData.getHighlightMode();
513 public void setHighlightMode(@NotNull HighlightMode mode) {
514 setHighlightMode(mode, true);
517 public void setHighlightMode(@NotNull HighlightMode mode, boolean notifyManager) {
518 myData.setHighlightMode(mode);
522 DiffManagerImpl.getInstanceEx().setHighlightMode(mode);
526 public void setAutoScrollEnabled(boolean enabled) {
527 myScrollSupport.setEnabled(enabled);
530 public boolean isAutoScrollEnabled() {
531 return myScrollSupport.isEnabled();
534 public Rediffers getDiffUpdater() {
535 return myDiffUpdater;
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);
544 DiffSideView viewSide = getSideView(source.getSide());
545 viewSide.setEditorSource(getProject(), source);
546 Disposer.dispose(myScrollSupport);
547 if (editor == null) {
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);
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});
568 final VisibleAreaListener visibleAreaListener = mySplitter.getVisibleAreaListener();
569 final ScrollingModel scrollingModel = editor.getScrollingModel();
570 if (visibleAreaListener != null) {
571 scrollingModel.addVisibleAreaListener(visibleAreaListener);
572 scrollingModel.addVisibleAreaListener(myVisibleAreaListener);
574 myFontSizeSynchronizer.synchronize(editor);
575 source.addDisposable(new Disposable() {
576 public void dispose() {
577 myFontSizeSynchronizer.stopSynchronize(editor);
580 source.addDisposable(new Disposable() {
581 public void dispose() {
582 if (visibleAreaListener != null) {
583 scrollingModel.removeVisibleAreaListener(visibleAreaListener);
584 scrollingModel.removeVisibleAreaListener(myVisibleAreaListener);
586 editor.getContentComponent().removeMouseListener(mouseListener);
591 public void setCurrentSide(@NotNull DiffSideView viewSide) {
592 LOG.assertTrue(viewSide != myCurrentSide);
593 if (myCurrentSide != null) myCurrentSide.beSlave();
594 myCurrentSide = viewSide;
597 public DiffSideView getCurrentSide() { return myCurrentSide; }
599 public Project getProject() {
600 return myData.getProject();
603 public void showSource(OpenFileDescriptor descriptor) {
604 myOptions.showSource(descriptor);
607 public DiffPanelOptions getOptions() {
611 public Editor getEditor(FragmentSide side) {
612 return getSideView(side).getEditor();
615 public DiffSideView getSideView(FragmentSide side) {
616 if (side == FragmentSide.SIDE1) {
619 if (side == FragmentSide.SIDE2) return myRightSide;
620 throw new IllegalArgumentException(String.valueOf(side));
623 public LineBlocks getLineBlocks() { return myLineBlocks; }
625 static JComponent createComponentForTitle(@Nullable String title,
626 @Nullable final LineSeparator sep1,
627 @Nullable final LineSeparator sep2,
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);
636 JPanel panel = new JPanel(new BorderLayout());
637 panel.add(new JLabel(title == null ? "" : title));
638 panel.add(bottomPanel, BorderLayout.SOUTH);
642 return new JBLabel(title == null ? "" : title);
647 public boolean canShowRequest(DiffRequest request) {
648 return myParentTool != null && myParentTool.canShow(request);
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);
656 myDataProvider.putData(myDiffRequest.getGenericData());
658 DiffContent content1 = data.getContents()[0];
659 DiffContent content2 = data.getContents()[1];
661 setContents(content1, content2);
664 setWindowTitle(myOwnerWindow, data.getWindowTitle());
665 myPanel.setToolbarActions(createToolbar());
666 data.customizeToolbar(myPanel.resetToolbar());
667 myPanel.registerToolbarActions();
668 initEditorSettings(getEditor1());
669 initEditorSettings(getEditor2());
671 final JComponent oldBottomComponent = myPanel.getBottomComponent();
672 if (oldBottomComponent instanceof Disposable) {
673 Disposer.dispose((Disposable)oldBottomComponent);
675 final JComponent newBottomComponent = data.getBottomComponent();
676 myPanel.setBottomComponent(newBottomComponent);
679 if (myIsRequestFocus) {
680 IdeFocusManager fm = IdeFocusManager.getInstance(myProject);
681 boolean isEditor1Focused = getEditor1() != null
682 && fm.getFocusedDescendantFor(getEditor1().getComponent()) != null;
684 boolean isEditor2Focused = myData.getContent2() != null
685 && getEditor2() != null
686 && fm.getFocusedDescendantFor(getEditor2().getComponent()) != null;
688 if (isEditor1Focused || isEditor2Focused) {
689 Editor e = isEditor2Focused ? getEditor2() : getEditor1();
691 fm.requestFocus(e.getContentComponent(), true);
695 myPanel.requestScrollEditors();
699 private static void initEditorSettings(@Nullable Editor editor) {
700 if (editor == null) {
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));
708 ((EditorEx)editor).getGutterComponentEx().setShowDefaultGutterPopup(false);
711 private void setTitles(@NotNull DiffRequest data) {
712 LineSeparator sep1 = data.getContents()[0].getLineSeparator();
713 LineSeparator sep2 = data.getContents()[1].getLineSeparator();
715 String title1 = addReadOnly(data.getContentTitles()[0], myLeftSide.getEditor());
716 String title2 = addReadOnly(data.getContentTitles()[1], myRightSide.getEditor());
718 setTitle1(createComponentForTitle(title1, sep1, sep2, true));
719 setTitle2(createComponentForTitle(title2, sep1, sep2, false));
722 private void setTitle1(JComponent title) {
723 myLeftSide.setTitle(title);
726 private void setTitle2(JComponent title) {
727 myRightSide.setTitle(title);
730 private static void setWindowTitle(Window window, String title) {
731 if (window instanceof JDialog) {
732 ((JDialog)window).setTitle(title);
734 else if (window instanceof JFrame) ((JFrame)window).setTitle(title);
738 public static DiffPanelImpl fromDataContext(DataContext dataContext) {
739 DiffViewer viewer = PlatformDataKeys.DIFF_VIEWER.getData(dataContext);
740 return viewer instanceof DiffPanelImpl ? (DiffPanelImpl)viewer : null;
743 public Window getOwnerWindow() {
744 return myOwnerWindow;
747 public void focusOppositeSide() {
748 if (myCurrentSide == myLeftSide) {
749 myRightSide.getEditor().getContentComponent().requestFocus();
752 myLeftSide.getEditor().getContentComponent().requestFocus();
756 public void setRequestFocus(boolean isRequestFocus) {
757 myIsRequestFocus = isRequestFocus;
760 private class MyScrollingPanel implements DiffPanelOuterComponent.ScrollingPanel {
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();
769 final Document document = myRightSide.getEditor().getDocument();
771 final FragmentList fragmentList = getFragments();
773 final Application application = ApplicationManager.getApplication();
774 application.executeOnPooledThread(new Runnable() {
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();
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();
791 final int finalLine = line;
792 final ModalityState modalityState = myOwnerWindow == null ? ModalityState.NON_MODAL : ModalityState.stateForComponent(myOwnerWindow);
793 application.invokeLater(new Runnable() {
796 if (finalLine >= 0) {
797 final int line = myLineBlocks.transform(myRightSide.getSide(), finalLine);
798 myLeftSide.scrollToFirstDiff(line);
800 scrollCurrentToFirstDiff();
809 private void scrollCurrentToFirstDiff() {
810 int[] fragments = getFragmentBeginnings();
811 if (fragments.length > 0) myCurrentSide.scrollToFirstDiff(fragments[0]);
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;
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>>();
829 public boolean hasNext() {
830 return !myBuffer.isEmpty() || myFragmentsIterator.hasNext();
834 public Pair<Integer, String> next() {
835 if (! myBuffer.isEmpty()) {
836 return myBuffer.remove(0);
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;
846 if (fragment == null) return null;
848 final TextRange textRange = fragment.getRange(FragmentSide.SIDE2);
849 ApplicationManager.getApplication().runReadAction(new Runnable() {
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;
861 if (lineStartOffset > lineEndOffset) continue;
862 String text = myDocument.getText().substring(lineStartOffset, lineEndOffset);
863 myBuffer.add(new Pair<Integer, String>(i, text));
867 if (myBuffer.isEmpty()) return null;
868 return myBuffer.remove(0);
872 public void remove() {
873 throw new UnsupportedOperationException();
877 private static class NavigationContextChecker {
878 private final Iterator<Pair<Integer, String>> myChangedLinesIterator;
879 private final DiffNavigationContext myContext;
881 private NavigationContextChecker(Iterator<Pair<Integer, String>> changedLinesIterator, DiffNavigationContext context) {
882 myChangedLinesIterator = changedLinesIterator;
886 public int contextMatchCheck() {
887 final Iterable<String> contextLines = myContext.getPreviousLinesIterable();
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();
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();
901 if (iterator.hasNext()) {
905 if (! myChangedLinesIterator.hasNext()) return -1;
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();
918 private class MyGenericDataProvider extends GenericDataProvider {
919 private final DiffPanelImpl myDiffPanel;
921 private MyGenericDataProvider(DiffPanelImpl diffPanel) {
922 myDiffPanel = diffPanel;
925 private final FocusDiffSide myFocusDiffSide = new FocusDiffSide() {
926 public Editor getEditor() {
927 return myDiffPanel.getCurrentSide().getEditor();
930 public int[] getFragmentStartingLines() {
931 return myDiffPanel.getFragmentBeginnings();
936 public Object getData(String dataId) {
937 if (PlatformDataKeys.DIFF_VIEWER.is(dataId)) {
940 if (FocusDiffSide.DATA_KEY.is(dataId)) {
941 return myDiffPanel.myCurrentSide == null ? null : myFocusDiffSide;
943 if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
944 final DiffSideView currentSide = myDiffPanel.myCurrentSide;
945 if (currentSide != null) {
946 return new DiffNavigatable(currentSide);
949 if (PlatformDataKeys.HELP_ID.is(dataId)) {
950 return "reference.dialogs.diff.file";
953 return super.getData(dataId);
957 public static class FileContentsAreIdenticalDiffPanel extends EditorNotificationPanel {
958 public FileContentsAreIdenticalDiffPanel() {
959 myLabel.setText(DiffBundle.message("diff.contents.are.identical.message.text"));
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"));
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.");
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>");
981 private class DiffNavigatable implements Navigatable {
982 private final DiffSideView mySide;
984 public DiffNavigatable(DiffSideView side) {
989 public boolean canNavigateToSource() {
994 public boolean canNavigate() {
999 public void navigate(boolean requestFocus) {
1000 final OpenFileDescriptor descriptor = mySide.getCurrentOpenFileDescriptor();
1001 if (descriptor != null) {
1002 showSource(descriptor);