| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.openapi.diff.impl; |
| |
| import com.intellij.icons.AllIcons; |
| import com.intellij.ide.actions.EditSourceAction; |
| import com.intellij.idea.ActionsBundle; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.application.Application; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.ModalityState; |
| import com.intellij.openapi.components.ServiceManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.diff.*; |
| import com.intellij.openapi.diff.actions.MergeActionGroup; |
| import com.intellij.openapi.diff.actions.ToggleAutoScrollAction; |
| import com.intellij.openapi.diff.ex.DiffPanelEx; |
| import com.intellij.openapi.diff.ex.DiffPanelOptions; |
| import com.intellij.openapi.diff.impl.external.DiffManagerImpl; |
| import com.intellij.openapi.diff.impl.fragments.Fragment; |
| import com.intellij.openapi.diff.impl.fragments.FragmentList; |
| import com.intellij.openapi.diff.impl.highlighting.DiffPanelState; |
| import com.intellij.openapi.diff.impl.highlighting.FragmentSide; |
| import com.intellij.openapi.diff.impl.processing.HighlightMode; |
| import com.intellij.openapi.diff.impl.processing.HorizontalDiffSplitter; |
| import com.intellij.openapi.diff.impl.settings.DiffMergeEditorSetting; |
| import com.intellij.openapi.diff.impl.settings.DiffMergeSettings; |
| import com.intellij.openapi.diff.impl.settings.DiffMergeSettingsAction; |
| import com.intellij.openapi.diff.impl.settings.DiffToolSettings; |
| import com.intellij.openapi.diff.impl.splitter.DiffDividerPaint; |
| import com.intellij.openapi.diff.impl.splitter.LineBlocks; |
| import com.intellij.openapi.diff.impl.util.*; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.ScrollType; |
| import com.intellij.openapi.editor.ScrollingModel; |
| import com.intellij.openapi.editor.event.VisibleAreaEvent; |
| import com.intellij.openapi.editor.event.VisibleAreaListener; |
| import com.intellij.openapi.editor.ex.EditorEx; |
| import com.intellij.openapi.editor.ex.EditorGutterComponentEx; |
| import com.intellij.openapi.editor.ex.EditorMarkupModel; |
| import com.intellij.openapi.fileEditor.OpenFileDescriptor; |
| import com.intellij.openapi.fileTypes.FileType; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.util.Getter; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.wm.IdeFocusManager; |
| import com.intellij.pom.Navigatable; |
| import com.intellij.ui.EditorNotificationPanel; |
| import com.intellij.ui.JBColor; |
| import com.intellij.ui.PopupHandler; |
| import com.intellij.ui.border.CustomLineBorder; |
| import com.intellij.ui.components.JBLabel; |
| import com.intellij.util.LineSeparator; |
| import com.intellij.util.containers.CacheOneStepIterator; |
| import com.intellij.util.diff.FilesTooBigForDiffException; |
| import com.intellij.util.ui.PlatformColors; |
| import gnu.trove.TIntFunction; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import java.awt.*; |
| import java.awt.event.MouseListener; |
| import java.util.Arrays; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| public class DiffPanelImpl implements DiffPanelEx, ContentChangeListener, TwoSidesContainer { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.DiffPanelImpl"); |
| |
| private final DiffSplitterI mySplitter; |
| private final DiffPanelOuterComponent myPanel; |
| |
| private final Window myOwnerWindow; |
| private final DiffPanelOptions myOptions; |
| |
| private final DiffPanelState myData; |
| |
| private final Rediffers myDiffUpdater; |
| private final DiffSideView myLeftSide; |
| private final DiffSideView myRightSide; |
| private DiffSideView myCurrentSide; |
| private LineBlocks myLineBlocks = LineBlocks.EMPTY; |
| private final SyncScrollSupport myScrollSupport = new SyncScrollSupport(); |
| private final FontSizeSynchronizer myFontSizeSynchronizer = new FontSizeSynchronizer(); |
| private DiffRequest myDiffRequest; |
| private boolean myIsRequestFocus = true; |
| private boolean myIsSyncScroll; |
| |
| private DiffRequest.ToolbarAddons createToolbar() { |
| return new DiffRequest.ToolbarAddons() { |
| public void customize(DiffToolbar toolbar) { |
| ActionManager actionManager = ActionManager.getInstance(); |
| toolbar.addAction(actionManager.getAction("DiffPanel.Toolbar")); |
| toolbar.addSeparator(); |
| toolbar.addAction(new ToggleAutoScrollAction()); |
| toolbar.addSeparator(); |
| toolbar.addAction(actionManager.getAction("ContextHelp")); |
| toolbar.addAction(getEditSourceAction()); |
| toolbar.addSeparator(); |
| toolbar.addAction(new DiffMergeSettingsAction(Arrays.asList(getEditor1(), getEditor2()), |
| ServiceManager.getService(myProject, DiffToolSettings.class))); |
| } |
| |
| @NotNull |
| private AnAction getEditSourceAction() { |
| AnAction editSourceAction = new EditSourceAction(); |
| editSourceAction.getTemplatePresentation().setIcon(AllIcons.Actions.EditSource); |
| editSourceAction.getTemplatePresentation().setText(ActionsBundle.actionText("EditSource")); |
| editSourceAction.getTemplatePresentation().setDescription(ActionsBundle.actionText("EditSource")); |
| editSourceAction.registerCustomShortcutSet(CommonShortcuts.getEditSource(), myPanel, DiffPanelImpl.this); |
| return editSourceAction; |
| } |
| }; |
| } |
| |
| private boolean myDisposed = false; |
| private final GenericDataProvider myDataProvider; |
| private final Project myProject; |
| private final boolean myIsHorizontal; |
| private final DiffTool myParentTool; |
| private EditorNotificationPanel myTopMessageDiffPanel; |
| private final VisibleAreaListener myVisibleAreaListener; |
| private final int myDiffDividerPolygonsOffset; |
| |
| public DiffPanelImpl(final Window owner, |
| Project project, |
| boolean enableToolbar, |
| boolean horizontal, |
| int diffDividerPolygonsOffset, |
| DiffTool parentTool) { |
| myProject = project; |
| myIsHorizontal = horizontal; |
| myParentTool = parentTool; |
| myOptions = new DiffPanelOptions(this); |
| myPanel = new DiffPanelOuterComponent(TextDiffType.DIFF_TYPES, null); |
| myPanel.disableToolbar(!enableToolbar); |
| if (enableToolbar) myPanel.resetToolbar(); |
| myOwnerWindow = owner; |
| myIsSyncScroll = true; |
| final boolean v = !horizontal; |
| myLeftSide = new DiffSideView(this, new CustomLineBorder(1, 0, v ? 0 : 1, v ? 0 : 1)); |
| myRightSide = new DiffSideView(this, new CustomLineBorder(v ? 0 : 1, v ? 0 : 1, 1, 0)); |
| myLeftSide.becomeMaster(); |
| myDiffUpdater = new Rediffers(this); |
| |
| myDiffDividerPolygonsOffset = diffDividerPolygonsOffset; |
| |
| myData = createDiffPanelState(this); |
| |
| if (horizontal) { |
| mySplitter = new DiffSplitter(myLeftSide.getComponent(), myRightSide.getComponent(), |
| new DiffDividerPaint(this, FragmentSide.SIDE1, diffDividerPolygonsOffset), myData); |
| } |
| else { |
| mySplitter = new HorizontalDiffSplitter(myLeftSide.getComponent(), myRightSide.getComponent()); |
| } |
| |
| myPanel.insertDiffComponent(mySplitter.getComponent(), new MyScrollingPanel()); |
| myDataProvider = new MyGenericDataProvider(this); |
| myPanel.setDataProvider(myDataProvider); |
| |
| final ComparisonPolicy comparisonPolicy = getComparisonPolicy(); |
| final ComparisonPolicy defaultComparisonPolicy = DiffManagerImpl.getInstanceEx().getComparisonPolicy(); |
| final HighlightMode highlightMode = getHighlightMode(); |
| final HighlightMode defaultHighlightMode = DiffManagerImpl.getInstanceEx().getHighlightMode(); |
| |
| if (defaultComparisonPolicy != null && comparisonPolicy != defaultComparisonPolicy) { |
| setComparisonPolicy(defaultComparisonPolicy); |
| } |
| if (defaultHighlightMode != null && highlightMode != defaultHighlightMode) { |
| setHighlightMode(defaultHighlightMode); |
| } |
| myVisibleAreaListener = new VisibleAreaListener() { |
| @Override |
| public void visibleAreaChanged(VisibleAreaEvent e) { |
| Editor editor1 = getEditor1(); |
| if (editor1 != null) { |
| editor1.getComponent().repaint(); |
| } |
| Editor editor2 = getEditor2(); |
| if (editor2 != null) { |
| editor2.getComponent().repaint(); |
| } |
| } |
| }; |
| registerActions(); |
| } |
| |
| private void registerActions() { |
| //control+tab switches editors |
| new AnAction(){ |
| @Override |
| public void actionPerformed(AnActionEvent e) { |
| if (getEditor1() != null && getEditor2() != null) { |
| Editor focus = getEditor1().getContentComponent().hasFocus() ? getEditor2() : getEditor1(); |
| IdeFocusManager.getGlobalInstance().requestFocus(focus.getContentComponent(), true); |
| focus.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); |
| } |
| } |
| }.registerCustomShortcutSet(CustomShortcutSet.fromString("control TAB"), myPanel, this); |
| } |
| |
| protected DiffPanelState createDiffPanelState(@NotNull Disposable parentDisposable) { |
| return new DiffPanelState(this, myProject, getDiffDividerPolygonsOffset(), parentDisposable); |
| } |
| |
| public int getDiffDividerPolygonsOffset() { |
| return myDiffDividerPolygonsOffset; |
| } |
| |
| public boolean isHorisontal() { |
| return myIsHorizontal; |
| } |
| |
| public DiffPanelState getDiffPanelState() { |
| return myData; |
| } |
| |
| public void noSynchScroll() { |
| myIsSyncScroll = false; |
| } |
| |
| public DiffSplitterI getSplitter() { |
| return mySplitter; |
| } |
| |
| public void reset() { |
| //myUi.getContentManager().removeAllContents(false); |
| myPanel.setPreferredHeightGetter(null); |
| } |
| |
| public void prefferedSizeByContents(final int maximumLines) { |
| if (getEditor1() == null && getEditor2() == null) return; |
| |
| if (getEditor1() != null) { |
| getEditor1().getSettings().setAdditionalLinesCount(0); |
| getEditor1().getSettings().setAdditionalPageAtBottom(false); |
| } |
| if (getEditor2() != null) { |
| getEditor2().getSettings().setAdditionalLinesCount(0); |
| getEditor2().getSettings().setAdditionalPageAtBottom(false); |
| } |
| myPanel.setPrefferedWidth(20); |
| myPanel.setPreferredHeightGetter(new Getter<Integer>() { |
| @Override |
| public Integer get() { |
| final int size1 = getEditor1() == null ? 10 : getEditor1().getComponent().getPreferredSize().height; |
| final int size2 = getEditor2() == null ? 10 : getEditor2().getComponent().getPreferredSize().height; |
| final int lineHeight = getEditor1() == null ? getEditor2().getLineHeight() : getEditor1().getLineHeight(); |
| final int size = Math.max(size1, size2); |
| if (maximumLines > 0) { |
| return Math.min(size, maximumLines * lineHeight); |
| } |
| return size; |
| } |
| }); |
| } |
| |
| @Nullable |
| public Editor getEditor1() { |
| return myLeftSide.getEditor(); |
| } |
| |
| @Nullable |
| public Editor getEditor2() { |
| if (myDisposed) LOG.error("Disposed"); |
| Editor editor = myRightSide.getEditor(); |
| if (editor != null) return editor; |
| if (myData.getContent2() == null) LOG.error("No content 2"); |
| return editor; |
| } |
| |
| public void setContents(DiffContent content1, DiffContent content2) { |
| LOG.assertTrue(content1 != null && content2 != null); |
| LOG.assertTrue(!myDisposed); |
| myData.setContents(content1, content2); |
| Project project = myData.getProject(); |
| FileType[] types = DiffUtil.chooseContentTypes(new DiffContent[]{content1, content2}); |
| VirtualFile beforeFile = content1.getFile(); |
| VirtualFile afterFile = content2.getFile(); |
| String path = myDiffRequest == null ? null : myDiffRequest.getWindowTitle(); |
| myLeftSide.setHighlighterFactory(createHighlighter(types[0], beforeFile, afterFile, path, project)); |
| myRightSide.setHighlighterFactory(createHighlighter(types[1], afterFile, beforeFile, path, project)); |
| setSplitterProportion(content1, content2); |
| rediff(); |
| if (myIsRequestFocus) { |
| myPanel.requestScrollEditors(); |
| } |
| } |
| |
| private void setSplitterProportion(DiffContent content1, DiffContent content2) { |
| if (content1.isEmpty()) { |
| mySplitter.setProportion(0f); |
| mySplitter.setResizeEnabled(false); |
| return; |
| } |
| if (content2.isEmpty()) { |
| mySplitter.setProportion(1.0f); |
| mySplitter.setResizeEnabled(false); |
| return; |
| } |
| mySplitter.setProportion(0.5f); |
| mySplitter.setResizeEnabled(true); |
| } |
| |
| public void removeStatusBar() { |
| myPanel.removeStatusBar(); |
| } |
| |
| public void enableToolbar(final boolean value) { |
| myPanel.disableToolbar(!value); |
| } |
| |
| public void setLineNumberConvertors(@NotNull TIntFunction old, @NotNull TIntFunction newConvertor) { |
| if (getEditor1() != null) { |
| ((EditorGutterComponentEx) getEditor1().getGutter()).setLineNumberConvertor(old); |
| } |
| if (getEditor2() != null) { |
| ((EditorGutterComponentEx) getEditor2().getGutter()).setLineNumberConvertor(newConvertor); |
| } |
| } |
| // todo pay attention here |
| private static DiffHighlighterFactory createHighlighter(FileType contentType, |
| VirtualFile file, |
| VirtualFile otherFile, |
| String path, |
| Project project) { |
| VirtualFile baseFile = file; |
| if (baseFile == null) baseFile = otherFile; |
| if (baseFile == null && path != null) baseFile = LocalFileSystem.getInstance().findFileByPath(path); |
| |
| return new DiffHighlighterFactoryImpl(contentType, baseFile, project); |
| } |
| |
| void rediff() { |
| try { |
| if (myTopMessageDiffPanel != null) { |
| myPanel.removeTopComponent(myTopMessageDiffPanel); |
| } |
| LineBlocks blocks = myData.updateEditors(); |
| setLineBlocks(blocks != null ? blocks : LineBlocks.EMPTY); |
| if (blocks != null && blocks.getCount() == 0) { |
| if (myData.isContentsEqual()) { |
| setFileContentsAreIdentical(); |
| } |
| } |
| } |
| catch (FilesTooBigForDiffException e) { |
| setTooBigFileErrorContents(); |
| } |
| } |
| |
| public void setTooBigFileErrorContents() { |
| setLineBlocks(LineBlocks.EMPTY); |
| myTopMessageDiffPanel = new CanNotCalculateDiffPanel(); |
| myPanel.insertTopComponent(myTopMessageDiffPanel); |
| } |
| |
| public void setPatchAppliedApproximately() { |
| if (!(myTopMessageDiffPanel instanceof CanNotCalculateDiffPanel)) { |
| myTopMessageDiffPanel = new DiffIsApproximate(); |
| myPanel.insertTopComponent(myTopMessageDiffPanel); |
| } |
| } |
| |
| public void setFileContentsAreIdentical() { |
| if (myTopMessageDiffPanel == null || myTopMessageDiffPanel instanceof FileContentsAreIdenticalDiffPanel) { |
| LineSeparator sep1 = myData.getContent1() == null ? null : myData.getContent1().getLineSeparator(); |
| LineSeparator sep2 = myData.getContent2() == null ? null : myData.getContent2().getLineSeparator(); |
| |
| if (LineSeparator.knownAndDifferent(sep1, sep2)) { |
| myTopMessageDiffPanel = new LineSeparatorsOnlyDiffPanel(); |
| } |
| else { |
| myTopMessageDiffPanel = new FileContentsAreIdenticalDiffPanel(); |
| } |
| myPanel.insertTopComponent(myTopMessageDiffPanel); |
| } |
| } |
| |
| public void setTitle1(String title) { |
| setTitle(title, true); |
| } |
| |
| private void setTitle(String title, boolean left) { |
| Editor editor = left ? getEditor1() : getEditor2(); |
| if (editor == null) return; |
| title = addReadOnly(title, editor); |
| JLabel label = new JLabel(title); |
| if (left) { |
| myLeftSide.setTitle(label); |
| } |
| else { |
| myRightSide.setTitle(label); |
| } |
| } |
| |
| @Nullable |
| private static String addReadOnly(@Nullable String title, @Nullable Editor editor) { |
| if (editor == null || title == null) { |
| return title; |
| } |
| boolean readonly = editor.isViewer() || !editor.getDocument().isWritable(); |
| if (readonly) { |
| title += " " + DiffBundle.message("diff.content.read.only.content.title.suffix"); |
| } |
| return title; |
| } |
| |
| public void setTitle2(String title) { |
| setTitle(title, false); |
| } |
| |
| private void setLineBlocks(@NotNull LineBlocks blocks) { |
| myLineBlocks = blocks; |
| mySplitter.redrawDiffs(); |
| updateStatusBar(); |
| } |
| |
| public void invalidateDiff() { |
| setLineBlocks(LineBlocks.EMPTY); |
| myData.removeActions(); |
| } |
| |
| public FragmentList getFragments() { |
| return myData.getFragmentList(); |
| } |
| |
| private int[] getFragmentBeginnings() { |
| return getFragmentBeginnings(myCurrentSide.getSide()); |
| } |
| |
| int[] getFragmentBeginnings(FragmentSide side) { |
| return getLineBlocks().getBeginnings(side); |
| } |
| |
| public void dispose() { |
| myDisposed = true; |
| myDiffUpdater.dispose(); |
| Disposer.dispose(myScrollSupport); |
| Disposer.dispose(myData); |
| myPanel.cancelScrollEditors(); |
| JComponent component = myPanel.getBottomComponent(); |
| if (component instanceof Disposable) { |
| Disposer.dispose((Disposable)component); |
| } |
| myPanel.setBottomComponent(null); |
| myPanel.setDataProvider(null); |
| myPanel.setScrollingPanel(null); |
| } |
| |
| public JComponent getComponent() { |
| return myPanel; |
| } |
| |
| private void updateStatusBar() { |
| myPanel.setStatusBarText(getNumDifferencesText()); |
| } |
| |
| public String getNumDifferencesText() { |
| return DiffBundle.message("diff.count.differences.status.text", getLineBlocks().getCount()); |
| } |
| |
| public boolean hasDifferences() { |
| return getLineBlocks().getCount() > 0 || myTopMessageDiffPanel != null; |
| } |
| |
| @Nullable |
| public JComponent getPreferredFocusedComponent() { |
| return myCurrentSide.getFocusableComponent(); |
| } |
| |
| public int getContentsNumber() { |
| return 2; |
| } |
| |
| @Override |
| public boolean acceptsType(DiffViewerType type) { |
| return DiffViewerType.contents.equals(type); |
| } |
| |
| public ComparisonPolicy getComparisonPolicy() { |
| return myData.getComparisonPolicy(); |
| } |
| |
| public void setComparisonPolicy(ComparisonPolicy comparisonPolicy) { |
| setComparisonPolicy(comparisonPolicy, true); |
| } |
| |
| private void setComparisonPolicy(ComparisonPolicy policy, boolean notifyManager) { |
| myData.setComparisonPolicy(policy); |
| rediff(); |
| |
| if (notifyManager) { |
| DiffManagerImpl.getInstanceEx().setComparisonPolicy(policy); |
| } |
| } |
| |
| @NotNull |
| public HighlightMode getHighlightMode() { |
| return myData.getHighlightMode(); |
| } |
| |
| public void setHighlightMode(@NotNull HighlightMode mode) { |
| setHighlightMode(mode, true); |
| } |
| |
| public void setHighlightMode(@NotNull HighlightMode mode, boolean notifyManager) { |
| myData.setHighlightMode(mode); |
| rediff(); |
| |
| if (notifyManager) { |
| DiffManagerImpl.getInstanceEx().setHighlightMode(mode); |
| } |
| } |
| |
| public void setAutoScrollEnabled(boolean enabled) { |
| myScrollSupport.setEnabled(enabled); |
| } |
| |
| public boolean isAutoScrollEnabled() { |
| return myScrollSupport.isEnabled(); |
| } |
| |
| public Rediffers getDiffUpdater() { |
| return myDiffUpdater; |
| } |
| |
| public void onContentChangedIn(EditorSource source) { |
| myDiffUpdater.contentRemoved(source); |
| final EditorEx editor = source.getEditor(); |
| if (myIsHorizontal && source.getSide() == FragmentSide.SIDE1 && editor != null) { |
| editor.setVerticalScrollbarOrientation(EditorEx.VERTICAL_SCROLLBAR_LEFT); |
| } |
| DiffSideView viewSide = getSideView(source.getSide()); |
| viewSide.setEditorSource(getProject(), source); |
| Disposer.dispose(myScrollSupport); |
| if (editor == null) { |
| if (!myDisposed) { |
| rediff(); |
| } |
| return; |
| } |
| |
| final MouseListener mouseListener = PopupHandler |
| .installUnknownPopupHandler(editor.getContentComponent(), new MergeActionGroup(this, source.getSide()), ActionManager.getInstance()); |
| myDiffUpdater.contentAdded(source); |
| editor.getSettings().setLineNumbersShown(true); |
| editor.getSettings().setFoldingOutlineShown(false); |
| editor.getFoldingModel().setFoldingEnabled(false); |
| ((EditorMarkupModel)editor.getMarkupModel()).setErrorStripeVisible(true); |
| |
| Editor editor1 = getEditor(FragmentSide.SIDE1); |
| Editor editor2 = getEditor(FragmentSide.SIDE2); |
| if (editor1 != null && editor2 != null && myIsSyncScroll) { |
| myScrollSupport.install(new EditingSides[]{this}); |
| } |
| |
| final VisibleAreaListener visibleAreaListener = mySplitter.getVisibleAreaListener(); |
| final ScrollingModel scrollingModel = editor.getScrollingModel(); |
| if (visibleAreaListener != null) { |
| scrollingModel.addVisibleAreaListener(visibleAreaListener); |
| scrollingModel.addVisibleAreaListener(myVisibleAreaListener); |
| } |
| myFontSizeSynchronizer.synchronize(editor); |
| source.addDisposable(new Disposable() { |
| public void dispose() { |
| myFontSizeSynchronizer.stopSynchronize(editor); |
| } |
| }); |
| source.addDisposable(new Disposable() { |
| public void dispose() { |
| if (visibleAreaListener != null) { |
| scrollingModel.removeVisibleAreaListener(visibleAreaListener); |
| scrollingModel.removeVisibleAreaListener(myVisibleAreaListener); |
| } |
| editor.getContentComponent().removeMouseListener(mouseListener); |
| } |
| }); |
| } |
| |
| public void setCurrentSide(@NotNull DiffSideView viewSide) { |
| LOG.assertTrue(viewSide != myCurrentSide); |
| if (myCurrentSide != null) myCurrentSide.beSlave(); |
| myCurrentSide = viewSide; |
| } |
| |
| public DiffSideView getCurrentSide() { return myCurrentSide; } |
| |
| public Project getProject() { |
| return myData.getProject(); |
| } |
| |
| public void showSource(@Nullable OpenFileDescriptor descriptor) { |
| myOptions.showSource(descriptor); |
| } |
| |
| public DiffPanelOptions getOptions() { |
| return myOptions; |
| } |
| |
| public Editor getEditor(FragmentSide side) { |
| return getSideView(side).getEditor(); |
| } |
| |
| public DiffSideView getSideView(FragmentSide side) { |
| if (side == FragmentSide.SIDE1) { |
| return myLeftSide; |
| } |
| if (side == FragmentSide.SIDE2) return myRightSide; |
| throw new IllegalArgumentException(String.valueOf(side)); |
| } |
| |
| public LineBlocks getLineBlocks() { return myLineBlocks; } |
| |
| static JComponent createComponentForTitle(@Nullable String title, |
| @Nullable final LineSeparator sep1, |
| @Nullable final LineSeparator sep2, |
| boolean left) { |
| if (sep1 != null && sep2 != null && !sep1.equals(sep2)) { |
| LineSeparator separator = left ? sep1 : sep2; |
| JPanel bottomPanel = new JPanel(new BorderLayout()); |
| JLabel sepLabel = new JLabel(separator.name()); |
| sepLabel.setForeground(separator.equals(LineSeparator.CRLF) ? JBColor.RED : PlatformColors.BLUE); |
| bottomPanel.add(sepLabel, left ? BorderLayout.EAST : BorderLayout.WEST); |
| |
| JPanel panel = new JPanel(new BorderLayout()); |
| panel.add(new JLabel(title == null ? "" : title)); |
| panel.add(bottomPanel, BorderLayout.SOUTH); |
| return panel; |
| } |
| else { |
| return new JBLabel(title == null ? "" : title); |
| } |
| } |
| |
| @Override |
| public boolean canShowRequest(DiffRequest request) { |
| return myParentTool != null && myParentTool.canShow(request); |
| } |
| |
| public void setDiffRequest(DiffRequest data) { |
| myDiffRequest = data; |
| if (data.getHints().contains(DiffTool.HINT_DO_NOT_IGNORE_WHITESPACES)) { |
| setComparisonPolicy(ComparisonPolicy.DEFAULT, false); |
| } |
| myDataProvider.putData(myDiffRequest.getGenericData()); |
| |
| DiffContent content1 = data.getContents()[0]; |
| DiffContent content2 = data.getContents()[1]; |
| |
| setContents(content1, content2); |
| setTitles(data); |
| |
| setWindowTitle(myOwnerWindow, data.getWindowTitle()); |
| myPanel.setToolbarActions(createToolbar()); |
| data.customizeToolbar(myPanel.resetToolbar()); |
| myPanel.registerToolbarActions(); |
| initEditorSettings(getEditor1()); |
| initEditorSettings(getEditor2()); |
| |
| final JComponent oldBottomComponent = myPanel.getBottomComponent(); |
| if (oldBottomComponent instanceof Disposable) { |
| Disposer.dispose((Disposable)oldBottomComponent); |
| } |
| final JComponent newBottomComponent = data.getBottomComponent(); |
| myPanel.setBottomComponent(newBottomComponent); |
| |
| |
| if (myIsRequestFocus) { |
| IdeFocusManager fm = IdeFocusManager.getInstance(myProject); |
| boolean isEditor1Focused = getEditor1() != null |
| && fm.getFocusedDescendantFor(getEditor1().getComponent()) != null; |
| |
| boolean isEditor2Focused = myData.getContent2() != null |
| && getEditor2() != null |
| && fm.getFocusedDescendantFor(getEditor2().getComponent()) != null; |
| |
| if (isEditor1Focused || isEditor2Focused) { |
| Editor e = isEditor2Focused ? getEditor2() : getEditor1(); |
| if (e != null) { |
| fm.requestFocus(e.getContentComponent(), true); |
| } |
| } |
| |
| myPanel.requestScrollEditors(); |
| } |
| } |
| |
| private static void initEditorSettings(@Nullable Editor editor) { |
| if (editor == null) { |
| return; |
| } |
| Project project = editor.getProject(); |
| DiffMergeSettings settings = project == null ? null : ServiceManager.getService(project, DiffToolSettings.class); |
| for (DiffMergeEditorSetting property : DiffMergeEditorSetting.values()) { |
| property.apply(editor, settings == null ? property.getDefault() : settings.getPreference(property)); |
| } |
| ((EditorEx)editor).getGutterComponentEx().setShowDefaultGutterPopup(false); |
| } |
| |
| private void setTitles(@NotNull DiffRequest data) { |
| LineSeparator sep1 = data.getContents()[0].getLineSeparator(); |
| LineSeparator sep2 = data.getContents()[1].getLineSeparator(); |
| |
| String title1 = addReadOnly(data.getContentTitles()[0], myLeftSide.getEditor()); |
| String title2 = addReadOnly(data.getContentTitles()[1], myRightSide.getEditor()); |
| |
| setTitle1(createComponentForTitle(title1, sep1, sep2, true)); |
| setTitle2(createComponentForTitle(title2, sep1, sep2, false)); |
| } |
| |
| private void setTitle1(JComponent title) { |
| myLeftSide.setTitle(title); |
| } |
| |
| private void setTitle2(JComponent title) { |
| myRightSide.setTitle(title); |
| } |
| |
| private static void setWindowTitle(Window window, String title) { |
| if (window instanceof JDialog) { |
| ((JDialog)window).setTitle(title); |
| } |
| else if (window instanceof JFrame) ((JFrame)window).setTitle(title); |
| } |
| |
| @Nullable |
| public static DiffPanelImpl fromDataContext(DataContext dataContext) { |
| DiffViewer viewer = PlatformDataKeys.DIFF_VIEWER.getData(dataContext); |
| return viewer instanceof DiffPanelImpl ? (DiffPanelImpl)viewer : null; |
| } |
| |
| public Window getOwnerWindow() { |
| return myOwnerWindow; |
| } |
| |
| public void focusOppositeSide() { |
| if (myCurrentSide == myLeftSide) { |
| myRightSide.getEditor().getContentComponent().requestFocus(); |
| } |
| else { |
| myLeftSide.getEditor().getContentComponent().requestFocus(); |
| } |
| } |
| |
| public void setRequestFocus(boolean isRequestFocus) { |
| myIsRequestFocus = isRequestFocus; |
| } |
| |
| private class MyScrollingPanel implements DiffPanelOuterComponent.ScrollingPanel { |
| |
| public void scrollEditors() { |
| getOptions().onNewContent(myCurrentSide); |
| final DiffNavigationContext scrollContext = myDiffRequest == null ? null : |
| (DiffNavigationContext) myDiffRequest.getGenericData().get(DiffTool.SCROLL_TO_LINE.getName()); |
| if (scrollContext == null) { |
| scrollCurrentToFirstDiff(); |
| } else { |
| final Document document = myRightSide.getEditor().getDocument(); |
| |
| final FragmentList fragmentList = getFragments(); |
| |
| final Application application = ApplicationManager.getApplication(); |
| application.executeOnPooledThread(new Runnable() { |
| @Override |
| public void run() { |
| final ChangedLinesIterator changedLinesIterator = new ChangedLinesIterator(fragmentList.iterator(), document, false); |
| final CacheOneStepIterator<Pair<Integer, String>> cacheOneStepIterator = |
| new CacheOneStepIterator<Pair<Integer, String>>(changedLinesIterator); |
| final NavigationContextChecker checker = new NavigationContextChecker(cacheOneStepIterator, scrollContext); |
| int line = checker.contextMatchCheck(); |
| if (line < 0) { |
| // this will work for the case, when spaces changes are ignored, and corresponding fragments are not reported as changed |
| // just try to find target line -> +- |
| final ChangedLinesIterator changedLinesIterator2 = new ChangedLinesIterator(fragmentList.iterator(), document, true); |
| final CacheOneStepIterator<Pair<Integer, String>> cacheOneStepIterator2 = |
| new CacheOneStepIterator<Pair<Integer, String>>(changedLinesIterator2); |
| final NavigationContextChecker checker2 = new NavigationContextChecker(cacheOneStepIterator2, scrollContext); |
| line = checker2.contextMatchCheck(); |
| } |
| final int finalLine = line; |
| final ModalityState modalityState = myOwnerWindow == null ? ModalityState.NON_MODAL : ModalityState.stateForComponent(myOwnerWindow); |
| application.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| if (finalLine >= 0) { |
| final int line = myLineBlocks.transform(myRightSide.getSide(), finalLine); |
| myLeftSide.scrollToFirstDiff(line); |
| } else { |
| scrollCurrentToFirstDiff(); |
| } |
| } |
| }, modalityState); |
| } |
| }); |
| } |
| } |
| |
| private void scrollCurrentToFirstDiff() { |
| int[] fragments = getFragmentBeginnings(); |
| if (fragments.length > 0) myCurrentSide.scrollToFirstDiff(fragments[0]); |
| } |
| } |
| |
| private static class ChangedLinesIterator implements Iterator<Pair<Integer, String>> { |
| private final Document myDocument; |
| private final boolean myIgnoreFragmentsType; |
| private final Iterator<Fragment> myFragmentsIterator; |
| private final List<Pair<Integer, String>> myBuffer; |
| |
| private ChangedLinesIterator(Iterator<Fragment> fragmentsIterator, Document document, final boolean ignoreFragmentsType) { |
| myFragmentsIterator = fragmentsIterator; |
| myDocument = document; |
| myIgnoreFragmentsType = ignoreFragmentsType; |
| myBuffer = new LinkedList<Pair<Integer, String>>(); |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return !myBuffer.isEmpty() || myFragmentsIterator.hasNext(); |
| } |
| |
| @Override |
| public Pair<Integer, String> next() { |
| if (! myBuffer.isEmpty()) { |
| return myBuffer.remove(0); |
| } |
| |
| Fragment fragment = null; |
| while (myFragmentsIterator.hasNext()) { |
| fragment = myFragmentsIterator.next(); |
| final TextDiffTypeEnum type = fragment.getType(); |
| if (! myIgnoreFragmentsType && (type == null || TextDiffTypeEnum.DELETED.equals(type) || TextDiffTypeEnum.NONE.equals(type))) continue; |
| break; |
| } |
| if (fragment == null) return null; |
| |
| final TextRange textRange = fragment.getRange(FragmentSide.SIDE2); |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| @Override |
| public void run() { |
| final int startLine = myDocument.getLineNumber(textRange.getStartOffset()); |
| final int endFragmentOffset = textRange.getEndOffset(); |
| final int endLine = myDocument.getLineNumber(endFragmentOffset); |
| for (int i = startLine; i <= endLine; i++) { |
| int lineEndOffset = myDocument.getLineEndOffset(i); |
| final int lineStartOffset = myDocument.getLineStartOffset(i); |
| if (lineEndOffset > endFragmentOffset && endFragmentOffset == lineStartOffset) { |
| lineEndOffset = endFragmentOffset; |
| } |
| if (lineStartOffset > lineEndOffset) continue; |
| String text = myDocument.getText().substring(lineStartOffset, lineEndOffset); |
| myBuffer.add(new Pair<Integer, String>(i, text)); |
| } |
| } |
| }); |
| if (myBuffer.isEmpty()) return null; |
| return myBuffer.remove(0); |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| private static class NavigationContextChecker { |
| private final Iterator<Pair<Integer, String>> myChangedLinesIterator; |
| private final DiffNavigationContext myContext; |
| |
| private NavigationContextChecker(Iterator<Pair<Integer, String>> changedLinesIterator, DiffNavigationContext context) { |
| myChangedLinesIterator = changedLinesIterator; |
| myContext = context; |
| } |
| |
| public int contextMatchCheck() { |
| final Iterable<String> contextLines = myContext.getPreviousLinesIterable(); |
| |
| // we ignore spaces.. at least at start/end, since some version controls could ignore their changes when doing annotate |
| final Iterator<String> iterator = contextLines.iterator(); |
| if (iterator.hasNext()) { |
| String contextLine = iterator.next().trim(); |
| |
| while (myChangedLinesIterator.hasNext()) { |
| final Pair<Integer, String> pair = myChangedLinesIterator.next(); |
| if (pair.getSecond().trim().equals(contextLine)) { |
| if (! iterator.hasNext()) break; |
| contextLine = iterator.next().trim(); |
| } |
| } |
| if (iterator.hasNext()) { |
| return -1; |
| } |
| } |
| if (! myChangedLinesIterator.hasNext()) return -1; |
| |
| final String targetLine = myContext.getTargetString().trim(); |
| while (myChangedLinesIterator.hasNext()) { |
| final Pair<Integer, String> pair = myChangedLinesIterator.next(); |
| if (pair.getSecond().trim().equals(targetLine)) { |
| return pair.getFirst(); |
| } |
| } |
| return -1; |
| } |
| } |
| |
| private class MyGenericDataProvider extends GenericDataProvider { |
| private final DiffPanelImpl myDiffPanel; |
| |
| private MyGenericDataProvider(DiffPanelImpl diffPanel) { |
| myDiffPanel = diffPanel; |
| } |
| |
| private final FocusDiffSide myFocusDiffSide = new FocusDiffSide() { |
| public Editor getEditor() { |
| return myDiffPanel.getCurrentSide().getEditor(); |
| } |
| |
| public int[] getFragmentStartingLines() { |
| return myDiffPanel.getFragmentBeginnings(); |
| } |
| }; |
| |
| @Override |
| public Object getData(String dataId) { |
| if (PlatformDataKeys.DIFF_VIEWER.is(dataId)) { |
| return myDiffPanel; |
| } |
| if (FocusDiffSide.DATA_KEY.is(dataId)) { |
| return myDiffPanel.myCurrentSide == null ? null : myFocusDiffSide; |
| } |
| if (CommonDataKeys.NAVIGATABLE.is(dataId)) { |
| final DiffSideView currentSide = myDiffPanel.myCurrentSide; |
| if (currentSide != null) { |
| return new DiffNavigatable(currentSide); |
| } |
| } |
| if (PlatformDataKeys.HELP_ID.is(dataId)) { |
| return "reference.dialogs.diff.file"; |
| } |
| |
| return super.getData(dataId); |
| } |
| } |
| |
| public static class FileContentsAreIdenticalDiffPanel extends EditorNotificationPanel { |
| public FileContentsAreIdenticalDiffPanel() { |
| myLabel.setText(DiffBundle.message("diff.contents.are.identical.message.text")); |
| } |
| } |
| |
| public static class LineSeparatorsOnlyDiffPanel extends FileContentsAreIdenticalDiffPanel { |
| public LineSeparatorsOnlyDiffPanel() { |
| myLabel.setText(DiffBundle.message("diff.contents.have.differences.only.in.line.separators.message.text")); |
| } |
| } |
| |
| public static class CanNotCalculateDiffPanel extends EditorNotificationPanel { |
| public CanNotCalculateDiffPanel() { |
| myLabel.setText("Can not calculate diff. File is too big and there are too many changes."); |
| } |
| } |
| |
| public static class DiffIsApproximate extends EditorNotificationPanel { |
| public DiffIsApproximate() { |
| myLabel.setText("<html>Couldn't find context for patch. Some fragments were applied at the best possible place. <b>Please check carefully.</b></html>"); |
| } |
| } |
| |
| private class DiffNavigatable implements Navigatable { |
| private final DiffSideView mySide; |
| |
| public DiffNavigatable(DiffSideView side) { |
| mySide = side; |
| } |
| |
| @Override |
| public boolean canNavigateToSource() { |
| return false; |
| } |
| |
| @Override |
| public boolean canNavigate() { |
| return true; |
| } |
| |
| @Override |
| public void navigate(boolean requestFocus) { |
| showSource(mySide.getCurrentOpenFileDescriptor()); |
| } |
| } |
| } |