Merge branch 'master' into new-merge
authorAleksey Pivovarov <AMPivovarov@gmail.com>
Wed, 19 Aug 2015 11:07:35 +0000 (14:07 +0300)
committerAleksey Pivovarov <AMPivovarov@gmail.com>
Wed, 19 Aug 2015 11:07:35 +0000 (14:07 +0300)
75 files changed:
platform/diff-api/src/com/intellij/diff/DiffContentFactory.java
platform/diff-api/src/com/intellij/diff/DiffManager.java
platform/diff-api/src/com/intellij/diff/DiffManagerEx.java
platform/diff-api/src/com/intellij/diff/DiffRequestFactory.java
platform/diff-api/src/com/intellij/diff/InvalidDiffRequestException.java [new file with mode: 0644]
platform/diff-api/src/com/intellij/diff/merge/MergeContext.java [new file with mode: 0644]
platform/diff-api/src/com/intellij/diff/merge/MergeRequest.java [new file with mode: 0644]
platform/diff-api/src/com/intellij/diff/merge/MergeResult.java [new file with mode: 0644]
platform/diff-api/src/com/intellij/diff/merge/MergeTool.java [new file with mode: 0644]
platform/diff-api/src/com/intellij/diff/merge/TextMergeRequest.java [new file with mode: 0644]
platform/diff-api/src/com/intellij/diff/merge/ThreesideMergeRequest.java [new file with mode: 0644]
platform/diff-api/src/com/intellij/diff/util/Side.java
platform/diff-api/src/com/intellij/diff/util/ThreeSide.java
platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java
platform/diff-impl/src/com/intellij/diff/DiffManagerImpl.java
platform/diff-impl/src/com/intellij/diff/DiffRequestFactoryImpl.java
platform/diff-impl/src/com/intellij/diff/actions/impl/SetEditorSettingsAction.java
platform/diff-impl/src/com/intellij/diff/applications/ApplicationStarterBase.java
platform/diff-impl/src/com/intellij/diff/applications/DiffApplication.java
platform/diff-impl/src/com/intellij/diff/applications/MergeApplication.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/impl/DiffRequestProcessor.java
platform/diff-impl/src/com/intellij/diff/merge/BinaryMergeTool.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/merge/ErrorMergeTool.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/merge/MergeRequestProcessor.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/merge/MergeUtil.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/merge/MergeWindow.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/merge/TextMergeChange.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/merge/TextMergeTool.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/requests/BinaryMergeRequestImpl.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/requests/TextMergeRequestImpl.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/settings/DiffSettingsPaneExternall.form
platform/diff-impl/src/com/intellij/diff/settings/ExternalDiffSettingsPanel.java
platform/diff-impl/src/com/intellij/diff/tools/external/ExternalDiffSettings.java
platform/diff-impl/src/com/intellij/diff/tools/external/ExternalDiffToolUtil.java
platform/diff-impl/src/com/intellij/diff/tools/external/ExternalMergeTool.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/tools/fragmented/UnifiedDiffChange.java
platform/diff-impl/src/com/intellij/diff/tools/fragmented/UnifiedDiffViewer.java
platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java
platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleThreesideDiffChange.java
platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleThreesideDiffViewer.java
platform/diff-impl/src/com/intellij/diff/tools/simple/ThreesideDiffChangeBase.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/tools/simple/ThreesideTextDiffViewerEx.java [new file with mode: 0644]
platform/diff-impl/src/com/intellij/diff/tools/util/PrevNextDifferenceIterableBase.java
platform/diff-impl/src/com/intellij/diff/tools/util/SyncScrollSupport.java
platform/diff-impl/src/com/intellij/diff/tools/util/base/DiffPanelBase.java
platform/diff-impl/src/com/intellij/diff/tools/util/base/DiffViewerBase.java
platform/diff-impl/src/com/intellij/diff/tools/util/side/ThreesideTextDiffViewer.java
platform/diff-impl/src/com/intellij/diff/tools/util/side/TwosideTextDiffViewer.java
platform/diff-impl/src/com/intellij/diff/util/DiffDividerDrawUtil.java
platform/diff-impl/src/com/intellij/diff/util/DiffDrawUtil.java
platform/diff-impl/src/com/intellij/diff/util/DiffLineMarkerRenderer.java
platform/diff-impl/src/com/intellij/diff/util/DiffPlaces.java
platform/diff-impl/src/com/intellij/diff/util/DiffUtil.java
platform/icons/src/diff/applyNotConflictsLeft.png [new file with mode: 0644]
platform/icons/src/diff/applyNotConflictsLeft@2x.png [new file with mode: 0644]
platform/icons/src/diff/applyNotConflictsLeft@2x_dark.png [new file with mode: 0644]
platform/icons/src/diff/applyNotConflictsLeft_dark.png [new file with mode: 0644]
platform/icons/src/diff/applyNotConflictsRight.png [new file with mode: 0644]
platform/icons/src/diff/applyNotConflictsRight@2x.png [new file with mode: 0644]
platform/icons/src/diff/applyNotConflictsRight@2x_dark.png [new file with mode: 0644]
platform/icons/src/diff/applyNotConflictsRight_dark.png [new file with mode: 0644]
platform/lang-impl/src/com/intellij/application/options/colors/ColorAndFontOptions.java
platform/lang-impl/src/com/intellij/openapi/diff/impl/settings/DiffPreviewPanel.java
platform/platform-impl/src/com/intellij/openapi/diff/MergeApplication.java
platform/platform-impl/src/com/intellij/openapi/diff/actions/MergeFilesAction.java
platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorGutterComponentEx.java
platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java
platform/platform-resources-en/src/messages/ActionsBundle.properties
platform/platform-resources-en/src/messages/DiffBundle.properties
platform/platform-resources/src/META-INF/PlatformExtensionPoints.xml
platform/platform-resources/src/META-INF/PlatformExtensions.xml
platform/platform-resources/src/idea/PlatformActions.xml
platform/util/src/com/intellij/icons/AllIcons.java
platform/vcs-impl/src/com/intellij/openapi/vcs/changes/actions/diff/ChangeDiffRequestProducer.java
platform/vcs-impl/src/com/intellij/openapi/vcs/merge/MultipleFileMergeDialog.java

index 98e5745c2b8d5f8e057feeecd44f3d912ca2a5bb..bd41446c1c1f6ecab5123a7e1839926a21e20b37 100644 (file)
@@ -18,6 +18,7 @@ package com.intellij.diff;
 import com.intellij.diff.contents.DiffContent;
 import com.intellij.diff.contents.DocumentContent;
 import com.intellij.diff.contents.EmptyContent;
+import com.intellij.diff.contents.FileContent;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.fileTypes.FileType;
@@ -64,6 +65,9 @@ public abstract class DiffContentFactory {
   @Nullable
   public abstract DocumentContent createDocument(@Nullable Project project, @NotNull VirtualFile file);
 
+  @Nullable
+  public abstract FileContent createFile(@Nullable Project project, @NotNull VirtualFile file);
+
   @NotNull
   public abstract DiffContent createClipboardContent();
 
index d12e4e73f12cfb9f2a6ec3f35b5fd21b2e9f1706..ec4dc7eb98caa2adee3c913d03c4407992157feb 100644 (file)
@@ -16,6 +16,7 @@
 package com.intellij.diff;
 
 import com.intellij.diff.chains.DiffRequestChain;
+import com.intellij.diff.merge.MergeRequest;
 import com.intellij.diff.requests.DiffRequest;
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.components.ServiceManager;
@@ -47,4 +48,7 @@ public abstract class DiffManager {
 
   @NotNull
   public abstract DiffRequestPanel createRequestPanel(@Nullable Project project, @NotNull Disposable parent, @Nullable Window window);
+
+  @CalledInAwt
+  public abstract void showMerge(@Nullable Project project, @NotNull MergeRequest request);
 }
index 348825389683cd58ead2e616aa8f035935c0d7ee..cef525a236aa09d50d16254adcb5b4f8212cb640 100644 (file)
@@ -16,6 +16,8 @@
 package com.intellij.diff;
 
 import com.intellij.diff.chains.DiffRequestChain;
+import com.intellij.diff.merge.MergeRequest;
+import com.intellij.diff.merge.MergeTool;
 import com.intellij.diff.requests.DiffRequest;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.project.Project;
@@ -45,6 +47,12 @@ public abstract class DiffManagerEx extends DiffManager {
   @CalledInAwt
   public abstract void showDiffBuiltin(@Nullable Project project, @NotNull DiffRequestChain requests, @NotNull DiffDialogHints hints);
 
+  @CalledInAwt
+  public abstract void showMergeBuiltin(@Nullable Project project, @NotNull MergeRequest request);
+
   @NotNull
   public abstract List<DiffTool> getDiffTools();
+
+  @NotNull
+  public abstract List<MergeTool> getMergeTools();
 }
index c47a0659e435162e83bddc572a0e3cdd382aa02a..1f1f3db93fe90a00aba52d69a58d7aa8d6c86d08 100644 (file)
  */
 package com.intellij.diff;
 
+import com.intellij.diff.merge.MergeRequest;
+import com.intellij.diff.merge.MergeResult;
+import com.intellij.diff.merge.TextMergeRequest;
 import com.intellij.diff.requests.ContentDiffRequest;
 import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileTypes.FileType;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Consumer;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.List;
+
 /*
  * Use ProgressManager.executeProcessUnderProgress() to pass modality state if needed
  */
@@ -31,12 +39,19 @@ public abstract class DiffRequestFactory {
     return ServiceManager.getService(DiffRequestFactory.class);
   }
 
+  //
+  // Diff
+  //
+
   @NotNull
   public abstract ContentDiffRequest createFromFiles(@Nullable Project project, @NotNull VirtualFile file1, @NotNull VirtualFile file2);
 
   @NotNull
   public abstract ContentDiffRequest createClipboardVsValue(@NotNull String value);
 
+  //
+  // Titles
+  //
 
   @NotNull
   public abstract String getContentTitle(@NotNull VirtualFile file);
@@ -46,4 +61,63 @@ public abstract class DiffRequestFactory {
 
   @NotNull
   public abstract String getTitle(@NotNull VirtualFile file);
+
+  //
+  // Merge
+  //
+
+  @NotNull
+  public abstract MergeRequest createMergeRequest(@Nullable Project project,
+                                                  @Nullable FileType fileType,
+                                                  @NotNull Document output,
+                                                  @NotNull List<String> textContents,
+                                                  @Nullable String title,
+                                                  @NotNull List<String> titles,
+                                                  @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
+
+  @NotNull
+  public abstract MergeRequest createMergeRequest(@Nullable Project project,
+                                                  @NotNull VirtualFile output,
+                                                  @NotNull List<byte[]> byteContents,
+                                                  @Nullable String title,
+                                                  @NotNull List<String> contentTitles,
+                                                  @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
+
+  @NotNull
+  public abstract TextMergeRequest createTextMergeRequest(@Nullable Project project,
+                                                          @NotNull VirtualFile output,
+                                                          @NotNull List<byte[]> byteContents,
+                                                          @Nullable String title,
+                                                          @NotNull List<String> contentTitles,
+                                                          @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
+
+  @NotNull
+  public abstract MergeRequest createBinaryMergeRequest(@Nullable Project project,
+                                                        @NotNull VirtualFile output,
+                                                        @NotNull List<byte[]> byteContents,
+                                                        @Nullable String title,
+                                                        @NotNull List<String> contentTitles,
+                                                        @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
+
+  @NotNull
+  public abstract MergeRequest createMergeRequestFromFiles(@Nullable Project project,
+                                                           @NotNull VirtualFile output,
+                                                           @NotNull List<VirtualFile> contents,
+                                                           @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
+
+  @NotNull
+  public abstract MergeRequest createMergeRequestFromFiles(@Nullable Project project,
+                                                           @NotNull VirtualFile output,
+                                                           @NotNull List<VirtualFile> contents,
+                                                           @Nullable String title,
+                                                           @NotNull List<String> contentTitles,
+                                                           @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
+
+  @NotNull
+  public abstract TextMergeRequest createTextMergeRequestFromFiles(@Nullable Project project,
+                                                                   @NotNull VirtualFile output,
+                                                                   @NotNull List<VirtualFile> contents,
+                                                                   @Nullable String title,
+                                                                   @NotNull List<String> contentTitles,
+                                                                   @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException;
 }
diff --git a/platform/diff-api/src/com/intellij/diff/InvalidDiffRequestException.java b/platform/diff-api/src/com/intellij/diff/InvalidDiffRequestException.java
new file mode 100644 (file)
index 0000000..6cb312c
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2000-2015 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.diff;
+
+public class InvalidDiffRequestException extends Exception {
+  public InvalidDiffRequestException(String message) {
+    super(message);
+  }
+
+  public InvalidDiffRequestException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+  public InvalidDiffRequestException(Throwable cause) {
+    super(cause);
+  }
+}
diff --git a/platform/diff-api/src/com/intellij/diff/merge/MergeContext.java b/platform/diff-api/src/com/intellij/diff/merge/MergeContext.java
new file mode 100644 (file)
index 0000000..6727d7e
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.UserDataHolder;
+import com.intellij.openapi.util.UserDataHolderBase;
+import org.jetbrains.annotations.CalledInAwt;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class MergeContext implements UserDataHolder {
+  protected final UserDataHolderBase myUserDataHolder = new UserDataHolderBase();
+
+  @Nullable
+  public abstract Project getProject();
+
+  public abstract boolean isFocused();
+
+  public abstract void requestFocus();
+
+  @CalledInAwt
+  public abstract void finishMerge(@NotNull MergeResult result);
+
+  @Nullable
+  @Override
+  public <T> T getUserData(@NotNull Key<T> key) {
+    return myUserDataHolder.getUserData(key);
+  }
+
+  @Override
+  public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
+    myUserDataHolder.putUserData(key, value);
+  }
+}
diff --git a/platform/diff-api/src/com/intellij/diff/merge/MergeRequest.java b/platform/diff-api/src/com/intellij/diff/merge/MergeRequest.java
new file mode 100644 (file)
index 0000000..3eb03a8
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.UserDataHolder;
+import com.intellij.openapi.util.UserDataHolderBase;
+import org.jetbrains.annotations.CalledInAwt;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public abstract class MergeRequest implements UserDataHolder {
+  protected final UserDataHolderBase myUserDataHolder = new UserDataHolderBase();
+
+  @Nullable
+  public abstract String getTitle();
+
+  /*
+   * Called on conflict resolve end.
+   */
+  @CalledInAwt
+  public abstract void applyResult(@NotNull MergeResult result);
+
+  @Nullable
+  @Override
+  public <T> T getUserData(@NotNull Key<T> key) {
+    return myUserDataHolder.getUserData(key);
+  }
+
+  @Override
+  public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
+    myUserDataHolder.putUserData(key, value);
+  }
+}
diff --git a/platform/diff-api/src/com/intellij/diff/merge/MergeResult.java b/platform/diff-api/src/com/intellij/diff/merge/MergeResult.java
new file mode 100644 (file)
index 0000000..26f0340
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+public enum MergeResult {CANCEL, LEFT, RIGHT, RESOLVED}
diff --git a/platform/diff-api/src/com/intellij/diff/merge/MergeTool.java b/platform/diff-api/src/com/intellij/diff/merge/MergeTool.java
new file mode 100644 (file)
index 0000000..86bc574
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.util.BooleanGetter;
+import org.jetbrains.annotations.CalledInAwt;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.List;
+
+public interface MergeTool {
+  ExtensionPointName<MergeTool> EP_NAME = ExtensionPointName.create("com.intellij.diff.merge.MergeTool");
+
+  @CalledInAwt
+  @NotNull
+  MergeViewer createComponent(@NotNull MergeContext context, @NotNull MergeRequest request);
+
+  boolean canShow(@NotNull MergeContext context, @NotNull MergeRequest request);
+
+  /*
+   * Merge viewer should call MergeContext.finishMerge(MergeResult) when processing is over.
+   *
+   * MergeRequest.applyResult() will be performed by the caller, so it shouldn't be called by MergeViewer directly.
+   */
+  interface MergeViewer extends Disposable {
+    @NotNull
+    JComponent getComponent();
+
+    @Nullable
+    JComponent getPreferredFocusedComponent();
+
+    @Nullable
+    Action getResolveAction(@NotNull MergeResult result);
+
+    @CalledInAwt
+    ToolbarComponents init();
+
+    @Override
+    @CalledInAwt
+    void dispose();
+  }
+
+  class ToolbarComponents {
+    @Nullable public List<AnAction> toolbarActions;
+    @Nullable public JComponent statusPanel;
+    @Nullable public BooleanGetter closeHandler; // return false if merge window should be prevented from closing and canceling resolve.
+  }
+}
diff --git a/platform/diff-api/src/com/intellij/diff/merge/TextMergeRequest.java b/platform/diff-api/src/com/intellij/diff/merge/TextMergeRequest.java
new file mode 100644 (file)
index 0000000..b449baa
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.diff.contents.DocumentContent;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public abstract class TextMergeRequest extends ThreesideMergeRequest {
+  @Override
+  @NotNull
+  public abstract List<DocumentContent> getContents();
+
+  @Override
+  @NotNull
+  public abstract DocumentContent getOutputContent();
+}
diff --git a/platform/diff-api/src/com/intellij/diff/merge/ThreesideMergeRequest.java b/platform/diff-api/src/com/intellij/diff/merge/ThreesideMergeRequest.java
new file mode 100644 (file)
index 0000000..982402b
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.diff.contents.DiffContent;
+import org.jetbrains.annotations.CalledInAwt;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public abstract class ThreesideMergeRequest extends MergeRequest {
+  /*
+   * 3 contents: left - middle - right (local - base - server)
+   */
+  @NotNull
+  public abstract List<? extends DiffContent> getContents();
+
+  @NotNull
+  public abstract DiffContent getOutputContent();
+
+  /**
+   * @return contents names. Should have same length as {@link #getContents()}
+   * Titles could be null.
+   */
+  @NotNull
+  public abstract List<String> getContentTitles();
+}
index 043b4650d24d38532a0391729e9a746c51cb3dfc..c783e893bc71fac5980047d7c7dd341bef3d4218 100644 (file)
@@ -34,6 +34,13 @@ public enum Side {
     myIndex = index;
   }
 
+  @NotNull
+  public static Side fromIndex(int index) {
+    if (index == 0) return LEFT;
+    if (index == 1) return RIGHT;
+    throw new IndexOutOfBoundsException("index: " + index);
+  }
+
   @NotNull
   public static Side fromLeft(boolean isLeft) {
     return isLeft ? LEFT : RIGHT;
index c1041273c86c0312f5e50945da68d9189f44873f..3042b3dc14f722fe9fc3765e141a14fca34accd9 100644 (file)
@@ -32,6 +32,14 @@ public enum ThreeSide {
     myIndex = index;
   }
 
+  @NotNull
+  public static ThreeSide fromIndex(int index) {
+    if (index == 0) return LEFT;
+    if (index == 1) return BASE;
+    if (index == 2) return RIGHT;
+    throw new IndexOutOfBoundsException("index: " + index);
+  }
+
   public int getIndex() {
     return myIndex;
   }
index af73b7ecf550daf91b7f9c1be13a20f6b7fb3d41..653017dcf5689ad0121b8a988b47bf109e6a1588 100644 (file)
@@ -17,7 +17,6 @@ package com.intellij.diff;
 
 import com.intellij.diff.contents.*;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.EditorFactory;
@@ -119,6 +118,13 @@ public class DiffContentFactoryImpl extends DiffContentFactory {
     return new FileDocumentContentImpl(project, document, file);
   }
 
+  @Override
+  @Nullable
+  public FileContent createFile(@Nullable Project project, @NotNull VirtualFile file) {
+    if (file.isDirectory()) return null;
+    return (FileContent)create(project, file);
+  }
+
   @Override
   @NotNull
   public DiffContent createClipboardContent() {
index 69345c86df04648e8b3ba1dc125acacaa5be7a50..26d799f7c45bf4aa2466e145590ebefe0bf1998f 100644 (file)
@@ -19,15 +19,18 @@ import com.intellij.diff.chains.DiffRequestChain;
 import com.intellij.diff.chains.SimpleDiffRequestChain;
 import com.intellij.diff.impl.DiffRequestPanelImpl;
 import com.intellij.diff.impl.DiffWindow;
+import com.intellij.diff.merge.*;
 import com.intellij.diff.requests.DiffRequest;
 import com.intellij.diff.tools.binary.BinaryDiffTool;
 import com.intellij.diff.tools.dir.DirDiffTool;
 import com.intellij.diff.tools.external.ExternalDiffTool;
+import com.intellij.diff.tools.external.ExternalMergeTool;
 import com.intellij.diff.tools.fragmented.UnifiedDiffTool;
 import com.intellij.diff.tools.simple.SimpleDiffTool;
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Disposer;
+import org.jetbrains.annotations.CalledInAwt;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -93,4 +96,29 @@ public class DiffManagerImpl extends DiffManagerEx {
     Collections.addAll(result, DiffTool.EP_NAME.getExtensions());
     return result;
   }
+
+  @NotNull
+  @Override
+  public List<MergeTool> getMergeTools() {
+    List<MergeTool> result = new ArrayList<MergeTool>();
+    Collections.addAll(result, MergeTool.EP_NAME.getExtensions());
+    result.add(TextMergeTool.INSTANCE);
+    result.add(BinaryMergeTool.INSTANCE);
+    return result;
+  }
+
+  @CalledInAwt
+  public void showMerge(@Nullable Project project, @NotNull MergeRequest request) {
+    if (ExternalMergeTool.isDefault()) {
+      ExternalMergeTool.show(project, request);
+      return;
+    }
+
+    showMergeBuiltin(project, request);
+  }
+
+  @CalledInAwt
+  public void showMergeBuiltin(@Nullable Project project, @NotNull MergeRequest request) {
+    new MergeWindow(project, request).show();
+  }
 }
index 6d5ae7efe08a8d8d691a616f065faea015300fce..dc9ccbbb3a026418d5fed38957fc39c9d2716edf 100644 (file)
 package com.intellij.diff;
 
 import com.intellij.diff.contents.DiffContent;
+import com.intellij.diff.contents.DocumentContent;
+import com.intellij.diff.contents.FileAwareDocumentContent;
+import com.intellij.diff.contents.FileContent;
+import com.intellij.diff.merge.MergeRequest;
+import com.intellij.diff.merge.MergeResult;
+import com.intellij.diff.merge.TextMergeRequest;
+import com.intellij.diff.requests.BinaryMergeRequestImpl;
 import com.intellij.diff.requests.ContentDiffRequest;
 import com.intellij.diff.requests.SimpleDiffRequest;
+import com.intellij.diff.requests.TextMergeRequestImpl;
+import com.intellij.diff.util.DiffUtil;
 import com.intellij.openapi.diff.DiffBundle;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileTypes.FileType;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Comparing;
 import com.intellij.openapi.vcs.FilePath;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Consumer;
+import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
 public class DiffRequestFactoryImpl extends DiffRequestFactory {
-  private DiffContentFactory myContentFactory = DiffContentFactory.getInstance();
+  private final DiffContentFactory myContentFactory = DiffContentFactory.getInstance();
+
+  //
+  // Diff
+  //
 
   @Override
   @NotNull
@@ -57,6 +79,10 @@ public class DiffRequestFactoryImpl extends DiffRequestFactory {
     return new SimpleDiffRequest(title, content1, content2, title1, title2);
   }
 
+  //
+  // Titles
+  //
+
   @Override
   @NotNull
   public String getContentTitle(@NotNull VirtualFile file) {
@@ -147,4 +173,195 @@ public class DiffRequestFactoryImpl extends DiffRequestFactory {
       }
     }
   }
+
+  //
+  // Merge
+  //
+
+  @NotNull
+  public MergeRequest createMergeRequest(@Nullable Project project,
+                                         @Nullable FileType fileType,
+                                         @NotNull Document outputDocument,
+                                         @NotNull List<String> textContents,
+                                         @Nullable String title,
+                                         @NotNull List<String> titles,
+                                         @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
+    if (textContents.size() != 3) throw new IllegalArgumentException();
+    if (titles.size() != 3) throw new IllegalArgumentException();
+
+    if (!DiffUtil.canMakeWritable(outputDocument)) throw new InvalidDiffRequestException("Output is read only: " + outputDocument);
+
+    DocumentContent outputContent = myContentFactory.create(project, outputDocument, fileType);
+    CharSequence originalContent = outputDocument.getImmutableCharSequence();
+
+    List<DocumentContent> contents = new ArrayList<DocumentContent>(3);
+    for (String text : textContents) {
+      contents.add(myContentFactory.create(text, fileType));
+    }
+
+    return new TextMergeRequestImpl(project, outputContent, originalContent, contents, title, titles, applyCallback);
+  }
+
+  @NotNull
+  @Override
+  public MergeRequest createMergeRequest(@Nullable Project project,
+                                         @NotNull VirtualFile output,
+                                         @NotNull List<byte[]> byteContents,
+                                         @Nullable String title,
+                                         @NotNull List<String> contentTitles,
+                                         @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
+    if (byteContents.size() != 3) throw new IllegalArgumentException();
+    if (contentTitles.size() != 3) throw new IllegalArgumentException();
+
+    try {
+      return createTextMergeRequest(project, output, byteContents, title, contentTitles, applyCallback);
+    }
+    catch (InvalidDiffRequestException e) {
+      return createBinaryMergeRequest(project, output, byteContents, title, contentTitles, applyCallback);
+    }
+  }
+
+  @NotNull
+  @Override
+  public TextMergeRequest createTextMergeRequest(@Nullable Project project,
+                                                 @NotNull VirtualFile output,
+                                                 @NotNull List<byte[]> byteContents,
+                                                 @Nullable String title,
+                                                 @NotNull List<String> contentTitles,
+                                                 @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
+    if (byteContents.size() != 3) throw new IllegalArgumentException();
+    if (contentTitles.size() != 3) throw new IllegalArgumentException();
+
+    final Document outputDocument = FileDocumentManager.getInstance().getDocument(output);
+    if (outputDocument == null) throw new InvalidDiffRequestException("Can't get output document: " + output);
+    if (!DiffUtil.canMakeWritable(outputDocument)) throw new InvalidDiffRequestException("Output is read only: " + output);
+
+    DocumentContent outputContent = myContentFactory.create(project, outputDocument);
+    CharSequence originalContent = outputDocument.getImmutableCharSequence();
+
+    List<DocumentContent> contents = new ArrayList<DocumentContent>(3);
+    for (byte[] bytes : byteContents) {
+      contents.add(FileAwareDocumentContent.create(project, bytes, output));
+    }
+
+    return new TextMergeRequestImpl(project, outputContent, originalContent, contents, title, contentTitles, applyCallback);
+  }
+
+  @NotNull
+  @Override
+  public MergeRequest createBinaryMergeRequest(@Nullable Project project,
+                                               @NotNull VirtualFile output,
+                                               @NotNull List<byte[]> byteContents,
+                                               @Nullable String title,
+                                               @NotNull List<String> contentTitles,
+                                               @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
+    if (byteContents.size() != 3) throw new IllegalArgumentException();
+    if (contentTitles.size() != 3) throw new IllegalArgumentException();
+
+    try {
+      FileContent outputContent = myContentFactory.createFile(project, output);
+      if (outputContent == null) throw new InvalidDiffRequestException("Can't create output content: " + output);
+      byte[] originalContent = output.contentsToByteArray();
+
+      List<DiffContent> contents = new ArrayList<DiffContent>(3);
+      for (byte[] bytes : byteContents) {
+        contents.add(myContentFactory.createFromBytes(project, output, bytes));
+      }
+
+      return new BinaryMergeRequestImpl(outputContent, originalContent, contents, byteContents, title, contentTitles, applyCallback);
+    }
+    catch (IOException e) {
+      throw new InvalidDiffRequestException("Can't read from file", e);
+    }
+  }
+
+  @NotNull
+  @Override
+  public MergeRequest createMergeRequestFromFiles(@Nullable Project project,
+                                                  @NotNull VirtualFile output,
+                                                  @NotNull List<VirtualFile> fileContents,
+                                                  @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
+    String title = "Merge " + output.getPresentableUrl();
+    List<String> titles = ContainerUtil.list("Your Version", "Base Version", "Their Version");
+    return createMergeRequestFromFiles(project, output, fileContents, title, titles, applyCallback);
+  }
+
+  @NotNull
+  @Override
+  public MergeRequest createMergeRequestFromFiles(@Nullable Project project,
+                                                  @NotNull VirtualFile output,
+                                                  @NotNull List<VirtualFile> fileContents,
+                                                  @Nullable String title,
+                                                  @NotNull List<String> contentTitles,
+                                                  @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
+    if (fileContents.size() != 3) throw new IllegalArgumentException();
+    if (contentTitles.size() != 3) throw new IllegalArgumentException();
+
+    try {
+      return createTextMergeRequestFromFiles(project, output, fileContents, title, contentTitles, applyCallback);
+    }
+    catch (InvalidDiffRequestException e) {
+      return createBinaryMergeRequestFromFiles(project, output, fileContents, title, contentTitles, applyCallback);
+    }
+  }
+
+  @NotNull
+  @Override
+  public TextMergeRequest createTextMergeRequestFromFiles(@Nullable Project project,
+                                                          @NotNull VirtualFile output,
+                                                          @NotNull List<VirtualFile> fileContents,
+                                                          @Nullable String title,
+                                                          @NotNull List<String> contentTitles,
+                                                          @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
+    if (fileContents.size() != 3) throw new IllegalArgumentException();
+    if (contentTitles.size() != 3) throw new IllegalArgumentException();
+
+    final Document outputDocument = FileDocumentManager.getInstance().getDocument(output);
+    if (outputDocument == null) throw new InvalidDiffRequestException("Can't get output document: " + output);
+    if (!DiffUtil.canMakeWritable(outputDocument)) throw new InvalidDiffRequestException("Output is read only: " + output);
+
+    DocumentContent outputContent = myContentFactory.create(project, outputDocument);
+    CharSequence originalContent = outputDocument.getImmutableCharSequence();
+
+    List<DocumentContent> contents = new ArrayList<DocumentContent>(3);
+    for (VirtualFile file : fileContents) {
+      DocumentContent document = myContentFactory.createDocument(project, file);
+      if (document == null) throw new InvalidDiffRequestException("Can't get text content: " + file);
+      contents.add(document);
+    }
+
+    return new TextMergeRequestImpl(project, outputContent, originalContent, contents, title, contentTitles, applyCallback);
+  }
+
+  @NotNull
+  public MergeRequest createBinaryMergeRequestFromFiles(@Nullable Project project,
+                                                        @NotNull VirtualFile output,
+                                                        @NotNull List<VirtualFile> fileContents,
+                                                        @Nullable String title,
+                                                        @NotNull List<String> contentTitles,
+                                                        @Nullable Consumer<MergeResult> applyCallback) throws InvalidDiffRequestException {
+    if (fileContents.size() != 3) throw new IllegalArgumentException();
+    if (contentTitles.size() != 3) throw new IllegalArgumentException();
+
+
+    try {
+      FileContent outputContent = myContentFactory.createFile(project, output);
+      if (outputContent == null) throw new InvalidDiffRequestException("Can't create output content: " + output);
+      byte[] originalContent = output.contentsToByteArray();
+
+      List<DiffContent> contents = new ArrayList<DiffContent>(3);
+      List<byte[]> byteContents = new ArrayList<byte[]>(3);
+      for (VirtualFile file : fileContents) {
+        FileContent content = myContentFactory.createFile(project, file);
+        if (content == null) throw new InvalidDiffRequestException("Can't create content: " + file);
+        contents.add(content);
+        byteContents.add(file.contentsToByteArray()); // TODO: we can read contents from file when needed
+      }
+
+      return new BinaryMergeRequestImpl(outputContent, originalContent, contents, byteContents, title, contentTitles, applyCallback);
+    }
+    catch (IOException e) {
+      throw new InvalidDiffRequestException("Can't read from file", e);
+    }
+  }
 }
index c525b86fb74c0c79e9b72ed76bab83b7b6481cf4..3f7ae6cf7339ea9a82f662e6d50dfdcb6d93a300 100644 (file)
@@ -19,6 +19,7 @@ import com.intellij.diff.tools.util.base.TextDiffSettingsHolder;
 import com.intellij.icons.AllIcons;
 import com.intellij.openapi.actionSystem.*;
 import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
 import com.intellij.openapi.editor.impl.EditorImpl;
 import com.intellij.openapi.project.DumbAware;
 import org.jetbrains.annotations.NotNull;
@@ -39,6 +40,10 @@ public class SetEditorSettingsAction extends ActionGroup implements DumbAware {
     myTextSettings = settings;
     myEditors = editors;
 
+    for (Editor editor : myEditors) {
+      ((EditorGutterComponentEx)editor.getGutter()).setGutterPopupGroup(this);
+    }
+
     myActions = new EditorSettingToggleAction[]{
       new EditorSettingToggleAction("EditorToggleShowWhitespaces") {
         @Override
index 0e60ab08a3c863d606575ec0ded200c76545e683..28a3d6892a81960f5b70942de91a7928889c2a14 100644 (file)
@@ -19,6 +19,7 @@ import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.ApplicationStarterEx;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.LocalFileSystem;
@@ -125,4 +126,9 @@ public abstract class ApplicationStarterBase extends ApplicationStarterEx {
   public boolean canProcessExternalCommandLine() {
     return true;
   }
+
+  @Nullable
+  protected Project getProject() {
+    return null; // TODO: try to guess project
+  }
 }
index 9e2f84f0b1caafaad9268bed7ac59ec2b3a541f1..6f78e5ad34d1edd957a1fee0ced85ccc7541f1f3 100644 (file)
@@ -21,7 +21,6 @@ import com.intellij.diff.DiffRequestFactory;
 import com.intellij.diff.requests.DiffRequest;
 import com.intellij.openapi.application.ApplicationNamesInfo;
 import com.intellij.openapi.diff.DiffBundle;
-import com.intellij.openapi.project.DefaultProjectFactory;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
@@ -47,8 +46,6 @@ public class DiffApplication extends ApplicationStarterBase {
   }
 
   public void processCommand(@NotNull String[] args, @Nullable String currentDirectory) throws Exception {
-    // TODO: try to guess 'right' project ?
-
     final String path1 = args[1];
     final String path2 = args[2];
 
@@ -59,9 +56,10 @@ public class DiffApplication extends ApplicationStarterBase {
     if (file2 == null) throw new Exception("Can't find file " + path2);
 
     VfsUtil.markDirtyAndRefresh(false, false, false, file1, file2);
-    DiffRequest request = DiffRequestFactory.getInstance().createFromFiles(null, file1, file2);
 
-    Project project = DefaultProjectFactory.getInstance().getDefaultProject();
+    Project project = getProject();
+
+    DiffRequest request = DiffRequestFactory.getInstance().createFromFiles(project, file1, file2);
     DiffManagerEx.getInstance().showDiffBuiltin(project, request, DiffDialogHints.MODAL);
   }
 }
diff --git a/platform/diff-impl/src/com/intellij/diff/applications/MergeApplication.java b/platform/diff-impl/src/com/intellij/diff/applications/MergeApplication.java
new file mode 100644 (file)
index 0000000..19cb225
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2000-2015 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.diff.applications;
+
+import com.intellij.diff.DiffManagerEx;
+import com.intellij.diff.DiffRequestFactory;
+import com.intellij.diff.merge.MergeRequest;
+import com.intellij.openapi.application.ApplicationNamesInfo;
+import com.intellij.openapi.diff.DiffBundle;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+@SuppressWarnings({"UseOfSystemOutOrSystemErr", "CallToPrintStackTrace"})
+public class MergeApplication extends ApplicationStarterBase {
+  @Override
+  protected boolean checkArguments(@NotNull String[] args) {
+    return (args.length == 4 || args.length == 5) && "merge".equals(args[0]);
+  }
+
+  @Override
+  public String getCommandName() {
+    return "merge";
+  }
+
+  @NotNull
+  public String getUsageMessage() {
+    final String scriptName = ApplicationNamesInfo.getInstance().getScriptName();
+    return DiffBundle.message("merge.application.usage.parameters.and.description", scriptName);
+  }
+
+  public void processCommand(@NotNull String[] args, @Nullable String currentDirectory) throws Exception {
+    // TODO: try to guess 'right' project ?
+
+    final String path1 = args[1];
+    final String path2 = args[2];
+    final String path3 = args[3];
+    final String path4 = args.length == 5 ? args[4] : args[3];
+
+    final VirtualFile file1 = findFile(path1, currentDirectory);
+    final VirtualFile file2 = findFile(path2, currentDirectory);
+    final VirtualFile file3 = findFile(path3, currentDirectory);
+    final VirtualFile file4 = findFile(path4, currentDirectory);
+
+    if (file1 == null) throw new Exception("Can't find file " + path1);
+    if (file2 == null) throw new Exception("Can't find file " + path2);
+    if (file3 == null) throw new Exception("Can't find file " + path3);
+    if (file4 == null) throw new Exception("Can't find file " + path4);
+
+    file1.refresh(false, true);
+    file2.refresh(false, true);
+    file3.refresh(false, true);
+    file4.refresh(false, true);
+
+    Project project = getProject();
+
+    List<VirtualFile> contents = ContainerUtil.list(file1, file3, file2); // left, base, right
+    MergeRequest request = DiffRequestFactory.getInstance().createMergeRequestFromFiles(project, file4, contents, null);
+
+    DiffManagerEx.getInstance().showMergeBuiltin(project, request);
+
+    Document document = FileDocumentManager.getInstance().getCachedDocument(file4);
+    if (document != null) FileDocumentManager.getInstance().saveDocument(document);
+  }
+}
index 43f42554da947a3dfe9ff1f399e3ff30a522f74f..6f657b49d5b218961de22355e26aca7d94178589 100644 (file)
@@ -445,13 +445,13 @@ public abstract class DiffRequestProcessor implements Disposable {
 
     myToolbarPanel.setContent(toolbar.getComponent());
     for (AnAction action : group.getChildren(null)) {
-      action.registerCustomShortcutSet(action.getShortcutSet(), myMainPanel);
+      DiffUtil.registerAction(action, myMainPanel);
     }
   }
 
   protected void buildActionPopup(@Nullable List<AnAction> viewerActions) {
     ShowActionGroupPopupAction action = new ShowActionGroupPopupAction();
-    action.registerCustomShortcutSet(action.getShortcutSet(), myMainPanel);
+    DiffUtil.registerAction(action, myMainPanel);
 
     myPopupActionGroup = collectPopupActions(viewerActions);
   }
diff --git a/platform/diff-impl/src/com/intellij/diff/merge/BinaryMergeTool.java b/platform/diff-impl/src/com/intellij/diff/merge/BinaryMergeTool.java
new file mode 100644 (file)
index 0000000..76df64d
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.diff.DiffContext;
+import com.intellij.diff.FrameDiffTool;
+import com.intellij.diff.contents.DiffContent;
+import com.intellij.diff.requests.ContentDiffRequest;
+import com.intellij.diff.requests.DiffRequest;
+import com.intellij.diff.requests.SimpleDiffRequest;
+import com.intellij.diff.tools.binary.ThreesideBinaryDiffViewer;
+import com.intellij.diff.tools.holders.BinaryEditorHolder;
+import com.intellij.openapi.diff.DiffBundle;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.BooleanGetter;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.CalledInAwt;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.util.List;
+
+public class BinaryMergeTool implements MergeTool {
+  public static final BinaryMergeTool INSTANCE = new BinaryMergeTool();
+
+  @NotNull
+  @Override
+  public MergeViewer createComponent(@NotNull MergeContext context, @NotNull MergeRequest request) {
+    return new BinaryMergeViewer(context, (ThreesideMergeRequest)request);
+  }
+
+  @Override
+  public boolean canShow(@NotNull MergeContext context, @NotNull MergeRequest request) {
+    if (!(request instanceof ThreesideMergeRequest)) return false;
+
+    MergeUtil.ProxyDiffContext diffContext = new MergeUtil.ProxyDiffContext(context);
+    for (DiffContent diffContent : ((ThreesideMergeRequest)request).getContents()) {
+      if (!BinaryEditorHolder.BinaryEditorHolderFactory.INSTANCE.canShowContent(diffContent, diffContext)) return false;
+    }
+
+    return true;
+  }
+
+  public static class BinaryMergeViewer implements MergeViewer {
+    @NotNull private final MergeContext myMergeContext;
+    @NotNull private final ThreesideMergeRequest myMergeRequest;
+
+    @NotNull private final DiffContext myDiffContext;
+    @NotNull private final ContentDiffRequest myDiffRequest;
+
+    @NotNull private final MyThreesideViewer myViewer;
+
+    public BinaryMergeViewer(@NotNull MergeContext context, @NotNull ThreesideMergeRequest request) {
+      myMergeContext = context;
+      myMergeRequest = request;
+
+      myDiffContext = new MergeUtil.ProxyDiffContext(myMergeContext);
+      myDiffRequest = new SimpleDiffRequest(myMergeRequest.getTitle(),
+                                            getDiffContents(myMergeRequest),
+                                            getDiffContentTitles(myMergeRequest));
+
+      myViewer = new MyThreesideViewer(myDiffContext, myDiffRequest);
+    }
+
+    @NotNull
+    private static List<DiffContent> getDiffContents(@NotNull ThreesideMergeRequest mergeRequest) {
+      return ContainerUtil.newArrayList(mergeRequest.getContents());
+    }
+
+    @NotNull
+    private static List<String> getDiffContentTitles(@NotNull ThreesideMergeRequest mergeRequest) {
+      return MergeUtil.notNullizeContentTitles(mergeRequest.getContentTitles());
+    }
+
+    //
+    // Impl
+    //
+
+    @NotNull
+    @Override
+    public JComponent getComponent() {
+      return myViewer.getComponent();
+    }
+
+    @Nullable
+    @Override
+    public JComponent getPreferredFocusedComponent() {
+      return myViewer.getPreferredFocusedComponent();
+    }
+
+    @NotNull
+    @Override
+    public ToolbarComponents init() {
+      ToolbarComponents components = new ToolbarComponents();
+
+      FrameDiffTool.ToolbarComponents init = myViewer.init();
+      components.statusPanel = init.statusPanel;
+      components.toolbarActions = init.toolbarActions;
+
+      components.closeHandler = new BooleanGetter() {
+        @Override
+        public boolean get() {
+          return Messages.showYesNoDialog(getComponent().getRootPane(),
+                                          DiffBundle.message("merge.dialog.exit.without.applying.changes.confirmation.message"),
+                                          DiffBundle.message("cancel.visual.merge.dialog.title"), Messages.getQuestionIcon()) == Messages.YES;
+        }
+      };
+
+      return components;
+    }
+
+    @Nullable
+    @Override
+    public Action getResolveAction(@NotNull final MergeResult result) {
+      if (result == MergeResult.RESOLVED) return null;
+
+      String caption = MergeUtil.getResolveActionTitle(result, myMergeRequest, myMergeContext);
+      return new AbstractAction(caption) {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+          if (result == MergeResult.CANCEL) {
+            if (Messages.showYesNoDialog(getComponent().getRootPane(),
+                                         DiffBundle.message("merge.dialog.exit.without.applying.changes.confirmation.message"),
+                                         DiffBundle.message("cancel.visual.merge.dialog.title"), Messages.getQuestionIcon()) != Messages.YES) {
+              return;
+            }
+          }
+
+          myMergeContext.finishMerge(result);
+        }
+      };
+    }
+
+    @Override
+    public void dispose() {
+      Disposer.dispose(myViewer);
+    }
+
+    //
+    // Getters
+    //
+
+    @NotNull
+    public MyThreesideViewer getViewer() {
+      return myViewer;
+    }
+
+    //
+    // Viewer
+    //
+
+    private static class MyThreesideViewer extends ThreesideBinaryDiffViewer {
+      public MyThreesideViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
+        super(context, request);
+      }
+
+      @Override
+      @CalledInAwt
+      public void rediff(boolean trySync) {
+      }
+    }
+  }
+}
diff --git a/platform/diff-impl/src/com/intellij/diff/merge/ErrorMergeTool.java b/platform/diff-impl/src/com/intellij/diff/merge/ErrorMergeTool.java
new file mode 100644 (file)
index 0000000..5386f5f
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.diff.util.DiffUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+
+public class ErrorMergeTool implements MergeTool {
+  public static final ErrorMergeTool INSTANCE = new ErrorMergeTool();
+
+  @NotNull
+  @Override
+  public MergeViewer createComponent(@NotNull MergeContext context, @NotNull MergeRequest request) {
+    return new MyViewer(context, request);
+  }
+
+  @Override
+  public boolean canShow(@NotNull MergeContext context, @NotNull MergeRequest request) {
+    return true;
+  }
+
+  private static class MyViewer implements MergeViewer {
+    @NotNull private final MergeContext myMergeContext;
+    @NotNull private final MergeRequest myMergeRequest;
+
+    @NotNull private final JPanel myPanel;
+
+    public MyViewer(@NotNull MergeContext context, @NotNull MergeRequest request) {
+      myMergeContext = context;
+      myMergeRequest = request;
+
+      myPanel = new JPanel(new BorderLayout());
+      myPanel.add(createComponent(), BorderLayout.CENTER);
+    }
+
+    @NotNull
+    private JComponent createComponent() {
+      return DiffUtil.createMessagePanel("Can't show diff");
+    }
+
+    @NotNull
+    @Override
+    public JComponent getComponent() {
+      return myPanel;
+    }
+
+    @Nullable
+    @Override
+    public JComponent getPreferredFocusedComponent() {
+      return null;
+    }
+
+    @NotNull
+    @Override
+    public ToolbarComponents init() {
+      return new ToolbarComponents();
+    }
+
+    @Nullable
+    @Override
+    public Action getResolveAction(@NotNull final MergeResult result) {
+      if (result == MergeResult.RESOLVED) return null;
+
+      String caption = MergeUtil.getResolveActionTitle(result, myMergeRequest, myMergeContext);
+      return new AbstractAction(caption) {
+        @Override
+        public void actionPerformed(ActionEvent e) {
+          myMergeContext.finishMerge(result);
+        }
+      };
+    }
+
+    @Override
+    public void dispose() {
+    }
+  }
+}
diff --git a/platform/diff-impl/src/com/intellij/diff/merge/MergeRequestProcessor.java b/platform/diff-impl/src/com/intellij/diff/merge/MergeRequestProcessor.java
new file mode 100644 (file)
index 0000000..0941628
--- /dev/null
@@ -0,0 +1,440 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.diff.DiffManagerEx;
+import com.intellij.diff.actions.impl.NextDifferenceAction;
+import com.intellij.diff.actions.impl.PrevDifferenceAction;
+import com.intellij.diff.tools.util.DiffDataKeys;
+import com.intellij.diff.tools.util.PrevNextDifferenceIterable;
+import com.intellij.diff.util.DiffPlaces;
+import com.intellij.diff.util.DiffUserDataKeys;
+import com.intellij.diff.util.DiffUserDataKeysEx;
+import com.intellij.diff.util.DiffUtil;
+import com.intellij.ide.DataManager;
+import com.intellij.ide.impl.DataManagerImpl;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.BooleanGetter;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
+import com.intellij.ui.components.panels.Wrapper;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.CalledInAwt;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.List;
+
+// TODO: support merge request chains
+// idea - to keep in memory all viewers that were modified (so binary conflict is not the case and OOM shouldn't be too often)
+// suspend() / resume() methods for viewers? To not interfere with MergeRequest lifecycle: single request -> single viewer -> single applyResult()
+public abstract class MergeRequestProcessor implements Disposable {
+  private static final Logger LOG = Logger.getInstance(MergeRequestProcessor.class);
+
+  private boolean myDisposed;
+
+  @Nullable private final Project myProject;
+  @NotNull private final MergeContext myContext;
+
+  @NotNull private final List<MergeTool> myAvailableTools;
+
+  @NotNull private final JPanel myPanel;
+  @NotNull private final MyPanel myMainPanel;
+  @NotNull private final Wrapper myContentPanel;
+  @NotNull private final Wrapper myToolbarPanel;
+  @NotNull private final Wrapper myToolbarStatusPanel;
+
+  @NotNull private final MergeRequest myRequest;
+  @NotNull private final MergeTool.MergeViewer myViewer;
+
+  @Nullable private BooleanGetter myCloseHandler;
+  private boolean myConflictResolved = false;
+
+  public MergeRequestProcessor(@Nullable Project project, @NotNull MergeRequest request) {
+    myProject = project;
+    myRequest = request;
+
+    myContext = new MyDiffContext();
+    myContext.putUserData(DiffUserDataKeysEx.PLACE, DiffPlaces.MERGE);
+
+    myAvailableTools = DiffManagerEx.getInstance().getMergeTools();
+
+    myPanel = new JPanel(new BorderLayout());
+    myMainPanel = new MyPanel();
+    myContentPanel = new Wrapper();
+    myToolbarPanel = new Wrapper();
+    myToolbarPanel.setFocusable(true);
+    myToolbarStatusPanel = new Wrapper();
+
+    myPanel.add(myMainPanel, BorderLayout.CENTER);
+
+    JPanel topPanel = new JPanel(new BorderLayout());
+    topPanel.add(myToolbarPanel, BorderLayout.CENTER);
+    topPanel.add(myToolbarStatusPanel, BorderLayout.EAST);
+
+
+    myMainPanel.add(topPanel, BorderLayout.NORTH);
+    myMainPanel.add(myContentPanel, BorderLayout.CENTER);
+
+    myMainPanel.setFocusTraversalPolicyProvider(true);
+    myMainPanel.setFocusTraversalPolicy(new MyFocusTraversalPolicy());
+
+    MergeTool.MergeViewer viewer;
+    try {
+      MergeTool tool = getFittedTool();
+      viewer = tool.createComponent(myContext, myRequest);
+    }
+    catch (Throwable e) {
+      LOG.error(e);
+      viewer = ErrorMergeTool.INSTANCE.createComponent(myContext, myRequest);
+    }
+    myViewer = viewer;
+  }
+
+  //
+  // Update
+  //
+
+  public void init() {
+    myContentPanel.setContent(myViewer.getComponent());
+    setTitle(myRequest.getTitle());
+
+    MergeTool.ToolbarComponents toolbarComponents = myViewer.init();
+
+    buildToolbar(toolbarComponents.toolbarActions);
+    myToolbarStatusPanel.setContent(toolbarComponents.statusPanel);
+    myCloseHandler = toolbarComponents.closeHandler;
+  }
+
+  @NotNull
+  public BottomActions getBottomActions() {
+    BottomActions actions = new BottomActions();
+    actions.applyLeft = myViewer.getResolveAction(MergeResult.LEFT);
+    actions.applyRight = myViewer.getResolveAction(MergeResult.RIGHT);
+    actions.resolveAction = myViewer.getResolveAction(MergeResult.RESOLVED);
+    actions.cancelAction = myViewer.getResolveAction(MergeResult.CANCEL);
+    return actions;
+  }
+
+  @NotNull
+  protected DefaultActionGroup collectToolbarActions(@Nullable List<AnAction> viewerActions) {
+    DefaultActionGroup group = new DefaultActionGroup();
+
+    List<AnAction> navigationActions = ContainerUtil.<AnAction>list(new MyPrevDifferenceAction(),
+                                                                    new MyNextDifferenceAction());
+    DiffUtil.addActionBlock(group, navigationActions);
+
+    DiffUtil.addActionBlock(group, viewerActions);
+
+    List<AnAction> requestContextActions = myRequest.getUserData(DiffUserDataKeys.CONTEXT_ACTIONS);
+    DiffUtil.addActionBlock(group, requestContextActions);
+
+    List<AnAction> contextActions = myContext.getUserData(DiffUserDataKeys.CONTEXT_ACTIONS);
+    DiffUtil.addActionBlock(group, contextActions);
+
+    return group;
+  }
+
+  protected void buildToolbar(@Nullable List<AnAction> viewerActions) {
+    ActionGroup group = collectToolbarActions(viewerActions);
+    ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.DIFF_TOOLBAR, group, true);
+
+    DataManager.registerDataProvider(toolbar.getComponent(), myMainPanel);
+    toolbar.setTargetComponent(toolbar.getComponent());
+
+    myToolbarPanel.setContent(toolbar.getComponent());
+    for (AnAction action : group.getChildren(null)) {
+      DiffUtil.registerAction(action, myMainPanel);
+    }
+  }
+
+  @NotNull
+  private MergeTool getFittedTool() {
+    for (MergeTool tool : myAvailableTools) {
+      try {
+        if (tool.canShow(myContext, myRequest)) return tool;
+      }
+      catch (Throwable e) {
+        LOG.error(e);
+      }
+    }
+
+    return ErrorMergeTool.INSTANCE;
+  }
+
+  private void setTitle(@Nullable String title) {
+    if (title == null) title = "Merge";
+    setWindowTitle(title);
+  }
+
+  @Override
+  public void dispose() {
+    if (myDisposed) return;
+    UIUtil.invokeLaterIfNeeded(new Runnable() {
+      @Override
+      public void run() {
+        if (myDisposed) return;
+        myDisposed = true;
+
+        onDispose();
+
+        Disposer.dispose(myViewer);
+
+        myToolbarStatusPanel.setContent(null);
+        myToolbarPanel.setContent(null);
+        myContentPanel.setContent(null);
+      }
+    });
+  }
+
+  @CalledInAwt
+  private void applyRequestResult(@NotNull MergeResult result) {
+    if (myConflictResolved) return;
+    myConflictResolved = true;
+    try {
+      myRequest.applyResult(result);
+    }
+    catch (Exception e) {
+      LOG.error(e);
+    }
+  }
+
+  //
+  // Abstract
+  //
+
+  @CalledInAwt
+  protected void onDispose() {
+    applyRequestResult(MergeResult.CANCEL);
+  }
+
+  protected void setWindowTitle(@NotNull String title) {
+  }
+
+  public abstract void closeDialog();
+
+  @Nullable
+  public <T> T getContextUserData(@NotNull Key<T> key) {
+    return myContext.getUserData(key);
+  }
+
+  public <T> void putContextUserData(@NotNull Key<T> key, @Nullable T value) {
+    myContext.putUserData(key, value);
+  }
+
+  //
+  // Getters
+  //
+
+  @NotNull
+  public JComponent getComponent() {
+    return myPanel;
+  }
+
+  @Nullable
+  public JComponent getPreferredFocusedComponent() {
+    JComponent component = myViewer.getPreferredFocusedComponent();
+    return component != null ? component : myToolbarPanel.getTargetComponent();
+  }
+
+  @Nullable
+  public Project getProject() {
+    return myProject;
+  }
+
+  @NotNull
+  public MergeContext getContext() {
+    return myContext;
+  }
+
+  @CalledInAwt
+  public boolean checkCloseAction() {
+    return myConflictResolved || myCloseHandler == null || myCloseHandler.get();
+  }
+
+  @Nullable
+  public String getHelpId() {
+    return PlatformDataKeys.HELP_ID.getData(myMainPanel);
+  }
+
+  //
+  // Misc
+  //
+
+  public boolean isFocused() {
+    return DiffUtil.isFocusedComponent(myProject, myPanel);
+  }
+
+  public void requestFocus() {
+    DiffUtil.requestFocus(myProject, getPreferredFocusedComponent());
+  }
+
+  protected void requestFocusInternal() {
+    JComponent component = getPreferredFocusedComponent();
+    if (component != null) component.requestFocus();
+  }
+
+  //
+  // Navigation
+  //
+
+  private static class MyNextDifferenceAction extends NextDifferenceAction {
+    @Override
+    public void update(@NotNull AnActionEvent e) {
+      if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
+        e.getPresentation().setEnabled(true);
+        return;
+      }
+
+      PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
+      if (iterable != null && iterable.canGoNext()) {
+        e.getPresentation().setEnabled(true);
+        return;
+      }
+
+      e.getPresentation().setEnabled(false);
+    }
+
+    @Override
+    public void actionPerformed(@NotNull AnActionEvent e) {
+      PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
+      if (iterable != null && iterable.canGoNext()) {
+        iterable.goNext();
+      }
+    }
+  }
+
+  private static class MyPrevDifferenceAction extends PrevDifferenceAction {
+    @Override
+    public void update(@NotNull AnActionEvent e) {
+      if (!ActionPlaces.DIFF_TOOLBAR.equals(e.getPlace())) {
+        e.getPresentation().setEnabled(true);
+        return;
+      }
+
+      PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
+      if (iterable != null && iterable.canGoPrev()) {
+        e.getPresentation().setEnabled(true);
+        return;
+      }
+
+      e.getPresentation().setEnabled(false);
+    }
+
+    @Override
+    public void actionPerformed(@NotNull AnActionEvent e) {
+      PrevNextDifferenceIterable iterable = DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.getData(e.getDataContext());
+      if (iterable != null && iterable.canGoPrev()) {
+        iterable.goPrev();
+      }
+    }
+  }
+
+  //
+  // Helpers
+  //
+
+  private class MyPanel extends JPanel implements DataProvider {
+    public MyPanel() {
+      super(new BorderLayout());
+    }
+
+    @Nullable
+    @Override
+    public Object getData(@NonNls String dataId) {
+      Object data;
+
+      DataProvider contentProvider = DataManagerImpl.getDataProviderEx(myContentPanel.getTargetComponent());
+      if (contentProvider != null) {
+        data = contentProvider.getData(dataId);
+        if (data != null) return data;
+      }
+
+      if (CommonDataKeys.PROJECT.is(dataId)) {
+        return myProject;
+      }
+      else if (PlatformDataKeys.HELP_ID.is(dataId)) {
+        if (myRequest.getUserData(DiffUserDataKeys.HELP_ID) != null) {
+          return myRequest.getUserData(DiffUserDataKeys.HELP_ID);
+        }
+        else {
+          return "procedures.vcWithIDEA.commonVcsOps.integrateDiffs.resolveConflict";
+        }
+      }
+
+      DataProvider requestProvider = myRequest.getUserData(DiffUserDataKeys.DATA_PROVIDER);
+      if (requestProvider != null) {
+        data = requestProvider.getData(dataId);
+        if (data != null) return data;
+      }
+
+      DataProvider contextProvider = myContext.getUserData(DiffUserDataKeys.DATA_PROVIDER);
+      if (contextProvider != null) {
+        data = contextProvider.getData(dataId);
+        if (data != null) return data;
+      }
+      return null;
+    }
+  }
+
+  private class MyFocusTraversalPolicy extends IdeFocusTraversalPolicy {
+    @Override
+    public final Component getDefaultComponentImpl(final Container focusCycleRoot) {
+      JComponent component = MergeRequestProcessor.this.getPreferredFocusedComponent();
+      if (component == null) return null;
+      return IdeFocusTraversalPolicy.getPreferredFocusedComponent(component, this);
+    }
+  }
+
+  private class MyDiffContext extends MergeContext {
+    @Nullable
+    @Override
+    public Project getProject() {
+      return MergeRequestProcessor.this.getProject();
+    }
+
+    @Override
+    public boolean isFocused() {
+      return MergeRequestProcessor.this.isFocused();
+    }
+
+    @Override
+    public void requestFocus() {
+      MergeRequestProcessor.this.requestFocusInternal();
+    }
+
+    @Override
+    public void finishMerge(@NotNull MergeResult result) {
+      applyRequestResult(result);
+      MergeRequestProcessor.this.closeDialog();
+    }
+  }
+
+  public static class BottomActions {
+    @Nullable public Action applyLeft;
+    @Nullable public Action applyRight;
+    @Nullable public Action resolveAction;
+    @Nullable public Action cancelAction;
+  }
+}
diff --git a/platform/diff-impl/src/com/intellij/diff/merge/MergeUtil.java b/platform/diff-impl/src/com/intellij/diff/merge/MergeUtil.java
new file mode 100644 (file)
index 0000000..36b5e6d
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.diff.DiffContext;
+import com.intellij.diff.util.ThreeSide;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+public class MergeUtil {
+  @NotNull
+  public static String getResolveActionTitle(@NotNull MergeResult result, @NotNull MergeRequest request, @NotNull MergeContext context) {
+    switch (result) {
+      case CANCEL:
+        return "Abort";
+      case LEFT:
+        return "Accept Left";
+      case RIGHT:
+        return "Accept Right";
+      case RESOLVED:
+        return "Apply";
+      default:
+        throw new IllegalArgumentException(result.toString());
+    }
+  }
+
+  @NotNull
+  public static List<String> notNullizeContentTitles(@NotNull List<String> mergeContentTitles) {
+    String left = StringUtil.notNullize(ThreeSide.LEFT.select(mergeContentTitles), "Your Version");
+    String base = StringUtil.notNullize(ThreeSide.BASE.select(mergeContentTitles), "Base Version");
+    String right = StringUtil.notNullize(ThreeSide.RIGHT.select(mergeContentTitles), "Server Version");
+    return ContainerUtil.list(left, base, right);
+  }
+
+  public static class ProxyDiffContext extends DiffContext {
+    @NotNull private final MergeContext myMergeContext;
+
+    public ProxyDiffContext(@NotNull MergeContext mergeContext) {
+      myMergeContext = mergeContext;
+    }
+
+    @Nullable
+    @Override
+    public Project getProject() {
+      return myMergeContext.getProject();
+    }
+
+    @Override
+    public boolean isWindowFocused() {
+      return true;
+    }
+
+    @Override
+    public boolean isFocused() {
+      return myMergeContext.isFocused();
+    }
+
+    @Override
+    public void requestFocus() {
+      myMergeContext.requestFocus();
+    }
+
+    @Nullable
+    @Override
+    public <T> T getUserData(@NotNull Key<T> key) {
+      return myMergeContext.getUserData(key);
+    }
+
+    @Override
+    public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
+      myMergeContext.putUserData(key, value);
+    }
+  }
+}
diff --git a/platform/diff-impl/src/com/intellij/diff/merge/MergeWindow.java b/platform/diff-impl/src/com/intellij/diff/merge/MergeWindow.java
new file mode 100644 (file)
index 0000000..7566317
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.diff.util.DiffUserDataKeys;
+import com.intellij.diff.util.DiffUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.List;
+
+public class MergeWindow {
+  @Nullable private final Project myProject;
+  @NotNull private final MergeRequest myMergeRequest;
+
+  private MyDialog myWrapper;
+
+  public MergeWindow(@Nullable Project project, @NotNull MergeRequest mergeRequest) {
+    myProject = project;
+    myMergeRequest = mergeRequest;
+  }
+
+  protected void init() {
+    MergeRequestProcessor processor = new MergeRequestProcessor(myProject, myMergeRequest) {
+      @Override
+      public void closeDialog() {
+        myWrapper.doCancelAction();
+      }
+
+      @Override
+      protected void setWindowTitle(@NotNull String title) {
+        myWrapper.setTitle(title);
+      }
+    };
+
+    myWrapper = new MyDialog(processor);
+    myWrapper.init();
+  }
+
+  public void show() {
+    init();
+    myWrapper.show();
+  }
+
+  // TODO: use WindowWrapper
+  private static class MyDialog extends DialogWrapper {
+    @NotNull private final MergeRequestProcessor myProcessor;
+    @NotNull private final MergeRequestProcessor.BottomActions myBottomActions;
+
+    public MyDialog(@NotNull MergeRequestProcessor processor) {
+      super(processor.getProject(), true);
+      myProcessor = processor;
+      myBottomActions = myProcessor.getBottomActions();
+    }
+
+    @Override
+    public void init() {
+      super.init();
+      Disposer.register(getDisposable(), myProcessor);
+      getWindow().addWindowListener(new WindowAdapter() {
+        @Override
+        public void windowOpened(WindowEvent e) {
+          myProcessor.init();
+        }
+      });
+    }
+
+    @Nullable
+    @Override
+    protected JComponent createCenterPanel() {
+      return new MyPanel(myProcessor.getComponent());
+    }
+
+    @Nullable
+    @Override
+    public JComponent getPreferredFocusedComponent() {
+      return myProcessor.getPreferredFocusedComponent();
+    }
+
+    @Nullable
+    @Override
+    protected String getDimensionServiceKey() {
+      return StringUtil.notNullize(myProcessor.getContextUserData(DiffUserDataKeys.DIALOG_GROUP_KEY), "MergeDialog");
+    }
+
+    @NotNull
+    @Override
+    protected Action[] createActions() {
+      List<Action> actions = ContainerUtil.skipNulls(ContainerUtil.list(myBottomActions.resolveAction, myBottomActions.cancelAction));
+      if (myBottomActions.resolveAction != null) {
+        myBottomActions.resolveAction.putValue(DialogWrapper.DEFAULT_ACTION, true);
+      }
+      return actions.toArray(new Action[actions.size()]);
+    }
+
+    @NotNull
+    @Override
+    protected Action[] createLeftSideActions() {
+      List<Action> actions = ContainerUtil.skipNulls(ContainerUtil.list(myBottomActions.applyLeft, myBottomActions.applyRight));
+      return actions.toArray(new Action[actions.size()]);
+    }
+
+    @NotNull
+    @Override
+    protected Action getOKAction() {
+      if (myBottomActions.resolveAction != null) return myBottomActions.resolveAction;
+      return super.getOKAction();
+    }
+
+    @NotNull
+    @Override
+    protected Action getCancelAction() {
+      if (myBottomActions.cancelAction != null) return myBottomActions.cancelAction;
+      return super.getCancelAction();
+    }
+
+    @Nullable
+    @Override
+    protected String getHelpId() {
+      return myProcessor.getHelpId();
+    }
+
+    @Override
+    public void doCancelAction() {
+      if (!myProcessor.checkCloseAction()) return;
+      super.doCancelAction();
+    }
+  }
+
+  private static class MyPanel extends JPanel {
+    public MyPanel(@NotNull JComponent content) {
+      super(new BorderLayout());
+      add(content, BorderLayout.CENTER);
+    }
+
+    @Override
+    public Dimension getPreferredSize() {
+      Dimension windowSize = DiffUtil.getDefaultDiffWindowSize();
+      Dimension size = super.getPreferredSize();
+      return new Dimension(Math.max(windowSize.width, size.width), Math.max(windowSize.height, size.height));
+    }
+  }
+}
diff --git a/platform/diff-impl/src/com/intellij/diff/merge/TextMergeChange.java b/platform/diff-impl/src/com/intellij/diff/merge/TextMergeChange.java
new file mode 100644 (file)
index 0000000..5479b0f
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.diff.comparison.ComparisonPolicy;
+import com.intellij.diff.fragments.MergeLineFragment;
+import com.intellij.diff.tools.simple.ThreesideDiffChangeBase;
+import com.intellij.diff.util.*;
+import com.intellij.diff.util.DiffUtil.UpdatedLineRange;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.diff.DiffBundle;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.editor.markup.*;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.CalledInAwt;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TextMergeChange extends ThreesideDiffChangeBase {
+  @NotNull private final TextMergeTool.TextMergeViewer myMergeViewer;
+  @NotNull private final TextMergeTool.TextMergeViewer.MyThreesideViewer myViewer;
+  @NotNull private final List<RangeHighlighter> myHighlighters = new ArrayList<RangeHighlighter>();
+
+  @NotNull private final List<MyGutterOperation> myOperations = new ArrayList<MyGutterOperation>();
+
+  private final int[] myStartLines = new int[3];
+  private final int[] myEndLines = new int[3];
+  private final boolean[] myResolved = new boolean[2];
+  private boolean myOnesideAppliedConflict;
+
+  @CalledInAwt
+  public TextMergeChange(@NotNull MergeLineFragment fragment, @NotNull TextMergeTool.TextMergeViewer viewer) {
+    super(fragment, viewer.getViewer().getEditors(), ComparisonPolicy.DEFAULT);
+    myMergeViewer = viewer;
+    myViewer = viewer.getViewer();
+
+    for (ThreeSide side : ThreeSide.values()) {
+      myStartLines[side.getIndex()] = fragment.getStartLine(side);
+      myEndLines[side.getIndex()] = fragment.getEndLine(side);
+    }
+
+    installHighlighter();
+  }
+
+  void installHighlighter() {
+    assert myHighlighters.isEmpty();
+
+    createHighlighter(ThreeSide.BASE);
+    if (getType().isLeftChange()) createHighlighter(ThreeSide.LEFT);
+    if (getType().isRightChange()) createHighlighter(ThreeSide.RIGHT);
+
+    doInstallActionHighlighters();
+  }
+
+  @CalledInAwt
+  void destroyHighlighter() {
+    for (RangeHighlighter highlighter : myHighlighters) {
+      highlighter.dispose();
+    }
+    myHighlighters.clear();
+
+    for (MyGutterOperation operation : myOperations) {
+      operation.dispose();
+    }
+    myOperations.clear();
+  }
+
+  @CalledInAwt
+  void doReinstallHighlighter() {
+    destroyHighlighter();
+    installHighlighter();
+    myViewer.repaintDividers();
+  }
+
+  private void createHighlighter(@NotNull ThreeSide side) {
+    Editor editor = side.select(myViewer.getEditors());
+    Document document = editor.getDocument();
+
+    TextDiffType type = getDiffType();
+    boolean resolved = isResolved(side);
+    int startLine = getStartLine(side);
+    int endLine = getEndLine(side);
+
+    int start;
+    int end;
+    if (startLine == endLine) {
+      start = end = startLine < DiffUtil.getLineCount(document) ? document.getLineStartOffset(startLine) : document.getTextLength();
+    }
+    else {
+      start = document.getLineStartOffset(startLine);
+      end = document.getLineEndOffset(endLine - 1);
+      if (end < document.getTextLength()) end++;
+    }
+
+    myHighlighters.addAll(DiffDrawUtil.createHighlighter(editor, start, end, type, false, HighlighterTargetArea.EXACT_RANGE, resolved));
+
+    if (startLine == endLine) {
+      if (startLine != 0) {
+        myHighlighters.addAll(DiffDrawUtil.createLineMarker(editor, endLine - 1, type, SeparatorPlacement.BOTTOM, true, resolved));
+      }
+    }
+    else {
+      myHighlighters.addAll(DiffDrawUtil.createLineMarker(editor, startLine, type, SeparatorPlacement.TOP, false, resolved));
+      myHighlighters.addAll(DiffDrawUtil.createLineMarker(editor, endLine - 1, type, SeparatorPlacement.BOTTOM, false, resolved));
+    }
+  }
+
+  //
+  // Getters
+  //
+
+  @CalledInAwt
+  void setResolved(@NotNull Side side, boolean value) {
+    myResolved[side.getIndex()] = value;
+  }
+
+  public boolean isResolved() {
+    return myResolved[0] && myResolved[1];
+  }
+
+  public boolean isResolved(@NotNull Side side) {
+    return side.select(myResolved);
+  }
+
+  public boolean isOnesideAppliedConflict() {
+    return myOnesideAppliedConflict;
+  }
+
+  public void markOnesideAppliedConflict() {
+    myOnesideAppliedConflict = true;
+  }
+
+  public boolean isResolved(@NotNull ThreeSide side) {
+    switch (side) {
+      case LEFT:
+        return isResolved(Side.LEFT);
+      case BASE:
+        return isResolved();
+      case RIGHT:
+        return isResolved(Side.RIGHT);
+      default:
+        throw new IllegalArgumentException(side.toString());
+    }
+  }
+
+  public int getStartLine(@NotNull ThreeSide side) {
+    return side.select(myStartLines);
+  }
+
+  public int getEndLine(@NotNull ThreeSide side) {
+    return side.select(myEndLines);
+  }
+
+  public void setStartLine(@NotNull ThreeSide side, int value) {
+    myStartLines[side.getIndex()] = value;
+  }
+
+  public void setEndLine(@NotNull ThreeSide side, int value) {
+    myEndLines[side.getIndex()] = value;
+  }
+
+  //
+  // Shift
+  //
+
+  @Nullable
+  State processBaseChange(int oldLine1, int oldLine2, int shift) {
+    int line1 = getStartLine(ThreeSide.BASE);
+    int line2 = getEndLine(ThreeSide.BASE);
+
+    UpdatedLineRange newRange = DiffUtil.updateRangeOnModification(line1, line2, oldLine1, oldLine2, shift);
+
+    boolean rangeAffected = newRange.damaged ||
+                            (oldLine2 >= line1 && oldLine1 <= line2); // RangeMarker can be updated in a different way
+    State oldState = rangeAffected ? storeState() : null;
+
+    if (newRange.startLine == newRange.endLine && getDiffType() == TextDiffType.DELETED && !isResolved()) {
+      if (oldState == null) oldState = storeState();
+      myViewer.markChangeResolved(this);
+    }
+
+    setStartLine(ThreeSide.BASE, newRange.startLine);
+    setEndLine(ThreeSide.BASE, newRange.endLine);
+
+    return oldState;
+  }
+
+  //
+  // Gutter actions
+  //
+
+  private void doInstallActionHighlighters() {
+    ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.LEFT, OperationType.APPLY));
+    ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.LEFT, OperationType.IGNORE));
+    ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.RIGHT, OperationType.APPLY));
+    ContainerUtil.addIfNotNull(myOperations, createOperation(ThreeSide.RIGHT, OperationType.IGNORE));
+  }
+
+  @Nullable
+  private MyGutterOperation createOperation(@NotNull ThreeSide side, @NotNull OperationType type) {
+    if (isResolved(side)) return null;
+
+    EditorEx editor = myViewer.getEditor(side);
+    Document document = editor.getDocument();
+
+    int line = getStartLine(side);
+    int offset = line == DiffUtil.getLineCount(document) ? document.getTextLength() : document.getLineStartOffset(line);
+
+    RangeHighlighter highlighter = editor.getMarkupModel().addRangeHighlighter(offset, offset,
+                                                                               HighlighterLayer.ADDITIONAL_SYNTAX,
+                                                                               null,
+                                                                               HighlighterTargetArea.LINES_IN_RANGE);
+    return new MyGutterOperation(side, highlighter, type);
+  }
+
+  public void updateGutterActions(boolean force) {
+    for (MyGutterOperation operation : myOperations) {
+      operation.update(force);
+    }
+  }
+
+  private class MyGutterOperation {
+    @NotNull private final ThreeSide mySide;
+    @NotNull private final RangeHighlighter myHighlighter;
+    @NotNull private final OperationType myType;
+
+    private boolean myCtrlPressed;
+    private boolean myShiftPressed;
+
+    private MyGutterOperation(@NotNull ThreeSide side, @NotNull RangeHighlighter highlighter, @NotNull OperationType type) {
+      mySide = side;
+      myHighlighter = highlighter;
+      myType = type;
+
+      update(true);
+    }
+
+    public void dispose() {
+      myHighlighter.dispose();
+    }
+
+    public void update(boolean force) {
+      if (!force && !areModifiersChanged()) {
+        return;
+      }
+      if (myHighlighter.isValid()) myHighlighter.setGutterIconRenderer(createRenderer());
+    }
+
+    private boolean areModifiersChanged() {
+      return myCtrlPressed != myViewer.getModifierProvider().isCtrlPressed() ||
+             myShiftPressed != myViewer.getModifierProvider().isShiftPressed();
+    }
+
+    @Nullable
+    public GutterIconRenderer createRenderer() {
+      if (mySide == ThreeSide.BASE) return null;
+      Side versionSide = mySide.select(Side.LEFT, null, Side.RIGHT);
+      assert versionSide != null;
+
+      myCtrlPressed = myViewer.getModifierProvider().isCtrlPressed();
+      myShiftPressed = myViewer.getModifierProvider().isShiftPressed();
+
+      if (!isChange(versionSide)) return null;
+
+      switch (myType) {
+        case APPLY:
+          return createApplyRenderer(versionSide, myCtrlPressed);
+        case IGNORE:
+          return createIgnoreRenderer(versionSide, myCtrlPressed);
+        default:
+          throw new IllegalArgumentException(myType.name());
+      }
+    }
+  }
+
+  @Nullable
+  private GutterIconRenderer createApplyRenderer(@NotNull final Side side, final boolean modifier) {
+    if (isResolved(side)) return null;
+    Icon icon = isOnesideAppliedConflict() ? AllIcons.Diff.ArrowLeftDown : AllIcons.Diff.Arrow;
+    return createIconRenderer(DiffBundle.message("merge.dialog.apply.change.action.name"), icon, new Runnable() {
+      @Override
+      public void run() {
+        myViewer.executeMergeCommand("Apply change", Collections.singletonList(TextMergeChange.this), new Runnable() {
+          @Override
+          public void run() {
+            myViewer.replaceChange(TextMergeChange.this, side, modifier);
+          }
+        });
+      }
+    });
+  }
+
+  @Nullable
+  private GutterIconRenderer createIgnoreRenderer(@NotNull final Side side, final boolean modifier) {
+    if (isResolved(side)) return null;
+    return createIconRenderer(DiffBundle.message("merge.dialog.ignore.change.action.name"), AllIcons.Diff.Remove, new Runnable() {
+      @Override
+      public void run() {
+        myViewer.executeMergeCommand(null, Collections.singletonList(TextMergeChange.this), new Runnable() {
+          @Override
+          public void run() {
+            myViewer.ignoreChange(TextMergeChange.this, side, modifier);
+          }
+        });
+      }
+    });
+  }
+
+  @Nullable
+  private GutterIconRenderer createIconRenderer(@NotNull final String tooltipText,
+                                                @NotNull final Icon icon,
+                                                @NotNull final Runnable perform) {
+    return new GutterIconRenderer() {
+      @NotNull
+      @Override
+      public Icon getIcon() {
+        return icon;
+      }
+
+      public boolean isNavigateAction() {
+        return true;
+      }
+
+      @Nullable
+      @Override
+      public AnAction getClickAction() {
+        return new DumbAwareAction() {
+          @Override
+          public void actionPerformed(AnActionEvent e) {
+            perform.run();
+          }
+        };
+      }
+
+      @Override
+      public boolean equals(Object obj) {
+        return obj == this;
+      }
+
+      @Override
+      public int hashCode() {
+        return System.identityHashCode(this);
+      }
+
+      @Nullable
+      @Override
+      public String getTooltipText() {
+        return tooltipText;
+      }
+
+      @Override
+      public boolean isDumbAware() {
+        return true;
+      }
+    };
+  }
+
+  private enum OperationType {
+    APPLY, IGNORE
+  }
+
+  //
+  // State
+  //
+
+  @NotNull
+  State storeState() {
+    return new State(
+      myStartLines[0],
+      myStartLines[1],
+      myStartLines[2],
+
+      myEndLines[0],
+      myEndLines[1],
+      myEndLines[2],
+
+      myResolved[0],
+      myResolved[1],
+
+      myOnesideAppliedConflict);
+  }
+
+  void restoreState(@NotNull State state) {
+    myStartLines[0] = state.myStartLine1;
+    myStartLines[1] = state.myStartLine2;
+    myStartLines[2] = state.myStartLine3;
+
+    myEndLines[0] = state.myEndLine1;
+    myEndLines[1] = state.myEndLine2;
+    myEndLines[2] = state.myEndLine3;
+
+    myResolved[0] = state.myResolved1;
+    myResolved[1] = state.myResolved2;
+
+    myOnesideAppliedConflict = state.myOnesideAppliedConflict;
+  }
+
+  public static class State {
+    private final int myStartLine1;
+    private final int myStartLine2;
+    private final int myStartLine3;
+
+    private final int myEndLine1;
+    private final int myEndLine2;
+    private final int myEndLine3;
+
+    private final boolean myResolved1;
+    private final boolean myResolved2;
+
+    private final boolean myOnesideAppliedConflict;
+
+    public State(int startLine1,
+                 int startLine2,
+                 int startLine3,
+                 int endLine1,
+                 int endLine2,
+                 int endLine3,
+                 boolean resolved1,
+                 boolean resolved2,
+                 boolean onesideAppliedConflict) {
+      myStartLine1 = startLine1;
+      myStartLine2 = startLine2;
+      myStartLine3 = startLine3;
+      myEndLine1 = endLine1;
+      myEndLine2 = endLine2;
+      myEndLine3 = endLine3;
+      myResolved1 = resolved1;
+      myResolved2 = resolved2;
+      myOnesideAppliedConflict = onesideAppliedConflict;
+    }
+  }
+}
diff --git a/platform/diff-impl/src/com/intellij/diff/merge/TextMergeTool.java b/platform/diff-impl/src/com/intellij/diff/merge/TextMergeTool.java
new file mode 100644 (file)
index 0000000..a0c1ca2
--- /dev/null
@@ -0,0 +1,1254 @@
+/*
+ * Copyright 2000-2015 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.diff.merge;
+
+import com.intellij.diff.DiffContext;
+import com.intellij.diff.FrameDiffTool;
+import com.intellij.diff.comparison.ByLine;
+import com.intellij.diff.comparison.ComparisonMergeUtil;
+import com.intellij.diff.comparison.ComparisonPolicy;
+import com.intellij.diff.comparison.DiffTooBigException;
+import com.intellij.diff.comparison.iterables.FairDiffIterable;
+import com.intellij.diff.contents.DiffContent;
+import com.intellij.diff.contents.DocumentContent;
+import com.intellij.diff.fragments.MergeLineFragment;
+import com.intellij.diff.requests.ContentDiffRequest;
+import com.intellij.diff.requests.SimpleDiffRequest;
+import com.intellij.diff.tools.simple.ThreesideTextDiffViewerEx;
+import com.intellij.diff.tools.util.DiffNotifications;
+import com.intellij.diff.tools.util.KeyboardModifierListener;
+import com.intellij.diff.util.*;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.UndoConfirmationPolicy;
+import com.intellij.openapi.command.undo.*;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.diff.DiffBundle;
+import com.intellij.openapi.editor.Caret;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.event.DocumentEvent;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.fileEditor.TextEditor;
+import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.popup.Balloon;
+import com.intellij.openapi.ui.popup.BalloonBuilder;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.util.BooleanGetter;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Pair;
+import com.intellij.ui.HyperlinkAdapter;
+import com.intellij.ui.awt.RelativePoint;
+import com.intellij.util.Function;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.hash.HashSet;
+import com.intellij.util.ui.JBUI;
+import org.jetbrains.annotations.CalledInAwt;
+import org.jetbrains.annotations.CalledWithWriteLock;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.event.HyperlinkEvent;
+import javax.swing.event.HyperlinkListener;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Set;
+
+public class TextMergeTool implements MergeTool {
+  public static final TextMergeTool INSTANCE = new TextMergeTool();
+
+  public static final Logger LOG = Logger.getInstance(TextMergeTool.class);
+
+  @NotNull
+  @Override
+  public MergeViewer createComponent(@NotNull MergeContext context, @NotNull MergeRequest request) {
+    return new TextMergeViewer(context, ((TextMergeRequest)request));
+  }
+
+  @Override
+  public boolean canShow(@NotNull MergeContext context, @NotNull MergeRequest request) {
+    return request instanceof TextMergeRequest;
+  }
+
+  public static class TextMergeViewer implements MergeViewer {
+    @NotNull private final MergeContext myMergeContext;
+    @NotNull private final TextMergeRequest myMergeRequest;
+
+    @NotNull private final DiffContext myDiffContext;
+    @NotNull private final ContentDiffRequest myDiffRequest;
+
+    @NotNull private final MyThreesideViewer myViewer;
+
+    public TextMergeViewer(@NotNull MergeContext context, @NotNull TextMergeRequest request) {
+      myMergeContext = context;
+      myMergeRequest = request;
+
+      myDiffContext = new MergeUtil.ProxyDiffContext(myMergeContext);
+      myDiffRequest = new SimpleDiffRequest(myMergeRequest.getTitle(),
+                                            getDiffContents(myMergeRequest),
+                                            getDiffContentTitles(myMergeRequest));
+      myDiffRequest.putUserData(DiffUserDataKeys.FORCE_READ_ONLY_CONTENTS, new boolean[]{true, false, true});
+
+      myViewer = new MyThreesideViewer(myDiffContext, myDiffRequest);
+    }
+
+    @NotNull
+    private static List<DiffContent> getDiffContents(@NotNull TextMergeRequest mergeRequest) {
+      List<DocumentContent> contents = mergeRequest.getContents();
+
+      final DocumentContent left = ThreeSide.LEFT.select(contents);
+      final DocumentContent right = ThreeSide.RIGHT.select(contents);
+      final DocumentContent output = mergeRequest.getOutputContent();
+
+      return ContainerUtil.<DiffContent>list(left, output, right);
+    }
+
+    @NotNull
+    private static List<String> getDiffContentTitles(@NotNull TextMergeRequest mergeRequest) {
+      List<String> titles = MergeUtil.notNullizeContentTitles(mergeRequest.getContentTitles());
+      titles.set(ThreeSide.BASE.getIndex(), "Result");
+      return titles;
+    }
+
+    //
+    // Impl
+    //
+
+    @NotNull
+    @Override
+    public JComponent getComponent() {
+      return myViewer.getComponent();
+    }
+
+    @Nullable
+    @Override
+    public JComponent getPreferredFocusedComponent() {
+      return myViewer.getPreferredFocusedComponent();
+    }
+
+    @Override
+    public ToolbarComponents init() {
+      ToolbarComponents components = new ToolbarComponents();
+
+      FrameDiffTool.ToolbarComponents init = myViewer.init();
+      components.statusPanel = init.statusPanel;
+      components.toolbarActions = init.toolbarActions;
+
+      components.closeHandler = new BooleanGetter() {
+        @Override
+        public boolean get() {
+          return Messages.showYesNoDialog(getComponent().getRootPane(),
+                                          DiffBundle.message("merge.dialog.exit.without.applying.changes.confirmation.message"),
+                                          DiffBundle.message("cancel.visual.merge.dialog.title"), Messages.getQuestionIcon()) == Messages.YES;
+        }
+      };
+
+      return components;
+    }
+
+    @Nullable
+    @Override
+    public Action getResolveAction(@NotNull MergeResult result) {
+      return myViewer.getResolveAction(result);
+    }
+
+    @Override
+    public void dispose() {
+      Disposer.dispose(myViewer);
+    }
+
+    //
+    // Getters
+    //
+
+    @NotNull
+    public MyThreesideViewer getViewer() {
+      return myViewer;
+    }
+
+    //
+    // Viewer
+    //
+
+    public class MyThreesideViewer extends ThreesideTextDiffViewerEx {
+      @NotNull private final ModifierProvider myModifierProvider;
+
+      // all changes - both applied and unapplied ones
+      @NotNull private final List<TextMergeChange> myAllMergeChanges = new ArrayList<TextMergeChange>();
+
+      private boolean myInitialRediffStarted;
+      private boolean myInitialRediffFinished;
+      private boolean myContentModified;
+
+      @Nullable private MergeCommandAction myCurrentMergeCommand;
+      private int myBulkChangeUpdateDepth;
+
+      private final Set<TextMergeChange> myChangesToUpdate = new HashSet<TextMergeChange>();
+
+      public MyThreesideViewer(@NotNull DiffContext context, @NotNull ContentDiffRequest request) {
+        super(context, request);
+
+        myModifierProvider = new ModifierProvider();
+
+        DiffUtil.registerAction(new ApplySelectedChangesAction(Side.LEFT, true), myPanel);
+        DiffUtil.registerAction(new ApplySelectedChangesAction(Side.RIGHT, true), myPanel);
+        DiffUtil.registerAction(new IgnoreSelectedChangesAction(Side.LEFT, true), myPanel);
+        DiffUtil.registerAction(new IgnoreSelectedChangesAction(Side.RIGHT, true), myPanel);
+
+        new UndoRedoAction(true).register();
+        new UndoRedoAction(false).register();
+      }
+
+      @Override
+      protected void onInit() {
+        super.onInit();
+        myModifierProvider.init();
+      }
+
+      @Override
+      protected void onDispose() {
+        LOG.assertTrue(myBulkChangeUpdateDepth == 0);
+        super.onDispose();
+      }
+
+      @NotNull
+      @Override
+      protected List<AnAction> createToolbarActions() {
+        List<AnAction> group = new ArrayList<AnAction>();
+
+        group.add(new MyToggleAutoScrollAction());
+        group.add(myEditorSettingsAction);
+
+        group.add(Separator.getInstance());
+        group.add(new ShowLeftBasePartialDiffAction());
+        group.add(new ShowBaseRightPartialDiffAction());
+        group.add(new ShowLeftRightPartialDiffAction());
+
+        group.add(Separator.getInstance());
+        group.add(new ApplyNonConflictsAction());
+        group.add(new ApplySideNonConflictsAction(Side.LEFT));
+        group.add(new ApplySideNonConflictsAction(Side.RIGHT));
+
+        return group;
+      }
+
+      @NotNull
+      @Override
+      protected List<AnAction> createEditorPopupActions() {
+        List<AnAction> group = new ArrayList<AnAction>();
+
+        group.add(new ApplySelectedChangesAction(Side.LEFT, false));
+        group.add(new ApplySelectedChangesAction(Side.RIGHT, false));
+        group.add(new IgnoreSelectedChangesAction(Side.LEFT, false));
+        group.add(new IgnoreSelectedChangesAction(Side.RIGHT, false));
+        group.add(Separator.getInstance());
+
+        group.addAll(super.createEditorPopupActions());
+
+        return group;
+      }
+
+      @Nullable
+      @Override
+      protected List<AnAction> createPopupActions() {
+        List<AnAction> group = new ArrayList<AnAction>();
+
+        group.add(Separator.getInstance());
+        group.add(new MyToggleAutoScrollAction());
+
+        return group;
+      }
+
+      @Nullable
+      public Action getResolveAction(@NotNull final MergeResult result) {
+        String caption = MergeUtil.getResolveActionTitle(result, myMergeRequest, myMergeContext);
+        return new AbstractAction(caption) {
+          @Override
+          public void actionPerformed(ActionEvent e) {
+            if ((result == MergeResult.LEFT || result == MergeResult.RIGHT) && myContentModified &&
+                  Messages.showYesNoDialog(myPanel.getRootPane(),
+                                           DiffBundle.message("merge.dialog.resolve.side.with.discard.message", result == MergeResult.LEFT ? 0 : 1),
+                                           DiffBundle.message("merge.dialog.resolve.side.with.discard.title"), Messages.getQuestionIcon()) != Messages.YES) {
+              return;
+            }
+            if (result == MergeResult.RESOLVED) {
+              if ((getChangesCount() != 0 || getConflictsCount() != 0) &&
+                  Messages.showYesNoDialog(myPanel.getRootPane(),
+                                           DiffBundle.message("merge.dialog.apply.partially.resolved.changes.confirmation.message", getChangesCount(), getConflictsCount()),
+                                           DiffBundle.message("apply.partially.resolved.merge.dialog.title"),
+                                           Messages.getQuestionIcon()) != Messages.YES) {
+                return;
+              }
+            }
+            if (result == MergeResult.CANCEL) {
+              if (Messages.showYesNoDialog(myPanel.getRootPane(),
+                                           DiffBundle.message("merge.dialog.exit.without.applying.changes.confirmation.message"),
+                                           DiffBundle.message("cancel.visual.merge.dialog.title"), Messages.getQuestionIcon()) != Messages.YES) {
+                return;
+              }
+            }
+            destroyChangedBlocks();
+            myMergeContext.finishMerge(result);
+          }
+        };
+      }
+
+      //
+      // Diff
+      //
+
+      private void setInitialOutputContent() {
+        final Document baseDocument = ThreeSide.BASE.select(myMergeRequest.getContents()).getDocument();
+        final Document outputDocument = myMergeRequest.getOutputContent().getDocument();
+
+        DiffUtil.executeWriteCommand(outputDocument, getProject(), "Init merge content", new Runnable() {
+          @Override
+          public void run() {
+            outputDocument.setText(baseDocument.getCharsSequence());
+            UndoManager undoManager = getProject() != null ? UndoManager.getInstance(getProject()) : UndoManager.getGlobalInstance();
+            if (undoManager != null) {
+              DocumentReference ref = DocumentReferenceManager.getInstance().create(outputDocument);
+              undoManager.nonundoableActionPerformed(ref, false);
+            }
+          }
+        });
+      }
+
+      @Override
+      @CalledInAwt
+      public void rediff(boolean trySync) {
+        if (myInitialRediffStarted) return;
+        myInitialRediffStarted = true;
+        assert myAllMergeChanges.isEmpty();
+        doRediff();
+      }
+
+      @NotNull
+      @Override
+      protected Runnable performRediff(@NotNull ProgressIndicator indicator) {
+        throw new UnsupportedOperationException();
+      }
+
+      @CalledInAwt
+      private void doRediff() {
+        myStatusPanel.setBusy(true);
+
+        // This is made to reduce unwanted modifications before rediff is finished.
+        // It could happen between this init() EDT chunk and invokeLater().
+        getEditor(ThreeSide.BASE).setViewer(true);
+
+        setInitialOutputContent();
+
+        // we have to collect contents here, because someone can modify document while we're starting rediff
+        List<DiffContent> contents = myRequest.getContents();
+        final List<CharSequence> sequences = ContainerUtil.map(contents, new Function<DiffContent, CharSequence>() {
+          @Override
+          public CharSequence fun(DiffContent diffContent) {
+            return ((DocumentContent)diffContent).getDocument().getImmutableCharSequence();
+          }
+        });
+        final long outputModificationStamp = myMergeRequest.getOutputContent().getDocument().getModificationStamp();
+
+        // we need invokeLater() here because viewer is partially-initialized (ex: there are no toolbar or status panel)
+        // user can see this state while we're showing progress indicator, so we want let init() to finish.
+        ApplicationManager.getApplication().invokeLater(new Runnable() {
+          @Override
+          public void run() {
+            ProgressManager.getInstance().run(new Task.Modal(getProject(), "Computing differences...", true) {
+              private Runnable myCallback;
+
+              @Override
+              public void run(@NotNull ProgressIndicator indicator) {
+                myCallback = doPerformRediff(sequences, outputModificationStamp, indicator);
+              }
+
+              @Override
+              public void onCancel() {
+                myMergeContext.finishMerge(MergeResult.CANCEL);
+              }
+
+              @Override
+              public void onSuccess() {
+                myCallback.run();
+              }
+            });
+          }
+        });
+      }
+
+      @NotNull
+      protected Runnable doPerformRediff(@NotNull List<CharSequence> sequences,
+                                         long outputModificationStamp,
+                                         @NotNull ProgressIndicator indicator) {
+        try {
+          indicator.checkCanceled();
+
+          FairDiffIterable fragments1 = ByLine.compareTwoStepFair(sequences.get(1), sequences.get(0), ComparisonPolicy.DEFAULT, indicator);
+          FairDiffIterable fragments2 = ByLine.compareTwoStepFair(sequences.get(1), sequences.get(2), ComparisonPolicy.DEFAULT, indicator);
+          List<MergeLineFragment> mergeFragments = ComparisonMergeUtil.buildFair(fragments1, fragments2, indicator);
+          return apply(mergeFragments, outputModificationStamp);
+        }
+        catch (DiffTooBigException e) {
+          return applyNotification(DiffNotifications.DIFF_TOO_BIG);
+        }
+        catch (ProcessCanceledException e) {
+          throw e;
+        }
+        catch (Throwable e) {
+          LOG.error(e);
+          return new Runnable() {
+            @Override
+            public void run() {
+              clearDiffPresentation();
+              myPanel.setErrorContent();
+            }
+          };
+        }
+      }
+
+      @NotNull
+      private Runnable apply(@NotNull final List<MergeLineFragment> fragments, final long outputModificationStamp) {
+        return new Runnable() {
+          @Override
+          public void run() {
+            if (myMergeRequest.getOutputContent().getDocument().getModificationStamp() != outputModificationStamp) {
+              setInitialOutputContent(); // in case if anyone changed output content since init() call. (unlikely, but possible)
+            }
+
+            clearDiffPresentation();
+
+            resetChangeCounters();
+            for (MergeLineFragment fragment : fragments) {
+              TextMergeChange change = new TextMergeChange(fragment, TextMergeViewer.this);
+              myAllMergeChanges.add(change);
+              onChangeAdded(change);
+            }
+
+            myInitialScrollHelper.onRediff();
+
+            myContentPanel.repaintDividers();
+            myStatusPanel.update();
+
+            getEditor(ThreeSide.BASE).setViewer(false);
+
+            myInitialRediffFinished = true;
+          }
+        };
+      }
+
+      protected void destroyChangedBlocks() {
+        for (TextMergeChange change : myAllMergeChanges) {
+          change.destroyHighlighter();
+        }
+        myAllMergeChanges.clear();
+      }
+
+      //
+      // Impl
+      //
+
+      @Override
+      @CalledInAwt
+      protected void onBeforeDocumentChange(@NotNull DocumentEvent e) {
+        super.onBeforeDocumentChange(e);
+        enterBulkChangeUpdateBlock();
+        if (myAllMergeChanges.isEmpty()) return;
+
+        ThreeSide side = null;
+        if (e.getDocument() == getEditor(ThreeSide.LEFT).getDocument()) side = ThreeSide.LEFT;
+        if (e.getDocument() == getEditor(ThreeSide.RIGHT).getDocument()) side = ThreeSide.RIGHT;
+        if (e.getDocument() == getEditor(ThreeSide.BASE).getDocument()) side = ThreeSide.BASE;
+        if (side == null) {
+          LOG.warn("Unknown document changed");
+          return;
+        }
+
+        if (side != ThreeSide.BASE) {
+          LOG.error("Non-base side was changed"); // unsupported operation
+          return;
+        }
+
+        if (myInitialRediffFinished) myContentModified = true;
+
+        int line1 = e.getDocument().getLineNumber(e.getOffset());
+        int line2 = e.getDocument().getLineNumber(e.getOffset() + e.getOldLength()) + 1;
+        int shift = DiffUtil.countLinesShift(e);
+
+        final List<Pair<TextMergeChange, TextMergeChange.State>> corruptedStates = ContainerUtil.newArrayList();
+        for (TextMergeChange change : myAllMergeChanges) {
+          TextMergeChange.State oldState = change.processBaseChange(line1, line2, shift);
+          if (oldState != null) {
+            if (myCurrentMergeCommand == null) {
+              corruptedStates.add(Pair.create(change, oldState));
+            }
+            reinstallHighlighter(change); // document state is not updated yet - can't reinstall range here
+          }
+        }
+
+        if (!corruptedStates.isEmpty()) {
+          // document undo is registered inside onDocumentChange, so our undo() will be called after its undo().
+          // thus thus we can avoid checks for isUndoInProgress() (to avoid modification of the same TextMergeChange by this listener)
+          UndoManager.getInstance(getProject()).undoableActionPerformed(new BasicUndoableAction(getEditor(ThreeSide.BASE).getDocument()) {
+            @Override
+            public void undo() throws UnexpectedUndoException {
+              enterBulkChangeUpdateBlock();
+              for (Pair<TextMergeChange, TextMergeChange.State> pair : corruptedStates) {
+                restoreChangeState(pair.first, pair.second);
+              }
+              exitBulkChangeUpdateBlock();
+            }
+
+            @Override
+            public void redo() throws UnexpectedUndoException {
+            }
+          });
+        }
+      }
+
+      @Override
+      protected void onDocumentChange(@NotNull DocumentEvent e) {
+        super.onDocumentChange(e);
+        exitBulkChangeUpdateBlock();
+      }
+
+      public void repaintDividers() {
+        myContentPanel.repaintDividers();
+      }
+
+      @CalledInAwt
+      public void reinstallHighlighter(@NotNull TextMergeChange change) {
+        if (myBulkChangeUpdateDepth > 0) {
+          myChangesToUpdate.add(change);
+        }
+        else {
+          change.doReinstallHighlighter();
+        }
+      }
+
+      @CalledInAwt
+      public void enterBulkChangeUpdateBlock() {
+        myBulkChangeUpdateDepth++;
+      }
+
+      @CalledInAwt
+      public void exitBulkChangeUpdateBlock() {
+        myBulkChangeUpdateDepth--;
+        LOG.assertTrue(myBulkChangeUpdateDepth >= 0);
+
+        if (myBulkChangeUpdateDepth == 0) {
+          for (TextMergeChange change : myChangesToUpdate) {
+            change.doReinstallHighlighter();
+          }
+          myChangesToUpdate.clear();
+        }
+      }
+
+      private void onChangeResolved(@NotNull TextMergeChange change) {
+        if (change.isResolved()) {
+          onChangeRemoved(change);
+        }
+        else {
+          onChangeAdded(change);
+        }
+        if (getChangesCount() == 0 && getConflictsCount() == 0) {
+          LOG.assertTrue(getFirstUnresolvedChange(true, null) == null);
+          ApplicationManager.getApplication().invokeLater(new Runnable() {
+            @Override
+            public void run() {
+              String message = "All changes have been processed.<br><a href=\"\">Save changes and finish merging</a>";
+              HyperlinkListener listener = new HyperlinkAdapter() {
+                @Override
+                protected void hyperlinkActivated(HyperlinkEvent e) {
+                  destroyChangedBlocks();
+                  myMergeContext.finishMerge(MergeResult.RESOLVED);
+                }
+              };
+
+              JComponent component = getEditor(ThreeSide.BASE).getComponent();
+              Point point = new Point(component.getWidth() / 2, JBUI.scale(5));
+              Color bgColor = MessageType.INFO.getPopupBackground();
+
+              BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(message, null, bgColor, listener)
+                                                                          .setAnimationCycle(200);
+              Balloon balloon = balloonBuilder.createBalloon();
+              balloon.show(new RelativePoint(component, point), Balloon.Position.below);
+              Disposer.register(MyThreesideViewer.this, balloon);
+            }
+          });
+        }
+      }
+
+      //
+      // Getters
+      //
+
+      @NotNull
+      public List<TextMergeChange> getAllChanges() {
+        return myAllMergeChanges;
+      }
+
+      @NotNull
+      public List<TextMergeChange> getChanges() {
+        return ContainerUtil.filter(myAllMergeChanges, new Condition<TextMergeChange>() {
+          @Override
+          public boolean value(TextMergeChange mergeChange) {
+            return !mergeChange.isResolved();
+          }
+        });
+      }
+
+      @NotNull
+      @Override
+      protected DiffDividerDrawUtil.DividerPaintable getDividerPaintable(@NotNull Side side) {
+        return new MyDividerPaintable(side);
+      }
+
+      @NotNull
+      public ModifierProvider getModifierProvider() {
+        return myModifierProvider;
+      }
+
+      @Nullable
+      private TextMergeChange getFirstUnresolvedChange(boolean acceptConflicts, @Nullable Side side) {
+        for (TextMergeChange change : getAllChanges()) {
+          if (change.isResolved()) continue;
+          if (!acceptConflicts && change.isConflict()) continue;
+          if (side != null && !change.isChange(side)) continue;
+          return change;
+        }
+        return null;
+      }
+
+      //
+      // Modification operations
+      //
+
+      private void restoreChangeState(@NotNull TextMergeChange change, @NotNull TextMergeChange.State state) {
+        boolean wasResolved = change.isResolved();
+        change.restoreState(state);
+        reinstallHighlighter(change);
+        if (wasResolved != change.isResolved()) onChangeResolved(change);
+      }
+
+      private abstract class MergeCommandAction extends DiffUtil.DiffCommandAction {
+        @Nullable private final List<TextMergeChange> myAffectedChanges;
+
+        public MergeCommandAction(@Nullable Project project,
+                                  @Nullable String commandName,
+                                  @Nullable List<TextMergeChange> changes) {
+          super(project, getEditor(ThreeSide.BASE).getDocument(), commandName);
+          myAffectedChanges = collectAffectedChanges(changes);
+        }
+
+        public MergeCommandAction(@Nullable Project project,
+                                  @Nullable String commandName,
+                                  @Nullable String commandGroupId,
+                                  @NotNull UndoConfirmationPolicy confirmationPolicy,
+                                  @Nullable List<TextMergeChange> changes) {
+          super(project, getEditor(ThreeSide.BASE).getDocument(), commandName, commandGroupId, confirmationPolicy);
+          myAffectedChanges = collectAffectedChanges(changes);
+        }
+
+        @Override
+        @CalledWithWriteLock
+        protected final void execute() {
+          LOG.assertTrue(myCurrentMergeCommand == null);
+          myContentModified = true;
+
+          // We should restore states after changes in document (by DocumentUndoProvider) to avoid corruption by our onBeforeDocumentChange()
+          // Undo actions are performed in backward order, while redo actions are performed in forward order.
+          // Thus we should register two UndoableActions.
+
+          myCurrentMergeCommand = this;
+          registerUndoRedo(true);
+          enterBulkChangeUpdateBlock();
+          try {
+            doExecute();
+          }
+          finally {
+            exitBulkChangeUpdateBlock();
+            registerUndoRedo(false);
+            myCurrentMergeCommand = null;
+          }
+        }
+
+        private void registerUndoRedo(final boolean undo) {
+          List<TextMergeChange> affectedChanges = getAffectedChanges();
+          final List<TextMergeChange.State> states = new ArrayList<TextMergeChange.State>(affectedChanges.size());
+          for (TextMergeChange change : affectedChanges) {
+            states.add(change.storeState());
+          }
+
+          UndoManager.getInstance(getProject()).undoableActionPerformed(new BasicUndoableAction(myDocument) {
+            @Override
+            public void undo() throws UnexpectedUndoException {
+              if (undo) restoreStates(states);
+            }
+
+            @Override
+            public void redo() throws UnexpectedUndoException {
+              if (!undo) restoreStates(states);
+            }
+          });
+        }
+
+        private void restoreStates(@NotNull List<TextMergeChange.State> states) {
+          List<TextMergeChange> affectedChanges = getAffectedChanges();
+
+          enterBulkChangeUpdateBlock();
+          for (int i = 0; i < affectedChanges.size(); i++) {
+            restoreChangeState(affectedChanges.get(i), states.get(i));
+          }
+          exitBulkChangeUpdateBlock();
+        }
+
+        @NotNull
+        private List<TextMergeChange> getAffectedChanges() {
+          return myAffectedChanges != null ? myAffectedChanges : myAllMergeChanges;
+        }
+
+        @CalledWithWriteLock
+        protected abstract void doExecute();
+      }
+
+      /*
+       * affected changes should be sorted
+       */
+      public void executeMergeCommand(@Nullable String commandName,
+                                      @Nullable List<TextMergeChange> affected,
+                                      @NotNull final Runnable task) {
+        new MergeCommandAction(getProject(), commandName, affected) {
+          @Override
+          protected void doExecute() {
+            task.run();
+          }
+        }.run();
+      }
+
+      public void executeMergeCommand(@Nullable String commandName, @NotNull final Runnable task) {
+        executeMergeCommand(commandName, null, task);
+      }
+
+      @CalledInAwt
+      public void markChangeResolved(@NotNull TextMergeChange change) {
+        if (change.isResolved()) return;
+        change.setResolved(Side.LEFT, true);
+        change.setResolved(Side.RIGHT, true);
+
+        onChangeResolved(change);
+        reinstallHighlighter(change);
+      }
+
+      @CalledInAwt
+      public void markChangeResolved(@NotNull TextMergeChange change, @NotNull Side side) {
+        if (change.isResolved(side)) return;
+        change.setResolved(side, true);
+
+        if (change.isResolved()) onChangeResolved(change);
+        reinstallHighlighter(change);
+      }
+
+      public void ignoreChange(@NotNull TextMergeChange change, @NotNull Side side, boolean modifier) {
+        if (!change.isConflict() || modifier) {
+          markChangeResolved(change);
+        }
+        else {
+          markChangeResolved(change, side);
+        }
+      }
+
+      @CalledWithWriteLock
+      public void replaceChange(@NotNull TextMergeChange change, @NotNull Side side, boolean modifier) {
+        LOG.assertTrue(myCurrentMergeCommand != null);
+        if (change.isResolved(side)) return;
+        if (!change.isChange(side)) {
+          markChangeResolved(change);
+          return;
+        }
+
+        ThreeSide sourceSide = side.select(ThreeSide.LEFT, ThreeSide.RIGHT);
+        ThreeSide oppositeSide = side.select(ThreeSide.RIGHT, ThreeSide.LEFT);
+        ThreeSide outputSide = ThreeSide.BASE;
+
+        int outputStartLine = change.getStartLine(outputSide);
+        int outputEndLine = change.getEndLine(outputSide);
+        int sourceStartLine = change.getStartLine(sourceSide);
+        int sourceEndLine = change.getEndLine(sourceSide);
+
+        enterBulkChangeUpdateBlock();
+        try {
+          if (change.isConflict()) {
+            boolean append = change.isOnesideAppliedConflict();
+            int actualOutputStartLine = append ? outputEndLine : outputStartLine;
+
+            DiffUtil.applyModification(getContent(outputSide).getDocument(), actualOutputStartLine, outputEndLine,
+                                       getContent(sourceSide).getDocument(), sourceStartLine, sourceEndLine);
+
+            if (outputStartLine == outputEndLine || append) { // onBeforeDocumentChange() should process other cases correctly
+              int newOutputEndLine = actualOutputStartLine + (sourceEndLine - sourceStartLine);
+              moveChangesAfterInsertion(change, outputStartLine, newOutputEndLine);
+            }
+
+            if (modifier || change.getStartLine(oppositeSide) == change.getEndLine(oppositeSide)) {
+              markChangeResolved(change);
+            } else {
+              change.markOnesideAppliedConflict();
+              markChangeResolved(change, side);
+            }
+          }
+          else {
+            DiffUtil.applyModification(getContent(outputSide).getDocument(), outputStartLine, outputEndLine,
+                                       getContent(sourceSide).getDocument(), sourceStartLine, sourceEndLine);
+
+            if (outputStartLine == outputEndLine) { // onBeforeDocumentChange() should process other cases correctly
+              int newOutputEndLine = outputStartLine + (sourceEndLine - sourceStartLine);
+              moveChangesAfterInsertion(change, outputStartLine, newOutputEndLine);
+            }
+
+            markChangeResolved(change);
+          }
+        }
+        finally {
+          exitBulkChangeUpdateBlock();
+        }
+      }
+
+      /*
+       * We want to include inserted block into change, so we are updating endLine(BASE).
+       *
+       * It could break order of changes if there are other changes that starts/ends at this line.
+       * So we should check all other changes and shift them if necessary.
+       */
+      private void moveChangesAfterInsertion(@NotNull TextMergeChange change,
+                                             int newOutputStartLine,
+                                             int newOutputEndLine) {
+        LOG.assertTrue(myCurrentMergeCommand != null);
+        if (change.getStartLine(ThreeSide.BASE) != newOutputStartLine ||
+            change.getEndLine(ThreeSide.BASE) != newOutputEndLine) {
+          change.setStartLine(ThreeSide.BASE, newOutputStartLine);
+          change.setEndLine(ThreeSide.BASE, newOutputEndLine);
+          reinstallHighlighter(change);
+        }
+
+        boolean beforeChange = true;
+        for (TextMergeChange otherChange : getAllChanges()) {
+          int startLine = otherChange.getStartLine(ThreeSide.BASE);
+          int endLine = otherChange.getEndLine(ThreeSide.BASE);
+          if (endLine < newOutputStartLine) continue;
+          if (startLine > newOutputEndLine) break;
+          if (otherChange == change) {
+            beforeChange = false;
+            continue;
+          }
+
+          int newStartLine = beforeChange ? Math.min(startLine, newOutputStartLine) : Math.max(startLine, newOutputEndLine);
+          int newEndLine = beforeChange ? Math.min(endLine, newOutputStartLine) : Math.max(endLine, newOutputEndLine);
+          if (startLine != newStartLine || endLine != newEndLine) {
+            otherChange.setStartLine(ThreeSide.BASE, newStartLine);
+            otherChange.setEndLine(ThreeSide.BASE, newEndLine);
+            reinstallHighlighter(otherChange);
+          }
+        }
+      }
+
+      /*
+       * Nearby changes could be affected as well (ex: by moveChangesAfterInsertion)
+       *
+       * null means all changes could be affected
+       */
+      @Nullable
+      private List<TextMergeChange> collectAffectedChanges(@Nullable List<TextMergeChange> directChanges) {
+        if (directChanges == null || directChanges.isEmpty()) return null;
+
+        List<TextMergeChange> result = new ArrayList<TextMergeChange>(directChanges.size());
+
+        int directIndex = 0;
+        int otherIndex = 0;
+        while (directIndex < directChanges.size() && otherIndex < myAllMergeChanges.size()) {
+          TextMergeChange directChange = directChanges.get(directIndex);
+          TextMergeChange otherChange = myAllMergeChanges.get(otherIndex);
+
+          if (directChange == otherChange) {
+            result.add(directChange);
+            otherIndex++;
+            continue;
+          }
+
+          int directStart = directChange.getStartLine(ThreeSide.BASE);
+          int directEnd = directChange.getEndLine(ThreeSide.BASE);
+          int otherStart = otherChange.getStartLine(ThreeSide.BASE);
+          int otherEnd = otherChange.getEndLine(ThreeSide.BASE);
+          if (otherEnd < directStart) {
+            otherIndex++;
+            continue;
+          }
+          if (otherStart > directEnd) {
+            directIndex++;
+            continue;
+          }
+
+          result.add(otherChange);
+          otherIndex++;
+        }
+
+        LOG.assertTrue(directChanges.size() <= result.size());
+        return result;
+      }
+
+      //
+      // Actions
+      //
+
+      private abstract class ApplySelectedChangesActionBase extends AnAction implements DumbAware {
+        private final boolean myShortcut;
+
+        public ApplySelectedChangesActionBase(boolean shortcut) {
+          myShortcut = shortcut;
+        }
+
+        @Override
+        public void update(@NotNull AnActionEvent e) {
+          if (myShortcut) {
+            // consume shortcut even if there are nothing to do - avoid calling some other action
+            e.getPresentation().setEnabledAndVisible(true);
+            return;
+          }
+
+          Presentation presentation = e.getPresentation();
+          Editor editor = e.getData(CommonDataKeys.EDITOR);
+
+          ThreeSide side = getEditorSide(editor);
+          if (side == null) {
+            presentation.setEnabledAndVisible(false);
+            return;
+          }
+
+          if (!isVisible(side)) {
+            presentation.setEnabledAndVisible(false);
+            return;
+          }
+
+          presentation.setVisible(true);
+          presentation.setEnabled(isSomeChangeSelected(side));
+        }
+
+        @Override
+        public void actionPerformed(@NotNull final AnActionEvent e) {
+          Editor editor = e.getData(CommonDataKeys.EDITOR);
+          final ThreeSide side = getEditorSide(editor);
+          if (editor == null || side == null) return;
+
+          final List<TextMergeChange> selectedChanges = getSelectedChanges(side);
+          if (selectedChanges.isEmpty()) return;
+
+          String title = e.getPresentation().getText() + " in merge";
+
+          getEditor(ThreeSide.BASE).getDocument().setInBulkUpdate(true);
+          try {
+            executeMergeCommand(title, selectedChanges, new Runnable() {
+              @Override
+              public void run() {
+                apply(side, selectedChanges);
+              }
+            });
+          }
+          finally {
+            getEditor(ThreeSide.BASE).getDocument().setInBulkUpdate(false);
+          }
+        }
+
+        private boolean isSomeChangeSelected(@NotNull ThreeSide side) {
+          EditorEx editor = getEditor(side);
+          List<Caret> carets = editor.getCaretModel().getAllCarets();
+          if (carets.size() != 1) return true;
+          Caret caret = carets.get(0);
+          if (caret.hasSelection()) return true;
+
+          int line = editor.getDocument().getLineNumber(editor.getExpectedCaretOffset());
+
+          List<TextMergeChange> changes = getAllChanges();
+          for (TextMergeChange change : changes) {
+            if (!isEnabled(change)) continue;
+            int line1 = change.getStartLine(side);
+            int line2 = change.getEndLine(side);
+
+            if (DiffUtil.isSelectedByLine(line, line1, line2)) return true;
+          }
+          return false;
+        }
+
+        @NotNull
+        @CalledInAwt
+        private List<TextMergeChange> getSelectedChanges(@NotNull ThreeSide side) {
+          final BitSet lines = DiffUtil.getSelectedLines(getEditor(side));
+          List<TextMergeChange> changes = getChanges();
+
+          List<TextMergeChange> affectedChanges = new ArrayList<TextMergeChange>();
+          for (TextMergeChange change : changes) {
+            if (!isEnabled(change)) continue;
+            int line1 = change.getStartLine(side);
+            int line2 = change.getEndLine(side);
+
+            if (DiffUtil.isSelectedByLine(lines, line1, line2)) {
+              affectedChanges.add(change);
+            }
+          }
+          return affectedChanges;
+        }
+
+        protected abstract boolean isVisible(@NotNull ThreeSide side);
+
+        protected abstract boolean isEnabled(@NotNull TextMergeChange change);
+
+        @CalledWithWriteLock
+        protected abstract void apply(@NotNull ThreeSide side, @NotNull List<TextMergeChange> changes);
+      }
+
+      private class IgnoreSelectedChangesAction extends ApplySelectedChangesActionBase {
+        @NotNull private final Side mySide;
+
+        public IgnoreSelectedChangesAction(@NotNull Side side, boolean shortcut) {
+          super(shortcut);
+          mySide = side;
+          EmptyAction.setupAction(this, mySide.select("Diff.IgnoreLeftSide", "Diff.IgnoreRightSide"), null);
+        }
+
+        @Override
+        protected boolean isVisible(@NotNull ThreeSide side) {
+          if (side == ThreeSide.BASE) return true;
+          return side == mySide.select(ThreeSide.LEFT, ThreeSide.RIGHT);
+        }
+
+        @Override
+        protected boolean isEnabled(@NotNull TextMergeChange change) {
+          return !change.isResolved(mySide);
+        }
+
+        @Override
+        protected void apply(@NotNull ThreeSide side, @NotNull List<TextMergeChange> changes) {
+          for (TextMergeChange change : changes) {
+            ignoreChange(change, mySide, false);
+          }
+        }
+      }
+
+      private class ApplySelectedChangesAction extends ApplySelectedChangesActionBase {
+        @NotNull private final Side mySide;
+
+        public ApplySelectedChangesAction(@NotNull Side side, boolean shortcut) {
+          super(shortcut);
+          mySide = side;
+          EmptyAction.setupAction(this, mySide.select("Diff.ApplyLeftSide", "Diff.ApplyRightSide"), null);
+        }
+
+        @Override
+        protected boolean isVisible(@NotNull ThreeSide side) {
+          if (side == ThreeSide.BASE) return true;
+          return side == mySide.select(ThreeSide.LEFT, ThreeSide.RIGHT);
+        }
+
+        @Override
+        protected boolean isEnabled(@NotNull TextMergeChange change) {
+          return !change.isResolved(mySide);
+        }
+
+        @Override
+        protected void apply(@NotNull ThreeSide side, @NotNull List<TextMergeChange> changes) {
+          for (int i = changes.size() - 1; i >= 0; i--) {
+            replaceChange(changes.get(i), mySide, false);
+          }
+        }
+      }
+
+      public abstract class ApplyNonConflictsActionBase extends DumbAwareAction {
+        public ApplyNonConflictsActionBase(@Nullable String text, @Nullable String description, @Nullable Icon icon) {
+          super(text, description, icon);
+        }
+
+        public void actionPerformed(AnActionEvent e) {
+          getEditor(ThreeSide.BASE).getDocument().setInBulkUpdate(true);
+          try {
+            executeMergeCommand("Apply Non Conflicted Changes", new Runnable() {
+              @Override
+              public void run() {
+                doPerform();
+              }
+            });
+          }
+          finally {
+            getEditor(ThreeSide.BASE).getDocument().setInBulkUpdate(false);
+          }
+
+          TextMergeChange firstConflict = getFirstUnresolvedChange(true, null);
+          if (firstConflict != null) doScrollToChange(firstConflict, true);
+        }
+
+        @CalledWithWriteLock
+        protected abstract void doPerform();
+      }
+
+      public class ApplyNonConflictsAction extends ApplyNonConflictsActionBase {
+        public ApplyNonConflictsAction() {
+          super(DiffBundle.message("merge.dialog.apply.all.non.conflicting.changes.action.name"), null, AllIcons.Diff.ApplyNotConflicts);
+        }
+
+        @Override
+        protected void doPerform() {
+          List<TextMergeChange> allChanges = ContainerUtil.newArrayList(getAllChanges());
+          for (TextMergeChange change : allChanges) {
+            if (change.isConflict()) continue;
+            if (change.isResolved()) continue;
+            Side masterSide = change.isChange(Side.LEFT) ? Side.LEFT : Side.RIGHT;
+            replaceChange(change, masterSide, false);
+          }
+        }
+
+        public void update(AnActionEvent e) {
+          e.getPresentation().setEnabled(getFirstUnresolvedChange(false, null) != null);
+        }
+      }
+
+      public class ApplySideNonConflictsAction extends ApplyNonConflictsActionBase {
+        @NotNull private final Side mySide;
+
+        public ApplySideNonConflictsAction(@NotNull Side side) {
+          super(side.select(DiffBundle.message("merge.dialog.apply.left.non.conflicting.changes.action.name"),
+                            DiffBundle.message("merge.dialog.apply.right.non.conflicting.changes.action.name")),
+                null,
+                side.select(AllIcons.Diff.ApplyNotConflictsLeft, AllIcons.Diff.ApplyNotConflictsRight));
+          mySide = side;
+        }
+
+        @Override
+        protected void doPerform() {
+          List<TextMergeChange> allChanges = ContainerUtil.newArrayList(getAllChanges());
+          for (TextMergeChange change : allChanges) {
+            if (change.isConflict()) continue;
+            if (change.isResolved(mySide)) continue;
+            if (!change.isChange(mySide)) continue;
+            replaceChange(change, mySide, false);
+          }
+        }
+
+        public void update(AnActionEvent e) {
+          e.getPresentation().setEnabled(getFirstUnresolvedChange(false, mySide) != null);
+        }
+      }
+
+      //
+      // Helpers
+      //
+
+      private class UndoRedoAction extends DumbAwareAction {
+        private final boolean myUndo;
+
+        public UndoRedoAction(boolean undo) {
+          myUndo = undo;
+        }
+
+        public void register() {
+          EmptyAction.setupAction(this, myUndo ? IdeActions.ACTION_UNDO : IdeActions.ACTION_REDO, myContentPanel);
+        }
+
+        @Override
+        public void update(AnActionEvent e) {
+          UndoManager undoManager = getUndoManager();
+          TextEditor textEditor = getTextEditor();
+
+          e.getPresentation().setEnabled(myUndo ? undoManager.isUndoAvailable(textEditor) : undoManager.isRedoAvailable(textEditor));
+        }
+
+        @Override
+        public void actionPerformed(AnActionEvent e) {
+          UndoManager undoManager = getUndoManager();
+          TextEditor textEditor = getTextEditor();
+
+          if (myUndo) {
+            undoManager.undo(textEditor);
+          }
+          else {
+            undoManager.redo(textEditor);
+          }
+        }
+
+        @NotNull
+        private TextEditor getTextEditor() {
+          EditorEx editor = getEditor(ThreeSide.BASE);
+          return TextEditorProvider.getInstance().getTextEditor(editor);
+        }
+
+        @NotNull
+        private UndoManager getUndoManager() {
+          Project project = getProject();
+          return project != null ? UndoManager.getInstance(project) : UndoManager.getGlobalInstance();
+        }
+      }
+
+      private class MyDividerPaintable implements DiffDividerDrawUtil.DividerPaintable {
+        @NotNull private final Side mySide;
+
+        public MyDividerPaintable(@NotNull Side side) {
+          mySide = side;
+        }
+
+        @Override
+        public void process(@NotNull Handler handler) {
+          ThreeSide left = mySide.select(ThreeSide.LEFT, ThreeSide.BASE);
+          ThreeSide right = mySide.select(ThreeSide.BASE, ThreeSide.RIGHT);
+          for (TextMergeChange mergeChange : myAllMergeChanges) {
+            if (!mergeChange.isChange(mySide)) continue;
+            Color color = mergeChange.getDiffType().getColor(getEditor(ThreeSide.BASE));
+            boolean isResolved = mergeChange.isResolved(mySide);
+            if (!handler.process(mergeChange.getStartLine(left), mergeChange.getEndLine(left),
+                                 mergeChange.getStartLine(right), mergeChange.getEndLine(right),
+                                 color, isResolved)) {
+              return;
+            }
+          }
+        }
+      }
+
+      public class ModifierProvider extends KeyboardModifierListener {
+        public void init() {
+          init(myPanel, TextMergeViewer.this);
+        }
+
+        @Override
+        public void onModifiersChanged() {
+          for (TextMergeChange change : myAllMergeChanges) {
+            change.updateGutterActions(false);
+          }
+        }
+      }
+
+    }
+  }
+}
diff --git a/platform/diff-impl/src/com/intellij/diff/requests/BinaryMergeRequestImpl.java b/platform/diff-impl/src/com/intellij/diff/requests/BinaryMergeRequestImpl.java
new file mode 100644 (file)
index 0000000..97f2e20
--- /dev/null
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2000-2015 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.diff.requests;
+
+import com.intellij.CommonBundle;
+import com.intellij.diff.contents.DiffContent;
+import com.intellij.diff.contents.FileContent;
+import com.intellij.diff.merge.ThreesideMergeRequest;
+import com.intellij.diff.merge.MergeResult;
+import com.intellij.diff.util.ThreeSide;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.util.Consumer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.List;
+
+public class BinaryMergeRequestImpl extends ThreesideMergeRequest {
+  private static final Logger LOG = Logger.getInstance(BinaryMergeRequestImpl.class);
+
+  @NotNull private final FileContent myFile;
+  @NotNull private final List<DiffContent> myContents;
+
+  @NotNull private final List<byte[]> myByteContents;
+  @NotNull private final byte[] myOriginalContent;
+
+  @Nullable private final String myTitle;
+  @NotNull private final List<String> myTitles;
+
+  @Nullable private final Consumer<MergeResult> myApplyCallback;
+
+  public BinaryMergeRequestImpl(@NotNull FileContent file,
+                                @NotNull byte[] originalContent,
+                                @NotNull List<DiffContent> contents,
+                                @NotNull List<byte[]> byteContents,
+                                @Nullable String title,
+                                @NotNull List<String> contentTitles,
+                                @Nullable Consumer<MergeResult> applyCallback) {
+    assert byteContents.size() == 3;
+    assert contents.size() == 3;
+    assert contentTitles.size() == 3;
+
+    myFile = file;
+    myOriginalContent = originalContent;
+
+    myByteContents = byteContents;
+    myContents = contents;
+    myTitle = title;
+    myTitles = contentTitles;
+
+    myApplyCallback = applyCallback;
+  }
+
+  @NotNull
+  @Override
+  public FileContent getOutputContent() {
+    return myFile;
+  }
+
+  @NotNull
+  @Override
+  public List<DiffContent> getContents() {
+    return myContents;
+  }
+
+  @Nullable
+  @Override
+  public String getTitle() {
+    return myTitle;
+  }
+
+  @NotNull
+  @Override
+  public List<String> getContentTitles() {
+    return myTitles;
+  }
+
+  @Override
+  public void applyResult(@NotNull MergeResult result) {
+    final byte[] applyContent;
+    switch (result) {
+      case CANCEL:
+        applyContent = myOriginalContent;
+        break;
+      case LEFT:
+        applyContent = ThreeSide.LEFT.select(myByteContents);
+        break;
+      case RIGHT:
+        applyContent = ThreeSide.RIGHT.select(myByteContents);
+        break;
+      case RESOLVED:
+        applyContent = null;
+        break;
+      default:
+        throw new IllegalArgumentException(result.toString());
+    }
+
+    if (applyContent != null) {
+      new WriteCommandAction.Simple(null) {
+        @Override
+        protected void run() throws Throwable {
+          try {
+            myFile.getFile().setBinaryContent(applyContent);
+          }
+          catch (IOException e) {
+            LOG.error(e);
+            Messages.showErrorDialog((Project)null, "Can't apply result", CommonBundle.getErrorTitle());
+          }
+        }
+      }.execute();
+    }
+
+    if (myApplyCallback != null) myApplyCallback.consume(result);
+  }
+}
diff --git a/platform/diff-impl/src/com/intellij/diff/requests/TextMergeRequestImpl.java b/platform/diff-impl/src/com/intellij/diff/requests/TextMergeRequestImpl.java
new file mode 100644 (file)
index 0000000..08a3a16
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2000-2015 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.diff.requests;
+
+import com.intellij.diff.contents.DocumentContent;
+import com.intellij.diff.merge.MergeResult;
+import com.intellij.diff.merge.TextMergeRequest;
+import com.intellij.diff.util.ThreeSide;
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.Consumer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+public class TextMergeRequestImpl extends TextMergeRequest {
+  @Nullable private final Project myProject;
+  @NotNull private final DocumentContent myOutput;
+  @NotNull private final List<DocumentContent> myContents;
+
+  @NotNull private final CharSequence myOriginalContent;
+
+  @Nullable private final String myTitle;
+  @NotNull private final List<String> myTitles;
+
+  @Nullable private final Consumer<MergeResult> myApplyCallback;
+
+  public TextMergeRequestImpl(@Nullable Project project,
+                              @NotNull DocumentContent output,
+                              @NotNull CharSequence originalContent,
+                              @NotNull List<DocumentContent> contents,
+                              @Nullable String title,
+                              @NotNull List<String> contentTitles,
+                              @Nullable Consumer<MergeResult> applyCallback) {
+    assert contents.size() == 3;
+    assert contentTitles.size() == 3;
+    myProject = project;
+
+    myOutput = output;
+    myOriginalContent = originalContent;
+
+    myContents = contents;
+    myTitles = contentTitles;
+    myTitle = title;
+
+    myApplyCallback = applyCallback;
+  }
+
+  @NotNull
+  @Override
+  public DocumentContent getOutputContent() {
+    return myOutput;
+  }
+
+  @NotNull
+  @Override
+  public List<DocumentContent> getContents() {
+    return myContents;
+  }
+
+  @Nullable
+  @Override
+  public String getTitle() {
+    return myTitle;
+  }
+
+  @NotNull
+  @Override
+  public List<String> getContentTitles() {
+    return myTitles;
+  }
+
+  @Override
+  public void applyResult(@NotNull MergeResult result) {
+    final CharSequence applyContent;
+    switch (result) {
+      case CANCEL:
+        applyContent = myOriginalContent;
+        break;
+      case LEFT:
+        CharSequence leftContent = ThreeSide.LEFT.select(getContents()).getDocument().getImmutableCharSequence();
+        applyContent = StringUtil.convertLineSeparators(leftContent.toString());
+        break;
+      case RIGHT:
+        CharSequence rightContent = ThreeSide.RIGHT.select(getContents()).getDocument().getImmutableCharSequence();
+        applyContent = StringUtil.convertLineSeparators(rightContent.toString());
+        break;
+      case RESOLVED:
+        applyContent = null;
+        break;
+      default:
+        throw new IllegalArgumentException(result.toString());
+    }
+
+    if (applyContent != null) {
+      new WriteCommandAction.Simple(myProject) {
+        @Override
+        protected void run() throws Throwable {
+          myOutput.getDocument().setText(applyContent);
+        }
+      }.execute();
+    }
+
+    if (myApplyCallback != null) myApplyCallback.consume(result);
+  }
+}
index 61589814bc43710ab28bf166ccf2c63b24b81a4f..7755da580518a8b4ba29955346a663c4ac517a7d 100644 (file)
@@ -84,7 +84,7 @@
           </component>
         </children>
       </grid>
-      <grid id="d5bc7" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+      <grid id="d5bc7" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
         <margin top="0" left="0" bottom="0" right="0"/>
         <constraints>
           <grid row="3" column="0" row-span="1" col-span="4" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
           </component>
           <hspacer id="3caba">
             <constraints>
-              <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+              <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
             </constraints>
           </hspacer>
+          <component id="5eb41" class="javax.swing.JCheckBox" binding="myRespectExitCodeCheckbox">
+            <constraints>
+              <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <margin top="4" left="2" bottom="0" right="2"/>
+              <text value="Trust process exit code"/>
+            </properties>
+          </component>
         </children>
       </grid>
       <component id="7e9c5" class="javax.swing.JLabel">
index 4ca97f64a09391c36cf5083269bcca8f5c0ab9b9..dde66d7425156c4ac95c315697e7bb8367d6b979 100644 (file)
@@ -50,6 +50,7 @@ public class ExternalDiffSettingsPanel {
   private TextFieldWithBrowseButton myMergePath;
   private JTextField myMergeParameters;
   private JLabel myDescriptionLabel;
+  private JCheckBox myRespectExitCodeCheckbox;
 
   public ExternalDiffSettingsPanel() {
     myDescriptionLabel.setText(DESCRIPTION_TEXT);
@@ -87,6 +88,7 @@ public class ExternalDiffSettingsPanel {
     if (mySettings.isMergeEnabled() != myMergeEnabled.isSelected()) return true;
     if (!mySettings.getMergeExePath().equals(myMergePath.getText())) return true;
     if (!mySettings.getMergeParameters().equals(myMergeParameters.getText())) return true;
+    if (mySettings.isMergeTrustExitCode() != myRespectExitCodeCheckbox.isSelected()) return true;
 
     return false;
   }
@@ -100,6 +102,7 @@ public class ExternalDiffSettingsPanel {
     mySettings.setMergeEnabled(myMergeEnabled.isSelected());
     mySettings.setMergeExePath(myMergePath.getText());
     mySettings.setMergeParameters(myMergeParameters.getText());
+    mySettings.setMergeTrustExitCode(myRespectExitCodeCheckbox.isSelected());
   }
 
   public void reset() {
@@ -111,6 +114,7 @@ public class ExternalDiffSettingsPanel {
     myMergePath.setText(mySettings.getMergeExePath());
     myMergeEnabled.setSelected(mySettings.isMergeEnabled());
     myMergeParameters.setText(mySettings.getMergeParameters());
+    myRespectExitCodeCheckbox.setSelected(mySettings.isMergeTrustExitCode());
 
     updateEnabledEffect();
   }
@@ -122,6 +126,7 @@ public class ExternalDiffSettingsPanel {
 
     UIUtil.setEnabled(myMergePath, myMergeEnabled.isSelected(), true);
     UIUtil.setEnabled(myMergeParameters, myMergeEnabled.isSelected(), true);
+    UIUtil.setEnabled(myRespectExitCodeCheckbox, myMergeEnabled.isSelected(), true);
   }
 
   private void createUIComponents() {
index 49d9391aa2a9114d221f48e486859d6e25702b66..7a4bfe2cc032df11ccc645523989c795bb95f0be 100644 (file)
@@ -91,6 +91,7 @@ public class ExternalDiffSettings implements PersistentStateComponent<ExternalDi
     @Nullable public Boolean MERGE_ENABLED = null;
     @Nullable public String MERGE_EXE_PATH = null;
     @Nullable public String MERGE_PARAMETERS = null;
+    public boolean MERGE_TRUST_EXIT_CODE = false;
   }
 
   public boolean isDiffEnabled() {
@@ -158,4 +159,12 @@ public class ExternalDiffSettings implements PersistentStateComponent<ExternalDi
     myState.MERGE_PARAMETERS = path;
     setProperty(DiffManagerImpl.MERGE_TOOL_PARAMETERS, path);
   }
+
+  public boolean isMergeTrustExitCode() {
+    return myState.MERGE_TRUST_EXIT_CODE;
+  }
+
+  public void setMergeTrustExitCode(boolean value) {
+    myState.MERGE_TRUST_EXIT_CODE = value;
+  }
 }
index cc7ec350f0679003bba7ef3ad0c19ca3e14ff27b..0d075f734268278519cae0a7a686aac8bd395476 100644 (file)
 package com.intellij.diff.tools.external;
 
 import com.intellij.diff.contents.*;
+import com.intellij.diff.merge.MergeResult;
+import com.intellij.diff.merge.ThreesideMergeRequest;
 import com.intellij.execution.ExecutionException;
 import com.intellij.execution.configurations.CommandLineTokenizer;
 import com.intellij.execution.configurations.GeneralCommandLine;
 import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Ref;
 import com.intellij.openapi.util.io.FileUtil;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.util.LineSeparator;
 import com.intellij.util.PathUtil;
+import com.intellij.util.TimeoutUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -36,6 +46,8 @@ import java.io.IOException;
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
 
 public class ExternalDiffToolUtil {
   public static boolean canCreateFile(@NotNull DiffContent content) {
@@ -59,31 +71,12 @@ public class ExternalDiffToolUtil {
         return file.getPath();
       }
 
-      byte[] bytes = file.contentsToByteArray();
-      return createFile(bytes, getFileName(title, windowTitle, content.getContentType())).getPath();
+      String tempFileName = getFileName(title, windowTitle, content.getContentType());
+      return createTempFile(file, tempFileName).getPath();
     }
     else if (content instanceof DocumentContent) {
-      final DocumentContent documentContent = (DocumentContent)content;
-      FileDocumentManager.getInstance().saveDocument(documentContent.getDocument());
-
-      LineSeparator separator = documentContent.getLineSeparator();
-      if (separator == null) separator = LineSeparator.getSystemLineSeparator();
-
-      Charset charset = documentContent.getCharset();
-      if (charset == null) charset = Charset.defaultCharset();
-
-      String contentData = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
-        @Override
-        public String compute() {
-          return documentContent.getDocument().getText();
-        }
-      });
-      if (separator != LineSeparator.LF) {
-        contentData = StringUtil.convertLineSeparators(contentData, separator.getSeparatorString());
-      }
-
-      byte[] bytes = contentData.getBytes(charset);
-      return createFile(bytes, getFileName(title, windowTitle, content.getContentType())).getPath();
+      String tempFileName = getFileName(title, windowTitle, content.getContentType());
+      return createTempFile(((DocumentContent)content), tempFileName).getPath();
     }
     else if (content instanceof DirectoryContent) {
       VirtualFile file = ((DirectoryContent)content).getFile();
@@ -97,6 +90,56 @@ public class ExternalDiffToolUtil {
     throw new IllegalArgumentException(content.toString());
   }
 
+  @NotNull
+  private static File createTempFile(@NotNull final DocumentContent content, @NotNull String tempFileName) throws IOException {
+    FileDocumentManager.getInstance().saveDocument(content.getDocument());
+
+    LineSeparator separator = content.getLineSeparator();
+    if (separator == null) separator = LineSeparator.getSystemLineSeparator();
+
+    Charset charset = content.getCharset();
+    if (charset == null) charset = Charset.defaultCharset();
+
+    String contentData = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
+      @Override
+      public String compute() {
+        return content.getDocument().getText();
+      }
+    });
+    if (separator != LineSeparator.LF) {
+      contentData = StringUtil.convertLineSeparators(contentData, separator.getSeparatorString());
+    }
+
+    byte[] bytes = contentData.getBytes(charset);
+    return createFile(bytes, tempFileName);
+  }
+
+  @NotNull
+  private static File createTempFile(@NotNull VirtualFile file, @NotNull String tempFileName) throws IOException {
+    byte[] bytes = file.contentsToByteArray();
+    return createFile(bytes, tempFileName);
+  }
+
+  @NotNull
+  public static OutputFile createOutputFile(@NotNull DiffContent content, @Nullable String windowTitle) throws IOException {
+    if (content instanceof FileContent) {
+      FileContent fileContent = (FileContent)content;
+      if (fileContent.getFile().isInLocalFileSystem()) {
+        return new LocalOutputFile(fileContent.getFile());
+      }
+
+      String tempFileName = getFileName(null, windowTitle, content.getContentType());
+      File tempFile = createTempFile(fileContent.getFile(), tempFileName);
+      return new NonLocalOutputFile(fileContent.getFile(), tempFile);
+    }
+    else if (content instanceof DocumentContent) {
+      String tempFileName = getFileName(null, windowTitle, content.getContentType());
+      File tempFile = createTempFile(((DocumentContent)content), tempFileName);
+      return new DocumentOutputFile(((DocumentContent)content).getDocument(), ((DocumentContent)content).getCharset(), tempFile);
+    }
+    throw new IllegalArgumentException(content.toString());
+  }
+
   @NotNull
   public static String getFileName(@Nullable String title, @Nullable String windowTitle, @Nullable FileType fileType) {
     String prefix = "";
@@ -120,7 +163,7 @@ public class ExternalDiffToolUtil {
   }
 
   public static void execute(@NotNull ExternalDiffSettings settings,
-                             @NotNull List<DiffContent> contents,
+                             @NotNull List<? extends DiffContent> contents,
                              @NotNull List<String> titles,
                              @Nullable String windowTitle)
     throws IOException, ExecutionException {
@@ -162,4 +205,194 @@ public class ExternalDiffToolUtil {
     commandLine.addParameters(args);
     commandLine.createProcess();
   }
+
+  public static void executeMerge(@Nullable Project project,
+                                  @NotNull ExternalDiffSettings settings,
+                                  @NotNull ThreesideMergeRequest request)
+    throws IOException, ExecutionException {
+    boolean success = false;
+    try {
+      DiffContent outputContent = request.getOutputContent();
+      List<? extends DiffContent> contents = request.getContents();
+      List<String> titles = request.getContentTitles();
+      String windowTitle = request.getTitle();
+
+      assert contents.size() == 3;
+      assert titles.size() == contents.size();
+
+      List<String> files = new ArrayList<String>();
+      for (int i = 0; i < contents.size(); i++) {
+        files.add(createFile(contents.get(i), titles.get(i), windowTitle));
+      }
+
+      OutputFile outputFile = createOutputFile(outputContent, windowTitle);
+
+      CommandLineTokenizer parameterTokenizer = new CommandLineTokenizer(settings.getMergeParameters(), true);
+
+      List<String> args = new ArrayList<String>();
+      while (parameterTokenizer.hasMoreTokens()) {
+        String arg = parameterTokenizer.nextToken();
+        if ("%1".equals(arg)) {
+          args.add(files.get(0));
+        }
+        else if ("%2".equals(arg)) {
+          args.add(files.get(2));
+        }
+        else if ("%3".equals(arg)) {
+          args.add(files.get(1));
+        }
+        else if ("%4".equals(arg)) {
+          args.add(outputFile.getPath());
+        }
+        else {
+          args.add(arg);
+        }
+      }
+
+      GeneralCommandLine commandLine = new GeneralCommandLine();
+      commandLine.setExePath(settings.getMergeExePath());
+
+      commandLine.addParameters(args);
+      final Process process = commandLine.createProcess();
+
+      if (settings.isMergeTrustExitCode()) {
+        final Ref<Boolean> resultRef = new Ref<Boolean>();
+
+        ProgressManager.getInstance().run(new Task.Modal(project, "Waiting for external tool", true) {
+          @Override
+          public void run(@NotNull ProgressIndicator indicator) {
+            final Semaphore semaphore = new Semaphore(0);
+
+            final Thread waiter = new Thread("external process waiter") {
+              @Override
+              public void run() {
+                try {
+                  resultRef.set(process.waitFor() == 0);
+                }
+                catch (InterruptedException ignore) {
+                }
+                finally {
+                  semaphore.release();
+                }
+              }
+            };
+            waiter.start();
+
+            try {
+              while (true) {
+                indicator.checkCanceled();
+                if (semaphore.tryAcquire(200, TimeUnit.MILLISECONDS)) break;
+              }
+            }
+            catch (InterruptedException ignore) {
+            }
+            finally {
+              waiter.interrupt();
+            }
+          }
+        });
+
+        success = resultRef.get() == Boolean.TRUE;
+      }
+      else {
+        ProgressManager.getInstance().run(new Task.Modal(project, "Launching external tool", false) {
+          @Override
+          public void run(@NotNull ProgressIndicator indicator) {
+            indicator.setIndeterminate(true);
+            TimeoutUtil.sleep(1000);
+          }
+        });
+
+        success = Messages.showYesNoDialog(project,
+                                           "Press \"Mark as Resolved\" when you finish resolving conflicts in the external tool",
+                                           "Merge In External Tool", "Mark as Resolved", "Revert", null) == Messages.YES;
+      }
+
+      if (success) outputFile.finish();
+    }
+    finally {
+      request.applyResult(success ? MergeResult.RESOLVED : MergeResult.CANCEL);
+    }
+  }
+
+  //
+  // Helpers
+  //
+
+  private interface OutputFile {
+    @NotNull
+    String getPath();
+
+    void finish() throws IOException;
+  }
+
+  private static class LocalOutputFile implements OutputFile {
+    @NotNull private final VirtualFile myFile;
+
+    public LocalOutputFile(@NotNull VirtualFile file) {
+      myFile = file;
+    }
+
+    @NotNull
+    @Override
+    public String getPath() {
+      return myFile.getPath();
+    }
+
+    @Override
+    public void finish() {
+      myFile.refresh(false, false);
+    }
+  }
+
+  private static class NonLocalOutputFile implements OutputFile {
+    @NotNull private final VirtualFile myFile;
+    @NotNull private final File myLocalFile;
+
+    public NonLocalOutputFile(@NotNull VirtualFile file, @NotNull File localFile) {
+      myFile = file;
+      myLocalFile = localFile;
+    }
+
+    @NotNull
+    @Override
+    public String getPath() {
+      return myLocalFile.getPath();
+    }
+
+    @Override
+    public void finish() throws IOException {
+      myFile.setBinaryContent(FileUtil.loadFileBytes(myLocalFile));
+    }
+  }
+
+  private static class DocumentOutputFile implements OutputFile {
+    @NotNull private final Document myDocument;
+    @NotNull private final File myLocalFile;
+    @NotNull private final Charset myCharset;
+
+    public DocumentOutputFile(@NotNull Document document, @Nullable Charset charset, @NotNull File localFile) {
+      myDocument = document;
+      myLocalFile = localFile;
+      // TODO: potentially dangerous operation - we're using default charset
+      myCharset = charset != null ? charset : Charset.defaultCharset();
+    }
+
+    @NotNull
+    @Override
+    public String getPath() {
+      return myLocalFile.getPath();
+    }
+
+    @Override
+    public void finish() throws IOException {
+      final String content = StringUtil.convertLineSeparators(FileUtil.loadFile(myLocalFile, myCharset));
+      ApplicationManager.getApplication().runWriteAction(new Runnable() {
+        @Override
+        public void run() {
+          myDocument.setText(content);
+        }
+      });
+    }
+  }
 }
diff --git a/platform/diff-impl/src/com/intellij/diff/tools/external/ExternalMergeTool.java b/platform/diff-impl/src/com/intellij/diff/tools/external/ExternalMergeTool.java
new file mode 100644 (file)
index 0000000..c47ee15
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2000-2015 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.diff.tools.external;
+
+import com.intellij.diff.DiffManagerEx;
+import com.intellij.diff.contents.DiffContent;
+import com.intellij.diff.contents.DocumentContent;
+import com.intellij.diff.contents.FileContent;
+import com.intellij.diff.merge.MergeRequest;
+import com.intellij.diff.merge.ThreesideMergeRequest;
+import com.intellij.execution.ExecutionException;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ExternalMergeTool {
+  public static final Logger LOG = Logger.getInstance(ExternalMergeTool.class);
+
+  public static boolean isDefault() {
+    return ExternalDiffSettings.getInstance().isMergeEnabled();
+  }
+
+  public static boolean isEnabled() {
+    return ExternalDiffSettings.getInstance().isMergeEnabled();
+  }
+
+  public static void show(@Nullable final Project project,
+                          @NotNull final MergeRequest request) {
+    try {
+      if (canShow(request)) {
+        showRequest(project, request);
+      }
+      else {
+        DiffManagerEx.getInstance().showMergeBuiltin(project, request);
+      }
+    }
+    catch (ProcessCanceledException ignore) {
+    }
+    catch (Throwable e) {
+      LOG.error(e);
+      Messages.showErrorDialog(project, e.getMessage(), "Can't Show Merge In External Tool");
+    }
+  }
+
+  public static void showRequest(@Nullable Project project, @NotNull MergeRequest request)
+    throws ExecutionException, IOException {
+    ExternalDiffSettings settings = ExternalDiffSettings.getInstance();
+
+    ExternalDiffToolUtil.executeMerge(project, settings, (ThreesideMergeRequest)request);
+  }
+
+  public static boolean canShow(@NotNull MergeRequest request) {
+    if (request instanceof ThreesideMergeRequest) {
+      DiffContent outputContent = ((ThreesideMergeRequest)request).getOutputContent();
+      if (!canProcessOutputContent(outputContent)) return false;
+
+      List<? extends DiffContent> contents = ((ThreesideMergeRequest)request).getContents();
+      if (contents.size() != 3) return false;
+      for (DiffContent content : contents) {
+        if (!ExternalDiffToolUtil.canCreateFile(content)) return false;
+      }
+      return true;
+    }
+    return false;
+  }
+
+  private static boolean canProcessOutputContent(@NotNull DiffContent content) {
+    if (content instanceof DocumentContent) return true;
+    if (content instanceof FileContent && ((FileContent)content).getFile().isInLocalFileSystem()) return true;
+    return false;
+  }
+}
index 0b70ee98736850638c64532fc9191afc9a99e0b3..664f93ce8744c505d6c3bd6810ff892e73907fb0 100644 (file)
@@ -257,13 +257,17 @@ public class UnifiedDiffChange {
         return new DumbAwareAction() {
           @Override
           public void actionPerformed(AnActionEvent e) {
+            if (myViewer.isStateIsOutOfDate()) return;
+            if (!myViewer.isEditable(sourceSide.other(), true)) return;
+
             final Project project = e.getProject();
             final Document document = myViewer.getDocument(sourceSide.other());
 
             DiffUtil.executeWriteCommand(document, project, "Replace change", new Runnable() {
               @Override
               public void run() {
-                myViewer.applyChange(UnifiedDiffChange.this, sourceSide);
+                myViewer.replaceChange(UnifiedDiffChange.this, sourceSide);
+                myViewer.scheduleRediff();
               }
             });
             // applyChange() will schedule rediff, but we want to try to do it in sync
index b2770697660b68b8d3e74f66f76fe121a970d7c8..87b8f6ab111d472642169fb1a3f4e4326deea539 100644 (file)
@@ -30,10 +30,9 @@ import com.intellij.diff.tools.util.base.*;
 import com.intellij.diff.tools.util.side.TwosideTextDiffViewer;
 import com.intellij.diff.util.*;
 import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
+import com.intellij.icons.AllIcons;
 import com.intellij.openapi.Disposable;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.CommonDataKeys;
-import com.intellij.openapi.actionSystem.Separator;
+import com.intellij.openapi.actionSystem.*;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.command.undo.UndoManager;
 import com.intellij.openapi.diagnostic.Logger;
@@ -50,6 +49,7 @@ import com.intellij.openapi.fileEditor.OpenFileDescriptor;
 import com.intellij.openapi.fileTypes.FileType;
 import com.intellij.openapi.progress.ProcessCanceledException;
 import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.DumbAware;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Computable;
 import com.intellij.openapi.util.Pair;
@@ -60,10 +60,7 @@ import gnu.trove.TIntFunction;
 import org.jetbrains.annotations.*;
 
 import javax.swing.*;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
+import java.util.*;
 
 import static com.intellij.diff.util.DiffUtil.getLineCount;
 
@@ -124,6 +121,11 @@ public class UnifiedDiffViewer extends ListenerDiffViewerBase {
     new MyOpenInEditorWithMouseAction().register(getEditors());
 
     TextDiffViewerUtil.checkDifferentDocuments(myRequest);
+
+    DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.LEFT, true), myPanel);
+    DiffUtil.registerAction(new AppendSelectedChangesAction(Side.LEFT, true), myPanel);
+    DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.RIGHT, true), myPanel);
+    DiffUtil.registerAction(new AppendSelectedChangesAction(Side.RIGHT, true), myPanel);
   }
 
   @Override
@@ -208,7 +210,19 @@ public class UnifiedDiffViewer extends ListenerDiffViewerBase {
 
   @NotNull
   protected List<AnAction> createEditorPopupActions() {
-    return TextDiffViewerUtil.createEditorPopupActions();
+    List<AnAction> group = new ArrayList<AnAction>();
+
+    group.add(new ReplaceSelectedChangesAction(Side.LEFT, false));
+    group.add(new AppendSelectedChangesAction(Side.LEFT, false));
+    group.add(new ReplaceSelectedChangesAction(Side.RIGHT, false));
+    group.add(new AppendSelectedChangesAction(Side.RIGHT, false));
+    group.add(new RevertSelectedChangesAction(Side.LEFT));
+    group.add(new RevertSelectedChangesAction(Side.RIGHT));
+    group.add(Separator.getInstance());
+
+    group.addAll(TextDiffViewerUtil.createEditorPopupActions());
+
+    return group;
   }
 
   @CalledInAwt
@@ -621,26 +635,163 @@ public class UnifiedDiffViewer extends ListenerDiffViewerBase {
     scheduleRediff();
   }
 
-  @CalledWithWriteLock
-  public void applyChange(@NotNull UnifiedDiffChange change, @NotNull Side sourceSide) {
-    if (myStateIsOutOfDate || myChangedBlockData == null) return;
+  //
+  // Modification operations
+  //
+
+  private abstract class ApplySelectedChangesActionBase extends AnAction implements DumbAware {
+    @NotNull protected final Side myModifiedSide;
+    private final boolean myShortcut;
+
+    public ApplySelectedChangesActionBase(@NotNull Side modifiedSide, boolean shortcut) {
+      myModifiedSide = modifiedSide;
+      myShortcut = shortcut;
+    }
+
+    @Override
+    public void update(@NotNull AnActionEvent e) {
+      if (myShortcut) {
+        // consume shortcut even if there are nothing to do - avoid calling some other action
+        e.getPresentation().setEnabledAndVisible(true);
+        return;
+      }
+
+      Editor editor = e.getData(CommonDataKeys.EDITOR);
+      if (editor != getEditor()) {
+        e.getPresentation().setEnabledAndVisible(false);
+        return;
+      }
+
+      if (!isEditable(myModifiedSide, true) || isStateIsOutOfDate()) {
+        e.getPresentation().setEnabledAndVisible(false);
+        return;
+      }
+
+      e.getPresentation().setVisible(true);
+      e.getPresentation().setEnabled(isSomeChangeSelected());
+    }
+
+    @Override
+    public void actionPerformed(@NotNull final AnActionEvent e) {
+      final List<UnifiedDiffChange> selectedChanges = getSelectedChanges();
+      if (selectedChanges.isEmpty()) return;
+
+      if (!isEditable(myModifiedSide, true)) return;
+      if (isStateIsOutOfDate()) return;
+
+      String title = e.getPresentation().getText() + " selected changes";
+      DiffUtil.executeWriteCommand(getDocument(myModifiedSide), e.getProject(), title, new Runnable() {
+        @Override
+        public void run() {
+          // state is invalidated during apply(), but changes are in reverse order, so they should not conflict with each other
+          apply(selectedChanges);
+          scheduleRediff();
+        }
+      });
+    }
+
+    protected boolean isSomeChangeSelected() {
+      if (myChangedBlockData == null) return false;
+      List<UnifiedDiffChange> changes = myChangedBlockData.getDiffChanges();
+      if (changes.isEmpty()) return false;
+
+      List<Caret> carets = getEditor().getCaretModel().getAllCarets();
+      if (carets.size() != 1) return true;
+      Caret caret = carets.get(0);
+      if (caret.hasSelection()) return true;
+      int line = getEditor().getDocument().getLineNumber(getEditor().getExpectedCaretOffset());
+
+      for (UnifiedDiffChange change : changes) {
+        if (DiffUtil.isSelectedByLine(line, change.getLine1(), change.getLine2())) return true;
+      }
+      return false;
+    }
+
+    @CalledWithWriteLock
+    protected abstract void apply(@NotNull List<UnifiedDiffChange> changes);
+  }
 
-    Side affectedSide = sourceSide.other();
-    if (!isEditable(affectedSide, true)) return;
+  private class ReplaceSelectedChangesAction extends ApplySelectedChangesActionBase {
+    public ReplaceSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
+      super(focusedSide.other(), shortcut);
+
+      setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.ApplyLeftSide", "Diff.ApplyRightSide")).getShortcutSet());
+      getTemplatePresentation().setText("Replace");
+      getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRight, AllIcons.Diff.Arrow));
+    }
+
+    @Override
+    protected void apply(@NotNull List<UnifiedDiffChange> changes) {
+      for (UnifiedDiffChange change : changes) {
+        replaceChange(change, myModifiedSide.other());
+      }
+    }
+  }
+
+  private class AppendSelectedChangesAction extends ApplySelectedChangesActionBase {
+    public AppendSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
+      super(focusedSide.other(), shortcut);
+
+      setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.AppendLeftSide", "Diff.AppendRightSide")).getShortcutSet());
+      getTemplatePresentation().setText("Insert");
+      getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRightDown, AllIcons.Diff.ArrowLeftDown));
+    }
+
+    @Override
+    protected void apply(@NotNull List<UnifiedDiffChange> changes) {
+      for (UnifiedDiffChange change : changes) {
+        appendChange(change, myModifiedSide.other());
+      }
+    }
+  }
+
+  private class RevertSelectedChangesAction extends ApplySelectedChangesActionBase {
+    public RevertSelectedChangesAction(@NotNull Side focusedSide) {
+      super(focusedSide, false);
+      getTemplatePresentation().setText("Revert");
+      getTemplatePresentation().setIcon(AllIcons.Diff.Remove);
+    }
+
+    @Override
+    protected void apply(@NotNull List<UnifiedDiffChange> changes) {
+      for (UnifiedDiffChange change : changes) {
+        replaceChange(change, myModifiedSide.other());
+      }
+    }
+  }
+
+  @CalledWithWriteLock
+  public void replaceChange(@NotNull UnifiedDiffChange change, @NotNull Side sourceSide) {
+    Side outputSide = sourceSide.other();
 
     Document document1 = getDocument(Side.LEFT);
     Document document2 = getDocument(Side.RIGHT);
 
     LineFragment lineFragment = change.getLineFragment();
 
-    DiffUtil.applyModification(affectedSide.select(document1, document2),
-                               affectedSide.getStartLine(lineFragment), affectedSide.getEndLine(lineFragment),
+    DiffUtil.applyModification(outputSide.select(document1, document2),
+                               outputSide.getStartLine(lineFragment), outputSide.getEndLine(lineFragment),
                                sourceSide.select(document1, document2),
                                sourceSide.getStartLine(lineFragment), sourceSide.getEndLine(lineFragment));
 
     // no need to mark myStateIsOutOfDate - it will be made by DocumentListener
     // TODO: we can apply change manually, without marking state out-of-date. But we'll have to schedule rediff anyway.
-    scheduleRediff();
+  }
+
+  @CalledWithWriteLock
+  public void appendChange(@NotNull UnifiedDiffChange change, @NotNull final Side sourceSide) {
+    Side outputSide = sourceSide.other();
+
+    Document document1 = getDocument(Side.LEFT);
+    Document document2 = getDocument(Side.RIGHT);
+
+    LineFragment lineFragment = change.getLineFragment();
+    if (sourceSide.getStartLine(lineFragment) == sourceSide.getEndLine(lineFragment)) return;
+
+    DiffUtil.applyModification(outputSide.select(document1, document2),
+                               outputSide.getEndLine(lineFragment), outputSide.getEndLine(lineFragment),
+                               sourceSide.select(document1, document2),
+                               sourceSide.getStartLine(lineFragment), sourceSide.getEndLine(lineFragment));
   }
 
   //
@@ -775,6 +926,26 @@ public class UnifiedDiffViewer extends ListenerDiffViewerBase {
     return null;
   }
 
+  @NotNull
+  @CalledInAwt
+  private List<UnifiedDiffChange> getSelectedChanges() {
+    if (myChangedBlockData == null) return Collections.emptyList();
+    final BitSet lines = DiffUtil.getSelectedLines(myEditor);
+    List<UnifiedDiffChange> changes = myChangedBlockData.getDiffChanges();
+
+    List<UnifiedDiffChange> affectedChanges = new ArrayList<UnifiedDiffChange>();
+    for (int i = changes.size() - 1; i >= 0; i--) {
+      UnifiedDiffChange change = changes.get(i);
+      int line1 = change.getLine1();
+      int line2 = change.getLine2();
+
+      if (DiffUtil.isSelectedByLine(lines, line1, line2)) {
+        affectedChanges.add(change);
+      }
+    }
+    return affectedChanges;
+  }
+
   @CalledInAwt
   @Nullable
   protected OpenFileDescriptor getOpenFileDescriptor(int offset) {
index 4781f04737c1bad345105ab370f5bec719dfd153..a2d0a598e95302815049f9a7f89d8ff731832556 100644 (file)
@@ -30,10 +30,7 @@ import com.intellij.diff.util.*;
 import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
 import com.intellij.icons.AllIcons;
 import com.intellij.openapi.Disposable;
-import com.intellij.openapi.actionSystem.AnAction;
-import com.intellij.openapi.actionSystem.AnActionEvent;
-import com.intellij.openapi.actionSystem.CommonDataKeys;
-import com.intellij.openapi.actionSystem.Separator;
+import com.intellij.openapi.actionSystem.*;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.diff.DiffNavigationContext;
@@ -84,6 +81,11 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
     myFoldingModel = new MyFoldingModel(getEditors(), this);
 
     myModifierProvider = new ModifierProvider();
+
+    DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.LEFT, true), myPanel);
+    DiffUtil.registerAction(new AppendSelectedChangesAction(Side.LEFT, true), myPanel);
+    DiffUtil.registerAction(new ReplaceSelectedChangesAction(Side.RIGHT, true), myPanel);
+    DiffUtil.registerAction(new AppendSelectedChangesAction(Side.RIGHT, true), myPanel);
   }
 
   @Override
@@ -137,9 +139,12 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
   protected List<AnAction> createEditorPopupActions() {
     List<AnAction> group = new ArrayList<AnAction>();
 
-    group.add(new ReplaceSelectedChangesAction());
-    group.add(new AppendSelectedChangesAction());
-    group.add(new RevertSelectedChangesAction());
+    group.add(new ReplaceSelectedChangesAction(Side.LEFT, false));
+    group.add(new AppendSelectedChangesAction(Side.LEFT, false));
+    group.add(new ReplaceSelectedChangesAction(Side.RIGHT, false));
+    group.add(new AppendSelectedChangesAction(Side.RIGHT, false));
+    group.add(new RevertSelectedChangesAction(Side.LEFT));
+    group.add(new RevertSelectedChangesAction(Side.RIGHT));
     group.add(Separator.getInstance());
 
     group.addAll(super.createEditorPopupActions());
@@ -491,18 +496,22 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
   //
 
   private abstract class ApplySelectedChangesActionBase extends AnAction implements DumbAware {
-    private final boolean myModifyOpposite;
+    @NotNull protected final Side myModifiedSide;
+    private final boolean myShortcut;
 
-    public ApplySelectedChangesActionBase(@Nullable String text,
-                                          @Nullable String description,
-                                          @Nullable Icon icon,
-                                          boolean modifyOpposite) {
-      super(text, description, icon);
-      myModifyOpposite = modifyOpposite;
+    public ApplySelectedChangesActionBase(@NotNull Side modifiedSide, boolean shortcut) {
+      myModifiedSide = modifiedSide;
+      myShortcut = shortcut;
     }
 
     @Override
     public void update(@NotNull AnActionEvent e) {
+      if (myShortcut) {
+        // consume shortcut even if there are nothing to do - avoid calling some other action
+        e.getPresentation().setEnabledAndVisible(true);
+        return;
+      }
+
       Editor editor = e.getData(CommonDataKeys.EDITOR);
       if (editor != getEditor1() && editor != getEditor2()) {
         e.getPresentation().setEnabledAndVisible(false);
@@ -510,30 +519,38 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
       }
 
       Side side = Side.fromLeft(editor == getEditor(Side.LEFT));
-      Editor modifiedEditor = getEditor(side.other(myModifyOpposite));
+      if (!isVisible(side)) {
+        e.getPresentation().setEnabledAndVisible(false);
+        return;
+      }
 
+      Editor modifiedEditor = getEditor(myModifiedSide);
       if (!DiffUtil.isEditable(modifiedEditor)) {
         e.getPresentation().setEnabledAndVisible(false);
         return;
       }
 
-      e.getPresentation().setIcon(getIcon(side));
       e.getPresentation().setVisible(true);
       e.getPresentation().setEnabled(isSomeChangeSelected(side));
     }
 
     @Override
     public void actionPerformed(@NotNull final AnActionEvent e) {
-      Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
+      Editor editor = e.getData(CommonDataKeys.EDITOR);
+      if (editor != getEditor1() && editor != getEditor2()) return;
+
       final Side side = Side.fromLeft(editor == getEditor(Side.LEFT));
       final List<SimpleDiffChange> selectedChanges = getSelectedChanges(side);
+      if (selectedChanges.isEmpty()) return;
+
+      Editor modifiedEditor = getEditor(myModifiedSide);
+      if (!DiffUtil.isEditable(modifiedEditor)) return;
 
-      Editor modifiedEditor = getEditor(side.other(myModifyOpposite));
       String title = e.getPresentation().getText() + " selected changes";
       DiffUtil.executeWriteCommand(modifiedEditor.getDocument(), e.getProject(), title, new Runnable() {
         @Override
         public void run() {
-          apply(side, selectedChanges);
+          apply(selectedChanges);
         }
       });
     }
@@ -554,66 +571,72 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
       return false;
     }
 
-    @NotNull
-    protected abstract Icon getIcon(@NotNull Side side);
+    protected abstract boolean isVisible(@NotNull Side side);
 
     @CalledWithWriteLock
-    protected abstract void apply(@NotNull Side side, @NotNull List<SimpleDiffChange> changes);
+    protected abstract void apply(@NotNull List<SimpleDiffChange> changes);
   }
 
   private class ReplaceSelectedChangesAction extends ApplySelectedChangesActionBase {
-    public ReplaceSelectedChangesAction() {
-      super("Replace", null, AllIcons.Diff.Arrow, true);
+    public ReplaceSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
+      super(focusedSide.other(), shortcut);
+
+      setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.ApplyLeftSide", "Diff.ApplyRightSide")).getShortcutSet());
+      getTemplatePresentation().setText("Replace");
+      getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRight, AllIcons.Diff.Arrow));
     }
 
-    @NotNull
     @Override
-    protected Icon getIcon(@NotNull Side side) {
-      return side.isLeft() ? AllIcons.Diff.ArrowRight : AllIcons.Diff.Arrow;
+    protected boolean isVisible(@NotNull Side side) {
+      return side == myModifiedSide.other();
     }
 
     @Override
-    protected void apply(@NotNull Side side, @NotNull List<SimpleDiffChange> changes) {
+    protected void apply(@NotNull List<SimpleDiffChange> changes) {
       for (SimpleDiffChange change : changes) {
-        replaceChange(change, side);
+        replaceChange(change, myModifiedSide.other());
       }
     }
   }
 
   private class AppendSelectedChangesAction extends ApplySelectedChangesActionBase {
-    public AppendSelectedChangesAction() {
-      super("Insert", null, AllIcons.Diff.ArrowLeftDown, true);
+    public AppendSelectedChangesAction(@NotNull Side focusedSide, boolean shortcut) {
+      super(focusedSide.other(), shortcut);
+
+      setShortcutSet(ActionManager.getInstance().getAction(focusedSide.select("Diff.AppendLeftSide", "Diff.AppendRightSide")).getShortcutSet());
+      getTemplatePresentation().setText("Insert");
+      getTemplatePresentation().setIcon(focusedSide.select(AllIcons.Diff.ArrowRightDown, AllIcons.Diff.ArrowLeftDown));
     }
 
-    @NotNull
     @Override
-    protected Icon getIcon(@NotNull Side side) {
-      return side.isLeft() ? AllIcons.Diff.ArrowRightDown : AllIcons.Diff.ArrowLeftDown;
+    protected boolean isVisible(@NotNull Side side) {
+      return side == myModifiedSide.other();
     }
 
     @Override
-    protected void apply(@NotNull Side side, @NotNull List<SimpleDiffChange> changes) {
+    protected void apply(@NotNull List<SimpleDiffChange> changes) {
       for (SimpleDiffChange change : changes) {
-        appendChange(change, side);
+        appendChange(change, myModifiedSide.other());
       }
     }
   }
 
   private class RevertSelectedChangesAction extends ApplySelectedChangesActionBase {
-    public RevertSelectedChangesAction() {
-      super("Revert", null, AllIcons.Diff.Remove, false);
+    public RevertSelectedChangesAction(@NotNull Side focusedSide) {
+      super(focusedSide, false);
+      getTemplatePresentation().setText("Revert");
+      getTemplatePresentation().setIcon(AllIcons.Diff.Remove);
     }
 
-    @NotNull
     @Override
-    protected Icon getIcon(@NotNull Side side) {
-      return AllIcons.Diff.Remove;
+    protected boolean isVisible(@NotNull Side side) {
+      return side == myModifiedSide;
     }
 
     @Override
-    protected void apply(@NotNull Side side, @NotNull List<SimpleDiffChange> changes) {
+    protected void apply(@NotNull List<SimpleDiffChange> changes) {
       for (SimpleDiffChange change : changes) {
-        replaceChange(change, side.other());
+        replaceChange(change, myModifiedSide.other());
       }
     }
   }
index b5a240ce5bf2edd36e71ff696f18ca5e9700c878..b25bc69c077a6ce4f97d5b5897d542efd40ae074 100644 (file)
  */
 package com.intellij.diff.tools.simple;
 
-import com.intellij.diff.comparison.ComparisonManager;
 import com.intellij.diff.comparison.ComparisonPolicy;
 import com.intellij.diff.fragments.MergeLineFragment;
-import com.intellij.diff.util.*;
+import com.intellij.diff.util.DiffDrawUtil;
+import com.intellij.diff.util.DiffUtil;
+import com.intellij.diff.util.TextDiffType;
+import com.intellij.diff.util.ThreeSide;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.ex.DocumentEx;
 import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.editor.markup.RangeHighlighter;
 import com.intellij.openapi.editor.markup.SeparatorPlacement;
-import com.intellij.openapi.util.text.StringUtil;
 import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
 
 import java.util.ArrayList;
 import java.util.List;
 
-public class SimpleThreesideDiffChange {
-  @NotNull private final MergeLineFragment myFragment;
+public class SimpleThreesideDiffChange extends ThreesideDiffChangeBase {
   @NotNull private final List<? extends EditorEx> myEditors;
-
-  @NotNull private ConflictType myType;
+  @NotNull private final MergeLineFragment myFragment;
 
   @NotNull private final List<RangeHighlighter> myHighlighters = new ArrayList<RangeHighlighter>();
 
@@ -46,10 +43,9 @@ public class SimpleThreesideDiffChange {
   public SimpleThreesideDiffChange(@NotNull MergeLineFragment fragment,
                                    @NotNull List<? extends EditorEx> editors,
                                    @NotNull ComparisonPolicy policy) {
-    myFragment = fragment;
+    super(fragment, editors, policy);
     myEditors = editors;
-
-    myType = calcType(fragment, editors, policy);
+    myFragment = fragment;
 
     installHighlighter();
   }
@@ -58,8 +54,8 @@ public class SimpleThreesideDiffChange {
     assert myHighlighters.isEmpty();
 
     createHighlighter(ThreeSide.BASE);
-    if (myType.isLeftChange()) createHighlighter(ThreeSide.LEFT);
-    if (myType.isRightChange()) createHighlighter(ThreeSide.RIGHT);
+    if (getType().isLeftChange()) createHighlighter(ThreeSide.LEFT);
+    if (getType().isRightChange()) createHighlighter(ThreeSide.RIGHT);
   }
 
   public void destroyHighlighter() {
@@ -107,24 +103,16 @@ public class SimpleThreesideDiffChange {
   // Getters
   //
 
+  @Override
   public int getStartLine(@NotNull ThreeSide side) {
     return myFragment.getStartLine(side) + side.select(myLineStartShifts);
   }
 
+  @Override
   public int getEndLine(@NotNull ThreeSide side) {
     return myFragment.getEndLine(side) + side.select(myLineEndShifts);
   }
 
-  @NotNull
-  public TextDiffType getDiffType() {
-    return myType.getDiffType();
-  }
-
-  @NotNull
-  public ConflictType getType() {
-    return myType;
-  }
-
   //
   // Shift
   //
@@ -140,125 +128,4 @@ public class SimpleThreesideDiffChange {
 
     return newRange.damaged;
   }
-
-  //
-  // Type
-  //
-
-  @NotNull
-  private static ConflictType calcType(@NotNull MergeLineFragment fragment,
-                                       @NotNull List<? extends EditorEx> editors,
-                                       @NotNull ComparisonPolicy policy) {
-    boolean isLeftEmpty = isIntervalEmpty(fragment, ThreeSide.LEFT);
-    boolean isBaseEmpty = isIntervalEmpty(fragment, ThreeSide.BASE);
-    boolean isRightEmpty = isIntervalEmpty(fragment, ThreeSide.RIGHT);
-    assert !isLeftEmpty || !isBaseEmpty || !isRightEmpty;
-
-    if (isBaseEmpty) {
-      if (isLeftEmpty) { // --=
-        return new ConflictType(TextDiffType.INSERTED, false, true);
-      }
-      else if (isRightEmpty) { // =--
-        return new ConflictType(TextDiffType.INSERTED, true, false);
-      }
-      else { // =-=
-        boolean equalModifications = compareLeftAndRight(fragment, editors, policy);
-        return new ConflictType(equalModifications ? TextDiffType.INSERTED : TextDiffType.CONFLICT);
-      }
-    }
-    else {
-      if (isLeftEmpty && isRightEmpty) { // -=-
-        return new ConflictType(TextDiffType.DELETED);
-      }
-      else { // -==, ==-, ===
-        boolean unchangedLeft = compareWithBase(fragment, editors, ThreeSide.LEFT);
-        boolean unchangedRight = compareWithBase(fragment, editors, ThreeSide.RIGHT);
-        assert !unchangedLeft || !unchangedRight;
-
-        if (unchangedLeft) return new ConflictType(isRightEmpty ? TextDiffType.DELETED : TextDiffType.MODIFIED, false, true);
-        if (unchangedRight) return new ConflictType(isLeftEmpty ? TextDiffType.DELETED : TextDiffType.MODIFIED, true, false);
-
-        boolean equalModifications = compareLeftAndRight(fragment, editors, policy);
-        return new ConflictType(equalModifications ? TextDiffType.MODIFIED : TextDiffType.CONFLICT);
-      }
-    }
-  }
-
-  private static boolean compareLeftAndRight(@NotNull MergeLineFragment fragment,
-                                             @NotNull List<? extends EditorEx> editors,
-                                             @NotNull ComparisonPolicy policy) {
-    CharSequence content1 = getRangeContent(fragment, editors, ThreeSide.LEFT);
-    CharSequence content2 = getRangeContent(fragment, editors, ThreeSide.RIGHT);
-
-    if (policy == ComparisonPolicy.IGNORE_WHITESPACES) {
-      if (content1 == null) content1 = "";
-      if (content2 == null) content2 = "";
-    }
-
-    if (content1 == null && content2 == null) return true;
-    if (content1 == null ^ content2 == null) return false;
-
-    return ComparisonManager.getInstance().isEquals(content1, content2, policy);
-  }
-
-  private static boolean compareWithBase(@NotNull MergeLineFragment fragment,
-                                         @NotNull List<? extends EditorEx> editors,
-                                         @NotNull ThreeSide side) {
-    CharSequence content1 = getRangeContent(fragment, editors, ThreeSide.BASE);
-    CharSequence content2 = getRangeContent(fragment, editors, side);
-
-    return StringUtil.equals(content1, content2);
-  }
-
-  @Nullable
-  private static CharSequence getRangeContent(@NotNull MergeLineFragment fragment,
-                                              @NotNull List<? extends EditorEx> editors,
-                                              @NotNull ThreeSide side) {
-    DocumentEx document = side.select(editors).getDocument();
-    int line1 = fragment.getStartLine(side);
-    int line2 = fragment.getEndLine(side);
-    if (line1 == line2) return null;
-    return DiffUtil.getLinesContent(document, line1, line2);
-  }
-
-  private static boolean isIntervalEmpty(@NotNull MergeLineFragment fragment, @NotNull ThreeSide side) {
-    return fragment.getStartLine(side) == fragment.getEndLine(side);
-  }
-
-  //
-  // Helpers
-  //
-
-  public static class ConflictType {
-    @NotNull private final TextDiffType myType;
-    private final boolean myLeftChange;
-    private final boolean myRightChange;
-
-    public ConflictType(@NotNull TextDiffType type) {
-      this(type, true, true);
-    }
-
-    public ConflictType(@NotNull TextDiffType type, boolean leftChange, boolean rightChange) {
-      myType = type;
-      myLeftChange = leftChange;
-      myRightChange = rightChange;
-    }
-
-    @NotNull
-    public TextDiffType getDiffType() {
-      return myType;
-    }
-
-    public boolean isLeftChange() {
-      return myLeftChange;
-    }
-
-    public boolean isRightChange() {
-      return myRightChange;
-    }
-
-    public boolean isChange(@NotNull Side side) {
-      return side.isLeft() ? myLeftChange : myRightChange;
-    }
-  }
 }
\ No newline at end of file
index 044c6cb42434c41e7df2f58360179552c1126e7d..adc2a32c1489f62f43ec49bf313ad9bbd4c8544a 100644 (file)
@@ -26,7 +26,7 @@ import com.intellij.diff.contents.DocumentContent;
 import com.intellij.diff.fragments.MergeLineFragment;
 import com.intellij.diff.requests.ContentDiffRequest;
 import com.intellij.diff.requests.DiffRequest;
-import com.intellij.diff.tools.util.*;
+import com.intellij.diff.tools.util.DiffNotifications;
 import com.intellij.diff.tools.util.base.IgnorePolicy;
 import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
 import com.intellij.diff.tools.util.side.ThreesideTextDiffViewer;
@@ -34,79 +34,31 @@ import com.intellij.diff.util.DiffDividerDrawUtil;
 import com.intellij.diff.util.DiffUtil;
 import com.intellij.diff.util.Side;
 import com.intellij.diff.util.ThreeSide;
-import com.intellij.diff.util.*;
-import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
-import com.intellij.openapi.Disposable;
 import com.intellij.openapi.actionSystem.AnAction;
 import com.intellij.openapi.actionSystem.Separator;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.diff.DiffBundle;
 import com.intellij.openapi.editor.Document;
-import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.event.DocumentEvent;
-import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.progress.ProcessCanceledException;
 import com.intellij.openapi.progress.ProgressIndicator;
 import com.intellij.openapi.util.Computable;
-import com.intellij.openapi.util.UserDataHolder;
-import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.util.Function;
 import com.intellij.util.containers.ContainerUtil;
-import com.intellij.util.ui.ButtonlessScrollBarUI;
 import org.jetbrains.annotations.CalledInAwt;
-import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import javax.swing.*;
-import java.awt.*;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 
-// TODO: extract common methods with Twoside
-public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
+public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewerEx {
   public static final Logger LOG = Logger.getInstance(SimpleThreesideDiffViewer.class);
 
-  @NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable1;
-  @NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable2;
-
-  @NotNull private final PrevNextDifferenceIterable myPrevNextDifferenceIterable;
-  @NotNull private final MyStatusPanel myStatusPanel;
-
   @NotNull private final List<SimpleThreesideDiffChange> myDiffChanges = new ArrayList<SimpleThreesideDiffChange>();
   @NotNull private final List<SimpleThreesideDiffChange> myInvalidDiffChanges = new ArrayList<SimpleThreesideDiffChange>();
-  private int myChangesCount = -1;
-  private int myConflictsCount = -1;
-
-  @NotNull private final MyFoldingModel myFoldingModel;
-  @NotNull private final MyInitialScrollHelper myInitialScrollHelper = new MyInitialScrollHelper();
 
   public SimpleThreesideDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
     super(context, (ContentDiffRequest)request);
-
-    mySyncScrollable1 = new MySyncScrollable(Side.LEFT);
-    mySyncScrollable2 = new MySyncScrollable(Side.RIGHT);
-    myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable();
-    myStatusPanel = new MyStatusPanel();
-    myFoldingModel = new MyFoldingModel(getEditors().toArray(new EditorEx[3]), this);
-  }
-
-  @Override
-  @CalledInAwt
-  protected void onInit() {
-    super.onInit();
-    myContentPanel.setPainter(new MyDividerPainter(Side.LEFT), Side.LEFT);
-    myContentPanel.setPainter(new MyDividerPainter(Side.RIGHT), Side.RIGHT);
-    myContentPanel.setScrollbarPainter(new MyScrollbarPainter());
-  }
-
-  @Override
-  @CalledInAwt
-  protected void onDispose() {
-    destroyChangedBlocks();
-    super.onDispose();
   }
 
   @NotNull
@@ -145,30 +97,10 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
     return group;
   }
 
-  @Override
-  @CalledInAwt
-  protected void processContextHints() {
-    super.processContextHints();
-    myInitialScrollHelper.processContext(myRequest);
-  }
-
-  @Override
-  @CalledInAwt
-  protected void updateContextHints() {
-    super.updateContextHints();
-    myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
-    myInitialScrollHelper.updateContext(myRequest);
-  }
-
   //
   // Diff
   //
 
-  @NotNull
-  public FoldingModelSupport.Settings getFoldingModelSettings() {
-    return TextDiffViewerUtil.getFoldingModelSettings(myContext);
-  }
-
   @Override
   protected void onSlowRediff() {
     super.onSlowRediff();
@@ -227,13 +159,11 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
         myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
         clearDiffPresentation();
 
-        myChangesCount = 0;
-        myConflictsCount = 0;
+        resetChangeCounters();
         for (MergeLineFragment fragment : fragments) {
           SimpleThreesideDiffChange change = new SimpleThreesideDiffChange(fragment, getEditors(), comparisonPolicy);
           myDiffChanges.add(change);
-          if (change.getDiffType() != TextDiffType.CONFLICT) myChangesCount++;
-          if (change.getDiffType() == TextDiffType.CONFLICT) myConflictsCount++;
+          onChangeAdded(change);
         }
 
         myFoldingModel.install(fragments, myRequest, getFoldingModelSettings());
@@ -246,24 +176,8 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
     };
   }
 
-  @NotNull
-  private Runnable applyNotification(@Nullable final JComponent notification) {
-    return new Runnable() {
-      @Override
-      public void run() {
-        clearDiffPresentation();
-        if (notification != null) myPanel.addNotification(notification);
-      }
-    };
-  }
-
-  private void clearDiffPresentation() {
-    myStatusPanel.setBusy(false);
-    myPanel.resetNotifications();
-    destroyChangedBlocks();
-  }
-
-  private void destroyChangedBlocks() {
+  protected void destroyChangedBlocks() {
+    super.destroyChangedBlocks();
     for (SimpleThreesideDiffChange change : myDiffChanges) {
       change.destroyHighlighter();
     }
@@ -273,14 +187,6 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
       change.destroyHighlighter();
     }
     myInvalidDiffChanges.clear();
-
-    myChangesCount = -1;
-    myConflictsCount = -1;
-
-    myFoldingModel.destroy();
-
-    myContentPanel.repaintDividers();
-    myStatusPanel.update();
   }
 
   //
@@ -319,28 +225,6 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
     }
   }
 
-  @Override
-  protected void onDocumentChange(@NotNull DocumentEvent e) {
-    super.onDocumentChange(e);
-    myFoldingModel.onDocumentChanged(e);
-  }
-
-  @CalledInAwt
-  protected boolean doScrollToChange(@NotNull ScrollToPolicy scrollToPolicy) {
-    SimpleThreesideDiffChange targetChange = scrollToPolicy.select(myDiffChanges);
-    if (targetChange == null) return false;
-
-    doScrollToChange(targetChange, false);
-    return true;
-  }
-
-  private void doScrollToChange(@NotNull SimpleThreesideDiffChange change, boolean animated) {
-    // TODO: use anchors to fix scrolling issue at the start/end of file
-    EditorEx editor = getCurrentEditor();
-    int line = change.getStartLine(getCurrentSide());
-    DiffUtil.scrollEditor(editor, line, animated);
-  }
-
   @NotNull
   private IgnorePolicy getIgnorePolicy() {
     IgnorePolicy policy = getTextSettings().getIgnorePolicy();
@@ -353,41 +237,20 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
   //
 
   @NotNull
-  protected List<SimpleThreesideDiffChange> getDiffChanges() {
+  public List<SimpleThreesideDiffChange> getChanges() {
     return myDiffChanges;
   }
 
   @NotNull
   @Override
-  protected SyncScrollSupport.SyncScrollable getSyncScrollable(@NotNull Side side) {
-    return side.select(mySyncScrollable1, mySyncScrollable2);
-  }
-
-  @NotNull
-  @Override
-  protected JComponent getStatusPanel() {
-    return myStatusPanel;
+  protected DiffDividerDrawUtil.DividerPaintable getDividerPaintable(@NotNull Side side) {
+    return new MyDividerPaintable(side);
   }
 
   //
   // Misc
   //
 
-  @Nullable
-  @CalledInAwt
-  private SimpleThreesideDiffChange getSelectedChange(@NotNull ThreeSide side) {
-    EditorEx editor = getEditor(side);
-    int caretLine = editor.getCaretModel().getLogicalPosition().line;
-
-    for (SimpleThreesideDiffChange change : myDiffChanges) {
-      int line1 = change.getStartLine(side);
-      int line2 = change.getEndLine(side);
-
-      if (DiffUtil.isSelectedByLine(caretLine, line1, line2)) return change;
-    }
-    return null;
-  }
-
   @SuppressWarnings("MethodOverridesStaticMethodOfSuperclass")
   public static boolean canShowRequest(@NotNull DiffContext context, @NotNull DiffRequest request) {
     return ThreesideTextDiffViewer.canShowRequest(context, request);
@@ -397,46 +260,6 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
   // Actions
   //
 
-  private class MyPrevNextDifferenceIterable extends PrevNextDifferenceIterableBase<SimpleThreesideDiffChange> {
-    @NotNull
-    @Override
-    protected List<SimpleThreesideDiffChange> getChanges() {
-      return myDiffChanges;
-    }
-
-    @NotNull
-    @Override
-    protected EditorEx getEditor() {
-      return getCurrentEditor();
-    }
-
-    @Override
-    protected int getStartLine(@NotNull SimpleThreesideDiffChange change) {
-      return change.getStartLine(getCurrentSide());
-    }
-
-    @Override
-    protected int getEndLine(@NotNull SimpleThreesideDiffChange change) {
-      return change.getEndLine(getCurrentSide());
-    }
-
-    @Override
-    protected void scrollToChange(@NotNull SimpleThreesideDiffChange change) {
-      doScrollToChange(change, true);
-    }
-  }
-
-  private class MyToggleExpandByDefaultAction extends TextDiffViewerUtil.ToggleExpandByDefaultAction {
-    public MyToggleExpandByDefaultAction() {
-      super(getTextSettings());
-    }
-
-    @Override
-    protected void expandAll(boolean expand) {
-      myFoldingModel.expandAll(expand);
-    }
-  }
-
   private class MyIgnorePolicySettingAction extends TextDiffViewerUtil.IgnorePolicySettingAction {
     public MyIgnorePolicySettingAction() {
       super(getTextSettings());
@@ -472,48 +295,6 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
   // Helpers
   //
 
-  @Nullable
-  @Override
-  public Object getData(@NonNls String dataId) {
-    if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
-      return myPrevNextDifferenceIterable;
-    }
-    else if (DiffDataKeys.CURRENT_CHANGE_RANGE.is(dataId)) {
-      SimpleThreesideDiffChange change = getSelectedChange(getCurrentSide());
-      if (change != null) {
-        return new LineRange(change.getStartLine(getCurrentSide()), change.getEndLine(getCurrentSide()));
-      }
-    }
-
-    return super.getData(dataId);
-  }
-
-  private class MySyncScrollable extends BaseSyncScrollable {
-    @NotNull private final Side mySide;
-
-    public MySyncScrollable(@NotNull Side side) {
-      mySide = side;
-    }
-
-    @Override
-    public boolean isSyncScrollEnabled() {
-      return getTextSettings().isEnableSyncScroll();
-    }
-
-    @Override
-    protected void processHelper(@NotNull ScrollHelper helper) {
-      ThreeSide left = mySide.select(ThreeSide.LEFT, ThreeSide.BASE);
-      ThreeSide right = mySide.select(ThreeSide.BASE, ThreeSide.RIGHT);
-
-      if (!helper.process(0, 0)) return;
-      for (SimpleThreesideDiffChange diffChange : myDiffChanges) {
-        if (!helper.process(diffChange.getStartLine(left), diffChange.getStartLine(right))) return;
-        if (!helper.process(diffChange.getEndLine(left), diffChange.getEndLine(right))) return;
-      }
-      helper.process(getEditor(left).getDocument().getLineCount(), getEditor(right).getDocument().getLineCount());
-    }
-  }
-
   private class MyDividerPaintable implements DiffDividerDrawUtil.DividerPaintable {
     @NotNull private final Side mySide;
 
@@ -527,7 +308,7 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
       ThreeSide right = mySide.select(ThreeSide.BASE, ThreeSide.RIGHT);
 
       for (SimpleThreesideDiffChange diffChange : myDiffChanges) {
-        if (!diffChange.getType().isChange(mySide)) continue;
+        if (!diffChange.isChange(mySide)) continue;
         if (!handler.process(diffChange.getStartLine(left), diffChange.getEndLine(left),
                              diffChange.getStartLine(right), diffChange.getEndLine(right),
                              diffChange.getDiffType().getColor(getEditor(ThreeSide.BASE)))) {
@@ -536,117 +317,4 @@ public class SimpleThreesideDiffViewer extends ThreesideTextDiffViewer {
       }
     }
   }
-
-  private class MyDividerPainter implements DiffSplitter.Painter {
-    @NotNull private final Side mySide;
-    @NotNull private final MyDividerPaintable myPaintable;
-
-    public MyDividerPainter(@NotNull Side side) {
-      mySide = side;
-      myPaintable = new MyDividerPaintable(side);
-    }
-
-    @Override
-    public void paint(@NotNull Graphics g, @NotNull JComponent divider) {
-      Graphics2D gg = DiffDividerDrawUtil.getDividerGraphics(g, divider, getEditor(ThreeSide.BASE).getComponent());
-
-      gg.setColor(DiffDrawUtil.getDividerColor(getEditor(ThreeSide.BASE)));
-      gg.fill(gg.getClipBounds());
-
-      Editor editor1 = mySide.select(getEditor(ThreeSide.LEFT), getEditor(ThreeSide.BASE));
-      Editor editor2 = mySide.select(getEditor(ThreeSide.BASE), getEditor(ThreeSide.RIGHT));
-
-      //DividerPolygonUtil.paintSimplePolygons(gg, divider.getWidth(), editor1, editor2, myPaintable);
-      DiffDividerDrawUtil.paintPolygons(gg, divider.getWidth(), editor1, editor2, myPaintable);
-
-      myFoldingModel.paintOnDivider(gg, divider, mySide);
-
-      gg.dispose();
-    }
-  }
-
-  private class MyScrollbarPainter implements ButtonlessScrollBarUI.ScrollbarRepaintCallback {
-    @NotNull private final MyDividerPaintable myPaintable = new MyDividerPaintable(Side.RIGHT);
-
-    @Override
-    public void call(Graphics g) {
-      EditorEx editor1 = getEditor(ThreeSide.BASE);
-      EditorEx editor2 = getEditor(ThreeSide.RIGHT);
-
-      int width = editor1.getScrollPane().getVerticalScrollBar().getWidth();
-      DiffDividerDrawUtil.paintPolygonsOnScrollbar((Graphics2D)g, width, editor1, editor2, myPaintable);
-
-      myFoldingModel.paintOnScrollbar((Graphics2D)g, width);
-    }
-  }
-
-  private class MyStatusPanel extends StatusPanel {
-    @Nullable
-    @Override
-    protected String getMessage() {
-      if (myChangesCount < 0 || myConflictsCount < 0) return null;
-      if (myChangesCount == 0 && myConflictsCount == 0) {
-        return DiffBundle.message("merge.dialog.all.conflicts.resolved.message.text");
-      }
-      return makeCounterWord(myChangesCount, "change") + ". " + makeCounterWord(myConflictsCount, "conflict");
-    }
-
-    @NotNull
-    private String makeCounterWord(int number, @NotNull String word) {
-      if (number == 0) {
-        return "No " + StringUtil.pluralize(word);
-      }
-      return number + " " + StringUtil.pluralize(word, number);
-    }
-  }
-
-  private static class MyFoldingModel extends FoldingModelSupport {
-    private final MyPaintable myPaintable1 = new MyPaintable(0, 1);
-    private final MyPaintable myPaintable2 = new MyPaintable(1, 2);
-
-    public MyFoldingModel(@NotNull EditorEx[] editors, @NotNull Disposable disposable) {
-      super(editors, disposable);
-      assert editors.length == 3;
-    }
-
-    public void install(@Nullable List<MergeLineFragment> fragments,
-                        @NotNull UserDataHolder context,
-                        @NotNull FoldingModelSupport.Settings settings) {
-      Iterator<int[]> it = map(fragments, new Function<MergeLineFragment, int[]>() {
-        @Override
-        public int[] fun(MergeLineFragment fragment) {
-          return new int[]{
-            fragment.getStartLine(ThreeSide.LEFT),
-            fragment.getEndLine(ThreeSide.LEFT),
-            fragment.getStartLine(ThreeSide.BASE),
-            fragment.getEndLine(ThreeSide.BASE),
-            fragment.getStartLine(ThreeSide.RIGHT),
-            fragment.getEndLine(ThreeSide.RIGHT)};
-        }
-      });
-      install(it, context, settings);
-    }
-
-    public void paintOnDivider(@NotNull Graphics2D gg, @NotNull Component divider, @NotNull Side side) {
-      MyPaintable paintable = side.select(myPaintable1, myPaintable2);
-      paintable.paintOnDivider(gg, divider);
-    }
-
-    public void paintOnScrollbar(@NotNull Graphics2D gg, int width) {
-      myPaintable2.paintOnScrollbar(gg, width);
-    }
-  }
-
-  private class MyInitialScrollHelper extends MyInitialScrollPositionHelper {
-    @Override
-    protected boolean doScrollToChange() {
-      if (myScrollToChange == null) return false;
-      return SimpleThreesideDiffViewer.this.doScrollToChange(myScrollToChange);
-    }
-
-    @Override
-    protected boolean doScrollToFirstChange() {
-      return SimpleThreesideDiffViewer.this.doScrollToChange(ScrollToPolicy.FIRST_CHANGE);
-    }
-  }
 }
diff --git a/platform/diff-impl/src/com/intellij/diff/tools/simple/ThreesideDiffChangeBase.java b/platform/diff-impl/src/com/intellij/diff/tools/simple/ThreesideDiffChangeBase.java
new file mode 100644 (file)
index 0000000..28ceb3c
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2000-2015 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.diff.tools.simple;
+
+import com.intellij.diff.comparison.ComparisonManager;
+import com.intellij.diff.comparison.ComparisonPolicy;
+import com.intellij.diff.fragments.MergeLineFragment;
+import com.intellij.diff.util.DiffUtil;
+import com.intellij.diff.util.Side;
+import com.intellij.diff.util.TextDiffType;
+import com.intellij.diff.util.ThreeSide;
+import com.intellij.openapi.editor.ex.DocumentEx;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+public abstract class ThreesideDiffChangeBase {
+  @NotNull private ConflictType myType;
+
+  public ThreesideDiffChangeBase(@NotNull MergeLineFragment fragment,
+                                 @NotNull List<? extends EditorEx> editors,
+                                 @NotNull ComparisonPolicy policy) {
+    myType = calcType(fragment, editors, policy);
+  }
+
+  //
+  // Getters
+  //
+
+  public abstract int getStartLine(@NotNull ThreeSide side);
+
+  public abstract int getEndLine(@NotNull ThreeSide side);
+
+  @NotNull
+  public TextDiffType getDiffType() {
+    return myType.getDiffType();
+  }
+
+  @NotNull
+  public ConflictType getType() {
+    return myType;
+  }
+
+  public boolean isConflict() {
+    return getDiffType() == TextDiffType.CONFLICT;
+  }
+
+  public boolean isChange(@NotNull Side side) {
+    return myType.isChange(side);
+  }
+
+  //
+  // Type
+  //
+
+  @NotNull
+  private static ConflictType calcType(@NotNull MergeLineFragment fragment,
+                                       @NotNull List<? extends EditorEx> editors,
+                                       @NotNull ComparisonPolicy policy) {
+    boolean isLeftEmpty = isIntervalEmpty(fragment, ThreeSide.LEFT);
+    boolean isBaseEmpty = isIntervalEmpty(fragment, ThreeSide.BASE);
+    boolean isRightEmpty = isIntervalEmpty(fragment, ThreeSide.RIGHT);
+    assert !isLeftEmpty || !isBaseEmpty || !isRightEmpty;
+
+    if (isBaseEmpty) {
+      if (isLeftEmpty) { // --=
+        return new ConflictType(TextDiffType.INSERTED, false, true);
+      }
+      else if (isRightEmpty) { // =--
+        return new ConflictType(TextDiffType.INSERTED, true, false);
+      }
+      else { // =-=
+        boolean equalModifications = compareLeftAndRight(fragment, editors, policy);
+        return new ConflictType(equalModifications ? TextDiffType.INSERTED : TextDiffType.CONFLICT);
+      }
+    }
+    else {
+      if (isLeftEmpty && isRightEmpty) { // -=-
+        return new ConflictType(TextDiffType.DELETED);
+      }
+      else { // -==, ==-, ===
+        boolean unchangedLeft = compareWithBase(fragment, editors, ThreeSide.LEFT);
+        boolean unchangedRight = compareWithBase(fragment, editors, ThreeSide.RIGHT);
+        assert !unchangedLeft || !unchangedRight;
+
+        if (unchangedLeft) return new ConflictType(isRightEmpty ? TextDiffType.DELETED : TextDiffType.MODIFIED, false, true);
+        if (unchangedRight) return new ConflictType(isLeftEmpty ? TextDiffType.DELETED : TextDiffType.MODIFIED, true, false);
+
+        boolean equalModifications = compareLeftAndRight(fragment, editors, policy);
+        return new ConflictType(equalModifications ? TextDiffType.MODIFIED : TextDiffType.CONFLICT);
+      }
+    }
+  }
+
+  private static boolean compareLeftAndRight(@NotNull MergeLineFragment fragment,
+                                             @NotNull List<? extends EditorEx> editors,
+                                             @NotNull ComparisonPolicy policy) {
+    CharSequence content1 = getRangeContent(fragment, editors, ThreeSide.LEFT);
+    CharSequence content2 = getRangeContent(fragment, editors, ThreeSide.RIGHT);
+
+    if (policy == ComparisonPolicy.IGNORE_WHITESPACES) {
+      if (content1 == null) content1 = "";
+      if (content2 == null) content2 = "";
+    }
+
+    if (content1 == null && content2 == null) return true;
+    if (content1 == null ^ content2 == null) return false;
+
+    return ComparisonManager.getInstance().isEquals(content1, content2, policy);
+  }
+
+  private static boolean compareWithBase(@NotNull MergeLineFragment fragment,
+                                         @NotNull List<? extends EditorEx> editors,
+                                         @NotNull ThreeSide side) {
+    CharSequence content1 = getRangeContent(fragment, editors, ThreeSide.BASE);
+    CharSequence content2 = getRangeContent(fragment, editors, side);
+
+    return StringUtil.equals(content1, content2);
+  }
+
+  @Nullable
+  private static CharSequence getRangeContent(@NotNull MergeLineFragment fragment,
+                                              @NotNull List<? extends EditorEx> editors,
+                                              @NotNull ThreeSide side) {
+    DocumentEx document = side.select(editors).getDocument();
+    int line1 = fragment.getStartLine(side);
+    int line2 = fragment.getEndLine(side);
+    if (line1 == line2) return null;
+    return DiffUtil.getLinesContent(document, line1, line2);
+  }
+
+  private static boolean isIntervalEmpty(@NotNull MergeLineFragment fragment, @NotNull ThreeSide side) {
+    return fragment.getStartLine(side) == fragment.getEndLine(side);
+  }
+
+  //
+  // Helpers
+  //
+
+  public static class ConflictType {
+    @NotNull private final TextDiffType myType;
+    private final boolean myLeftChange;
+    private final boolean myRightChange;
+
+    public ConflictType(@NotNull TextDiffType type) {
+      this(type, true, true);
+    }
+
+    public ConflictType(@NotNull TextDiffType type, boolean leftChange, boolean rightChange) {
+      myType = type;
+      myLeftChange = leftChange;
+      myRightChange = rightChange;
+    }
+
+    @NotNull
+    public TextDiffType getDiffType() {
+      return myType;
+    }
+
+    public boolean isLeftChange() {
+      return myLeftChange;
+    }
+
+    public boolean isRightChange() {
+      return myRightChange;
+    }
+
+    public boolean isChange(@NotNull Side side) {
+      return side.isLeft() ? myLeftChange : myRightChange;
+    }
+  }
+}
\ No newline at end of file
diff --git a/platform/diff-impl/src/com/intellij/diff/tools/simple/ThreesideTextDiffViewerEx.java b/platform/diff-impl/src/com/intellij/diff/tools/simple/ThreesideTextDiffViewerEx.java
new file mode 100644 (file)
index 0000000..e28ef46
--- /dev/null
@@ -0,0 +1,461 @@
+/*
+ * Copyright 2000-2015 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.diff.tools.simple;
+
+import com.intellij.diff.DiffContext;
+import com.intellij.diff.fragments.MergeLineFragment;
+import com.intellij.diff.requests.ContentDiffRequest;
+import com.intellij.diff.tools.util.*;
+import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
+import com.intellij.diff.tools.util.side.ThreesideTextDiffViewer;
+import com.intellij.diff.util.*;
+import com.intellij.diff.util.DiffDividerDrawUtil.DividerPaintable;
+import com.intellij.diff.util.DiffUserDataKeysEx.ScrollToPolicy;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.diff.DiffBundle;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.event.DocumentEvent;
+import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.util.UserDataHolder;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.Function;
+import com.intellij.util.ui.ButtonlessScrollBarUI;
+import org.jetbrains.annotations.CalledInAwt;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Iterator;
+import java.util.List;
+
+public abstract class ThreesideTextDiffViewerEx extends ThreesideTextDiffViewer {
+  public static final Logger LOG = Logger.getInstance(ThreesideTextDiffViewerEx.class);
+
+  @NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable1;
+  @NotNull private final SyncScrollSupport.SyncScrollable mySyncScrollable2;
+
+  @NotNull protected final PrevNextDifferenceIterable myPrevNextDifferenceIterable;
+  @NotNull protected final MyStatusPanel myStatusPanel;
+
+  @NotNull protected final MyFoldingModel myFoldingModel;
+  @NotNull protected final MyInitialScrollHelper myInitialScrollHelper = new MyInitialScrollHelper();
+
+  private int myChangesCount = -1;
+  private int myConflictsCount = -1;
+
+  public ThreesideTextDiffViewerEx(@NotNull DiffContext context, @NotNull ContentDiffRequest request) {
+    super(context, request);
+
+    mySyncScrollable1 = new MySyncScrollable(Side.LEFT);
+    mySyncScrollable2 = new MySyncScrollable(Side.RIGHT);
+    myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable();
+    myStatusPanel = new MyStatusPanel();
+    myFoldingModel = new MyFoldingModel(getEditors().toArray(new EditorEx[3]), this);
+  }
+
+  @Override
+  @CalledInAwt
+  protected void onInit() {
+    super.onInit();
+    myContentPanel.setPainter(new MyDividerPainter(Side.LEFT), Side.LEFT);
+    myContentPanel.setPainter(new MyDividerPainter(Side.RIGHT), Side.RIGHT);
+    myContentPanel.setScrollbarPainter(new MyScrollbarPainter());
+  }
+
+  @Override
+  @CalledInAwt
+  protected void onDispose() {
+    destroyChangedBlocks();
+    super.onDispose();
+  }
+
+  @Override
+  @CalledInAwt
+  protected void processContextHints() {
+    super.processContextHints();
+    myInitialScrollHelper.processContext(myRequest);
+  }
+
+  @Override
+  @CalledInAwt
+  protected void updateContextHints() {
+    super.updateContextHints();
+    myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
+    myInitialScrollHelper.updateContext(myRequest);
+  }
+
+  //
+  // Diff
+  //
+
+  @NotNull
+  public FoldingModelSupport.Settings getFoldingModelSettings() {
+    return TextDiffViewerUtil.getFoldingModelSettings(myContext);
+  }
+
+  @NotNull
+  protected Runnable applyNotification(@Nullable final JComponent notification) {
+    return new Runnable() {
+      @Override
+      public void run() {
+        clearDiffPresentation();
+        if (notification != null) myPanel.addNotification(notification);
+      }
+    };
+  }
+
+  protected void clearDiffPresentation() {
+    myStatusPanel.setBusy(false);
+    myPanel.resetNotifications();
+    destroyChangedBlocks();
+
+    myContentPanel.repaintDividers();
+    myStatusPanel.update();
+  }
+
+  protected void destroyChangedBlocks() {
+    myFoldingModel.destroy();
+  }
+
+  //
+  // Impl
+  //
+
+  @Override
+  protected void onDocumentChange(@NotNull DocumentEvent e) {
+    super.onDocumentChange(e);
+    myFoldingModel.onDocumentChanged(e);
+  }
+
+  @CalledInAwt
+  protected boolean doScrollToChange(@NotNull ScrollToPolicy scrollToPolicy) {
+    ThreesideDiffChangeBase targetChange = scrollToPolicy.select(getChanges());
+    if (targetChange == null) return false;
+
+    doScrollToChange(targetChange, false);
+    return true;
+  }
+
+  protected void doScrollToChange(@NotNull ThreesideDiffChangeBase change, boolean animated) {
+    int[] startLines = new int[3];
+    int[] endLines = new int[3];
+
+    for (int i = 0; i < 3; i++) {
+      ThreeSide side = ThreeSide.fromIndex(i);
+      startLines[i] = change.getStartLine(side);
+      endLines[i] = change.getEndLine(side);
+      DiffUtil.moveCaret(getEditor(side), startLines[i]);
+    }
+
+    getSyncScrollSupport().makeVisible(getCurrentSide(), startLines, endLines, animated);
+  }
+
+  //
+  // Counters
+  //
+
+  public int getChangesCount() {
+    return myChangesCount;
+  }
+
+  public int getConflictsCount() {
+    return myConflictsCount;
+  }
+
+  protected void resetChangeCounters() {
+    myChangesCount = 0;
+    myConflictsCount = 0;
+  }
+
+  protected void onChangeAdded(@NotNull ThreesideDiffChangeBase change) {
+    if (change.isConflict()) {
+      myConflictsCount++;
+    }
+    else {
+      myChangesCount++;
+    }
+    myStatusPanel.update();
+  }
+
+  protected void onChangeRemoved(@NotNull ThreesideDiffChangeBase change) {
+    if (change.isConflict()) {
+      myConflictsCount--;
+    }
+    else {
+      myChangesCount--;
+    }
+    myStatusPanel.update();
+  }
+
+  //
+  // Getters
+  //
+
+  @NotNull
+  protected abstract DividerPaintable getDividerPaintable(@NotNull Side side);
+
+  /*
+   * Some changes (ex: applied ones) can be excluded from general processing, but should be painted/used for synchronized scrolling
+   */
+  @NotNull
+  protected List<? extends ThreesideDiffChangeBase> getAllChanges() {
+    return getChanges();
+  }
+
+  @NotNull
+  protected abstract List<? extends ThreesideDiffChangeBase> getChanges();
+
+  @NotNull
+  @Override
+  protected SyncScrollSupport.SyncScrollable getSyncScrollable(@NotNull Side side) {
+    return side.select(mySyncScrollable1, mySyncScrollable2);
+  }
+
+  @NotNull
+  @Override
+  protected JComponent getStatusPanel() {
+    return myStatusPanel;
+  }
+
+  @NotNull
+  public SyncScrollSupport.ThreesideSyncScrollSupport getSyncScrollSupport() {
+    //noinspection ConstantConditions
+    return mySyncScrollSupport;
+  }
+
+  //
+  // Misc
+  //
+
+  @Nullable
+  @CalledInAwt
+  protected ThreesideDiffChangeBase getSelectedChange(@NotNull ThreeSide side) {
+    int caretLine = getEditor(side).getCaretModel().getLogicalPosition().line;
+
+    for (ThreesideDiffChangeBase change : getChanges()) {
+      int line1 = change.getStartLine(side);
+      int line2 = change.getEndLine(side);
+
+      if (DiffUtil.isSelectedByLine(caretLine, line1, line2)) return change;
+    }
+    return null;
+  }
+
+  //
+  // Actions
+  //
+
+  protected class MyPrevNextDifferenceIterable extends PrevNextDifferenceIterableBase<ThreesideDiffChangeBase> {
+    @NotNull
+    @Override
+    protected List<? extends ThreesideDiffChangeBase> getChanges() {
+      return ThreesideTextDiffViewerEx.this.getChanges();
+    }
+
+    @NotNull
+    @Override
+    protected EditorEx getEditor() {
+      return getCurrentEditor();
+    }
+
+    @Override
+    protected int getStartLine(@NotNull ThreesideDiffChangeBase change) {
+      return change.getStartLine(getCurrentSide());
+    }
+
+    @Override
+    protected int getEndLine(@NotNull ThreesideDiffChangeBase change) {
+      return change.getEndLine(getCurrentSide());
+    }
+
+    @Override
+    protected void scrollToChange(@NotNull ThreesideDiffChangeBase change) {
+      doScrollToChange(change, true);
+    }
+  }
+
+  protected class MyToggleExpandByDefaultAction extends TextDiffViewerUtil.ToggleExpandByDefaultAction {
+    public MyToggleExpandByDefaultAction() {
+      super(getTextSettings());
+    }
+
+    @Override
+    protected void expandAll(boolean expand) {
+      myFoldingModel.expandAll(expand);
+    }
+  }
+
+  //
+  // Helpers
+  //
+
+  @Nullable
+  @Override
+  public Object getData(@NonNls String dataId) {
+    if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
+      return myPrevNextDifferenceIterable;
+    }
+    else if (DiffDataKeys.CURRENT_CHANGE_RANGE.is(dataId)) {
+      ThreesideDiffChangeBase change = getSelectedChange(getCurrentSide());
+      if (change != null) {
+        return new LineRange(change.getStartLine(getCurrentSide()), change.getEndLine(getCurrentSide()));
+      }
+    }
+    return super.getData(dataId);
+  }
+
+  protected class MySyncScrollable extends BaseSyncScrollable {
+    @NotNull private final Side mySide;
+
+    public MySyncScrollable(@NotNull Side side) {
+      mySide = side;
+    }
+
+    @Override
+    public boolean isSyncScrollEnabled() {
+      return getTextSettings().isEnableSyncScroll();
+    }
+
+    @Override
+    protected void processHelper(@NotNull ScrollHelper helper) {
+      ThreeSide left = mySide.select(ThreeSide.LEFT, ThreeSide.BASE);
+      ThreeSide right = mySide.select(ThreeSide.BASE, ThreeSide.RIGHT);
+
+      if (!helper.process(0, 0)) return;
+      for (ThreesideDiffChangeBase diffChange : getAllChanges()) {
+        if (!helper.process(diffChange.getStartLine(left), diffChange.getStartLine(right))) return;
+        if (!helper.process(diffChange.getEndLine(left), diffChange.getEndLine(right))) return;
+      }
+      helper.process(getEditor(left).getDocument().getLineCount(), getEditor(right).getDocument().getLineCount());
+    }
+  }
+
+  protected class MyDividerPainter implements DiffSplitter.Painter {
+    @NotNull private final Side mySide;
+    @NotNull private final DividerPaintable myPaintable;
+
+    public MyDividerPainter(@NotNull Side side) {
+      mySide = side;
+      myPaintable = getDividerPaintable(side);
+    }
+
+    @Override
+    public void paint(@NotNull Graphics g, @NotNull JComponent divider) {
+      Graphics2D gg = DiffDividerDrawUtil.getDividerGraphics(g, divider, getEditor(ThreeSide.BASE).getComponent());
+
+      gg.setColor(DiffDrawUtil.getDividerColor(getEditor(ThreeSide.BASE)));
+      gg.fill(gg.getClipBounds());
+
+      Editor editor1 = mySide.select(getEditor(ThreeSide.LEFT), getEditor(ThreeSide.BASE));
+      Editor editor2 = mySide.select(getEditor(ThreeSide.BASE), getEditor(ThreeSide.RIGHT));
+
+      //DividerPolygonUtil.paintSimplePolygons(gg, divider.getWidth(), editor1, editor2, myPaintable);
+      DiffDividerDrawUtil.paintPolygons(gg, divider.getWidth(), editor1, editor2, myPaintable);
+
+      myFoldingModel.paintOnDivider(gg, divider, mySide);
+
+      gg.dispose();
+    }
+  }
+
+  protected class MyScrollbarPainter implements ButtonlessScrollBarUI.ScrollbarRepaintCallback {
+    @NotNull private final DividerPaintable myPaintable = getDividerPaintable(Side.RIGHT);
+
+    @Override
+    public void call(Graphics g) {
+      EditorEx editor1 = getEditor(ThreeSide.BASE);
+      EditorEx editor2 = getEditor(ThreeSide.RIGHT);
+
+      int width = editor1.getScrollPane().getVerticalScrollBar().getWidth();
+      DiffDividerDrawUtil.paintPolygonsOnScrollbar((Graphics2D)g, width, editor1, editor2, myPaintable);
+
+      myFoldingModel.paintOnScrollbar((Graphics2D)g, width);
+    }
+  }
+
+  protected class MyStatusPanel extends StatusPanel {
+    @Nullable
+    @Override
+    protected String getMessage() {
+      if (myChangesCount < 0 || myConflictsCount < 0) return null;
+      if (myChangesCount == 0 && myConflictsCount == 0) {
+        return DiffBundle.message("merge.dialog.all.conflicts.resolved.message.text");
+      }
+      return makeCounterWord(myChangesCount, "change") + ". " + makeCounterWord(myConflictsCount, "conflict");
+    }
+
+    @NotNull
+    private String makeCounterWord(int number, @NotNull String word) {
+      if (number == 0) {
+        return "No " + StringUtil.pluralize(word);
+      }
+      return number + " " + StringUtil.pluralize(word, number);
+    }
+  }
+
+  protected static class MyFoldingModel extends FoldingModelSupport {
+    private final MyPaintable myPaintable1 = new MyPaintable(0, 1);
+    private final MyPaintable myPaintable2 = new MyPaintable(1, 2);
+
+    public MyFoldingModel(@NotNull EditorEx[] editors, @NotNull Disposable disposable) {
+      super(editors, disposable);
+      assert editors.length == 3;
+    }
+
+    public void install(@Nullable List<MergeLineFragment> fragments,
+                        @NotNull UserDataHolder context,
+                        @NotNull FoldingModelSupport.Settings settings) {
+      Iterator<int[]> it = map(fragments, new Function<MergeLineFragment, int[]>() {
+        @Override
+        public int[] fun(MergeLineFragment fragment) {
+          return new int[]{
+            fragment.getStartLine(ThreeSide.LEFT),
+            fragment.getEndLine(ThreeSide.LEFT),
+            fragment.getStartLine(ThreeSide.BASE),
+            fragment.getEndLine(ThreeSide.BASE),
+            fragment.getStartLine(ThreeSide.RIGHT),
+            fragment.getEndLine(ThreeSide.RIGHT)};
+        }
+      });
+      install(it, context, settings);
+    }
+
+    public void paintOnDivider(@NotNull Graphics2D gg, @NotNull Component divider, @NotNull Side side) {
+      MyPaintable paintable = side.select(myPaintable1, myPaintable2);
+      paintable.paintOnDivider(gg, divider);
+    }
+
+    public void paintOnScrollbar(@NotNull Graphics2D gg, int width) {
+      myPaintable2.paintOnScrollbar(gg, width);
+    }
+  }
+
+  protected class MyInitialScrollHelper extends MyInitialScrollPositionHelper {
+    @Override
+    protected boolean doScrollToChange() {
+      if (myScrollToChange == null) return false;
+      return ThreesideTextDiffViewerEx.this.doScrollToChange(myScrollToChange);
+    }
+
+    @Override
+    protected boolean doScrollToFirstChange() {
+      return ThreesideTextDiffViewerEx.this.doScrollToChange(ScrollToPolicy.FIRST_CHANGE);
+    }
+  }
+}
index 9ffacde33b0b683122d7b1463d53871d273a5463..cda8d9d90219f1cbb02b9a9e7e18d1c0c1de19f9 100644 (file)
@@ -22,7 +22,7 @@ import java.util.List;
 
 public abstract class PrevNextDifferenceIterableBase<T> implements PrevNextDifferenceIterable {
   @NotNull
-  protected abstract List<T> getChanges();
+  protected abstract List<? extends T> getChanges();
 
   @NotNull
   protected abstract EditorEx getEditor();
@@ -35,7 +35,7 @@ public abstract class PrevNextDifferenceIterableBase<T> implements PrevNextDiffe
 
   @Override
   public boolean canGoNext() {
-    List<T> changes = getChanges();
+    List<? extends T> changes = getChanges();
     if (changes.isEmpty()) return false;
 
     EditorEx editor = getEditor();
@@ -50,7 +50,7 @@ public abstract class PrevNextDifferenceIterableBase<T> implements PrevNextDiffe
 
   @Override
   public void goNext() {
-    List<T> changes = getChanges();
+    List<? extends T> changes = getChanges();
     int line = getEditor().getCaretModel().getLogicalPosition().line;
 
     T next = null;
@@ -68,7 +68,7 @@ public abstract class PrevNextDifferenceIterableBase<T> implements PrevNextDiffe
 
   @Override
   public boolean canGoPrev() {
-    List<T> changes = getChanges();
+    List<? extends T> changes = getChanges();
     if (changes.isEmpty()) return false;
 
     int line = getEditor().getCaretModel().getLogicalPosition().line;
@@ -83,7 +83,7 @@ public abstract class PrevNextDifferenceIterableBase<T> implements PrevNextDiffe
 
   @Override
   public void goPrev() {
-    List<T> changes = getChanges();
+    List<? extends T> changes = getChanges();
     int line = getEditor().getCaretModel().getLogicalPosition().line;
 
     T prev = null;
index cd9ae43117106ef46d211416bb77ca72ecaf43cc..6f6450a21df9d5c1d7de13d660fcd64dd48559d8 100644 (file)
@@ -15,8 +15,8 @@
  */
 package com.intellij.diff.tools.util;
 
-import com.intellij.diff.util.IntPair;
 import com.intellij.diff.util.Side;
+import com.intellij.diff.util.ThreeSide;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.LogicalPosition;
@@ -24,7 +24,7 @@ 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 gnu.trove.TIntFunction;
+import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.CalledInAwt;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -44,37 +44,31 @@ public class SyncScrollSupport {
     int transfer(@NotNull Side baseSide, int line);
   }
 
-  public static class TwosideSyncScrollSupport {
-    @NotNull private final Editor myEditor1;
-    @NotNull private final Editor myEditor2;
+  public static class TwosideSyncScrollSupport extends SyncScrollSupportBase {
+    @NotNull private final List<? extends Editor> myEditors;
     @NotNull private final SyncScrollable myScrollable;
 
-    @NotNull private final MyScrollHelper myHelper1;
-    @NotNull private final MyScrollHelper myHelper2;
-
-    private boolean myDisabled = false;
-    private boolean myDuringSyncScroll = false;
+    @NotNull private final ScrollHelper myHelper1;
+    @NotNull private final ScrollHelper myHelper2;
 
     public TwosideSyncScrollSupport(@NotNull List<? extends Editor> editors, @NotNull SyncScrollable scrollable) {
-      this(Side.LEFT.select(editors), Side.RIGHT.select(editors), scrollable);
-    }
-
-    public TwosideSyncScrollSupport(@NotNull Editor editor1, @NotNull Editor editor2, @NotNull SyncScrollable scrollable) {
-      myEditor1 = editor1;
-      myEditor2 = editor2;
+      myEditors = editors;
       myScrollable = scrollable;
 
-      myHelper1 = create(myEditor1, myEditor2, myScrollable, Side.LEFT);
-      myHelper2 = create(myEditor2, myEditor1, myScrollable, Side.RIGHT);
+      myHelper1 = create(Side.LEFT);
+      myHelper2 = create(Side.RIGHT);
     }
 
-    public boolean isDuringSyncScroll() {
-      return myDuringSyncScroll;
+    @Override
+    @NotNull
+    protected List<? extends Editor> getEditors() {
+      return myEditors;
     }
 
-    public void setDisabled(boolean value) {
-      if (myDisabled == value) LOG.warn(new Throwable("myDisabled == value: " + myDisabled + " - " + value));
-      myDisabled = value;
+    @Override
+    @NotNull
+    protected List<? extends ScrollHelper> getScrollHelpers() {
+      return ContainerUtil.list(myHelper1, myHelper2);
     }
 
     @NotNull
@@ -83,159 +77,241 @@ public class SyncScrollSupport {
     }
 
     public void visibleAreaChanged(VisibleAreaEvent e) {
-      if (!myScrollable.isSyncScrollEnabled() || myDuringSyncScroll || myDisabled) return;
+      if (!myScrollable.isSyncScrollEnabled() || isDuringSyncScroll()) return;
 
-      myDuringSyncScroll = true;
+      enterDisableScrollSection();
       try {
-        if (e.getEditor() == myEditor1) {
+        if (e.getEditor() == Side.LEFT.select(myEditors)) {
           myHelper1.visibleAreaChanged(e);
         }
-        else if (e.getEditor() == myEditor2) {
+        else if (e.getEditor() == Side.RIGHT.select(myEditors)) {
           myHelper2.visibleAreaChanged(e);
         }
       }
       finally {
-        myDuringSyncScroll = false;
+        exitDisableScrollSection();
       }
     }
 
     public void makeVisible(@NotNull Side masterSide,
                             int startLine1, int endLine1, int startLine2, int endLine2,
                             final boolean animate) {
-      Side slaveSide = masterSide.other();
-
-      final IntPair offsets = getTargetOffsets(myEditor1, myEditor2, startLine1, endLine1, startLine2, endLine2);
-
-      final Editor masterEditor = masterSide.select(myEditor1, myEditor2);
-      final Editor slaveEditor = slaveSide.select(myEditor1, myEditor2);
-
-      final int masterOffset = masterSide.select(offsets.val1, offsets.val2);
-      final int slaveOffset = slaveSide.select(offsets.val1, offsets.val2);
-
-      int startOffset1 = myEditor1.getScrollingModel().getVisibleArea().y;
-      int startOffset2 = myEditor2.getScrollingModel().getVisibleArea().y;
-      final int masterStartOffset = masterSide.select(startOffset1, startOffset2);
-
-      myHelper1.setAnchor(startOffset1, offsets.val1, startOffset2, offsets.val2);
-      myHelper2.setAnchor(startOffset2, offsets.val2, startOffset1, offsets.val1);
-
-      doScrollHorizontally(masterEditor, 0, false); // animation will be canceled by "scroll vertically" anyway
-      doScrollVertically(masterEditor, masterOffset, animate);
-
-      masterEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
-        @Override
-        public void run() {
-          myHelper1.removeAnchor();
-          myHelper2.removeAnchor();
+      doMakeVisible(masterSide.getIndex(), new int[]{startLine1, startLine2}, new int[]{endLine1, endLine2}, animate);
+    }
 
-          int masterFinalOffset = masterEditor.getScrollingModel().getVisibleArea().y;
-          int slaveFinalOffset = slaveEditor.getScrollingModel().getVisibleArea().y;
-          if (slaveFinalOffset != slaveOffset) {
-            myDuringSyncScroll = true;
-
-            doScrollVertically(slaveEditor, slaveOffset, animate && masterFinalOffset == masterStartOffset);
-
-            slaveEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
-              @Override
-              public void run() {
-                myDuringSyncScroll = false;
-              }
-            });
-          }
-        }
-      });
+    @NotNull
+    private ScrollHelper create(@NotNull Side side) {
+      return ScrollHelper.create(myEditors, side.getIndex(), side.other().getIndex(), myScrollable, side);
     }
   }
 
-  public static class ThreesideSyncScrollSupport {
+  public static class ThreesideSyncScrollSupport extends SyncScrollSupportBase {
     @NotNull private final List<? extends Editor> myEditors;
-    @NotNull private final SyncScrollable myScrollable1;
-    @NotNull private final SyncScrollable myScrollable2;
+    @NotNull private final SyncScrollable myScrollable12;
+    @NotNull private final SyncScrollable myScrollable23;
 
-    @NotNull private final MyScrollHelper myHelper11;
-    @NotNull private final MyScrollHelper myHelper12;
-    @NotNull private final MyScrollHelper myHelper21;
-    @NotNull private final MyScrollHelper myHelper22;
-
-    private boolean myDisabled = false;
-    private boolean myDuringSyncScroll = false;
+    @NotNull private final ScrollHelper myHelper12;
+    @NotNull private final ScrollHelper myHelper21;
+    @NotNull private final ScrollHelper myHelper23;
+    @NotNull private final ScrollHelper myHelper32;
 
     public ThreesideSyncScrollSupport(@NotNull List<? extends Editor> editors,
-                                      @NotNull SyncScrollable scrollable1,
-                                      @NotNull SyncScrollable scrollable2) {
+                                      @NotNull SyncScrollable scrollable12,
+                                      @NotNull SyncScrollable scrollable23) {
       assert editors.size() == 3;
 
       myEditors = editors;
-      myScrollable1 = scrollable1;
-      myScrollable2 = scrollable2;
+      myScrollable12 = scrollable12;
+      myScrollable23 = scrollable23;
 
-      myHelper11 = create(editors.get(0), editors.get(1), myScrollable1, Side.LEFT);
-      myHelper12 = create(editors.get(1), editors.get(0), myScrollable1, Side.RIGHT);
+      myHelper12 = create(ThreeSide.LEFT, ThreeSide.BASE);
+      myHelper21 = create(ThreeSide.BASE, ThreeSide.LEFT);
 
-      myHelper21 = create(editors.get(1), editors.get(2), myScrollable2, Side.LEFT);
-      myHelper22 = create(editors.get(2), editors.get(1), myScrollable2, Side.RIGHT);
+      myHelper23 = create(ThreeSide.BASE, ThreeSide.RIGHT);
+      myHelper32 = create(ThreeSide.RIGHT, ThreeSide.BASE);
     }
 
-    public void setDisabled(boolean value) {
-      if (myDisabled == value) LOG.warn(new Throwable("myDisabled == value: " + myDisabled + " - " + value));
-      myDisabled = value;
+    @Override
+    @NotNull
+    protected List<? extends Editor> getEditors() {
+      return myEditors;
+    }
+
+    @Override
+    @NotNull
+    protected List<? extends ScrollHelper> getScrollHelpers() {
+      return ContainerUtil.list(myHelper12, myHelper21, myHelper23, myHelper32);
     }
 
     public void visibleAreaChanged(VisibleAreaEvent e) {
-      if (myDuringSyncScroll || myDisabled) return;
+      if (isDuringSyncScroll()) return;
 
-      myDuringSyncScroll = true;
+      enterDisableScrollSection();
       try {
-        if (e.getEditor() == myEditors.get(0)) {
-          if (myScrollable1.isSyncScrollEnabled()) myHelper11.visibleAreaChanged(e);
-          if (myScrollable1.isSyncScrollEnabled() && myScrollable2.isSyncScrollEnabled()) myHelper21.visibleAreaChanged(e);
+        if (e.getEditor() == ThreeSide.LEFT.select(myEditors)) {
+          if (myScrollable12.isSyncScrollEnabled()) {
+            myHelper12.visibleAreaChanged(e);
+            if (myScrollable23.isSyncScrollEnabled()) myHelper23.visibleAreaChanged(e);
+          }
         }
-        else if (e.getEditor() == myEditors.get(1)) {
-          if (myScrollable1.isSyncScrollEnabled()) myHelper12.visibleAreaChanged(e);
-          if (myScrollable2.isSyncScrollEnabled()) myHelper21.visibleAreaChanged(e);
+        else if (e.getEditor() == ThreeSide.BASE.select(myEditors)) {
+          if (myScrollable12.isSyncScrollEnabled()) myHelper21.visibleAreaChanged(e);
+          if (myScrollable23.isSyncScrollEnabled()) myHelper23.visibleAreaChanged(e);
         }
-        else if (e.getEditor() == myEditors.get(2)) {
-          if (myScrollable2.isSyncScrollEnabled()) myHelper22.visibleAreaChanged(e);
-          if (myScrollable2.isSyncScrollEnabled() && myScrollable1.isSyncScrollEnabled()) myHelper12.visibleAreaChanged(e);
+        else if (e.getEditor() == ThreeSide.RIGHT.select(myEditors)) {
+          if (myScrollable23.isSyncScrollEnabled()) {
+            myHelper32.visibleAreaChanged(e);
+            if (myScrollable12.isSyncScrollEnabled()) myHelper21.visibleAreaChanged(e);
+          }
         }
       }
       finally {
-        myDuringSyncScroll = false;
+        exitDisableScrollSection();
       }
     }
-  }
 
-  @NotNull
-  private static MyScrollHelper create(@NotNull Editor master,
-                                       @NotNull Editor slave,
-                                       @NotNull final SyncScrollable scrollable,
-                                       @NotNull final Side side) {
-    return new MyScrollHelper(master, slave, new TIntFunction() {
-      @Override
-      public int execute(int value) {
-        return scrollable.transfer(side, value);
+    public void makeVisible(@NotNull ThreeSide masterSide, int[] startLines, int[] endLines, boolean animate) {
+      doMakeVisible(masterSide.getIndex(), startLines, endLines, animate);
+    }
+
+    @NotNull
+    private ScrollHelper create(@NotNull ThreeSide master, @NotNull ThreeSide slave) {
+      assert master != slave;
+      assert master == ThreeSide.BASE || slave == ThreeSide.BASE;
+
+      boolean leftSide = master == ThreeSide.LEFT || slave == ThreeSide.LEFT;
+      SyncScrollable scrollable = leftSide ? myScrollable12 : myScrollable23;
+
+      Side side;
+      if (leftSide) {
+        // LEFT - BASE -> LEFT
+        // BASE - LEFT -> RIGHT
+        side = Side.fromLeft(master == ThreeSide.LEFT);
       }
-    });
+      else {
+        // BASE - RIGHT -> LEFT
+        // RIGHT - BASE -> RIGHT
+        side = Side.fromLeft(master == ThreeSide.BASE);
+      }
+
+      return ScrollHelper.create(myEditors, master.getIndex(), slave.getIndex(), scrollable, side);
+    }
   }
 
   //
   // Impl
   //
 
+  private abstract static class SyncScrollSupportBase {
+    private int myDuringSyncScrollDepth = 0;
+
+    public boolean isDuringSyncScroll() {
+      return myDuringSyncScrollDepth > 0;
+    }
+
+    public void enterDisableScrollSection() {
+      myDuringSyncScrollDepth++;
+    }
+
+    public void exitDisableScrollSection() {
+      myDuringSyncScrollDepth--;
+      assert myDuringSyncScrollDepth >= 0;
+    }
+
+    @NotNull
+    protected abstract List<? extends Editor> getEditors();
+
+    @NotNull
+    protected abstract List<? extends ScrollHelper> getScrollHelpers();
+
+    protected void doMakeVisible(final int masterIndex, int[] startLines, int[] endLines, final boolean animate) {
+      final List<? extends Editor> editors = getEditors();
+      final List<? extends ScrollHelper> helpers = getScrollHelpers();
+
+      final int count = editors.size();
+      assert startLines.length == count;
+      assert endLines.length == count;
+
+      final int[] offsets = getTargetOffsets(editors.toArray(new Editor[count]), startLines, endLines);
+
+      final int[] startOffsets = new int[count];
+      for (int i = 0; i < count; i++) {
+        startOffsets[i] = editors.get(i).getScrollingModel().getVisibleArea().y;
+      }
+
+      final Editor masterEditor = editors.get(masterIndex);
+      final int masterOffset = offsets[masterIndex];
+      final int masterStartOffset = startOffsets[masterIndex];
+
+      for (ScrollHelper helper : helpers) {
+        helper.setAnchor(startOffsets[helper.getMasterIndex()], offsets[helper.getMasterIndex()],
+                         startOffsets[helper.getSlaveIndex()], offsets[helper.getSlaveIndex()]);
+      }
+
+      doScrollHorizontally(masterEditor, 0, false); // animation will be canceled by "scroll vertically" anyway
+      doScrollVertically(masterEditor, masterOffset, animate);
+
+      masterEditor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
+        @Override
+        public void run() {
+          for (ScrollHelper helper : helpers) {
+            helper.removeAnchor();
+          }
+
+          int masterFinalOffset = masterEditor.getScrollingModel().getVisibleArea().y;
+          boolean animateSlaves = animate && masterFinalOffset == masterStartOffset;
+          for (int i = 0; i < count; i++) {
+            if (i == masterIndex) continue;
+            Editor editor = editors.get(i);
+
+            int finalOffset = editor.getScrollingModel().getVisibleArea().y;
+            if (finalOffset != offsets[i]) {
+              enterDisableScrollSection();
+
+              doScrollVertically(editor, offsets[i], animateSlaves);
+
+              editor.getScrollingModel().runActionOnScrollingFinished(new Runnable() {
+                @Override
+                public void run() {
+                  exitDisableScrollSection();
+                }
+              });
+            }
+          }
+        }
+      });
+    }
+  }
 
-  private static class MyScrollHelper implements VisibleAreaListener {
-    @NotNull private final Editor myMaster;
-    @NotNull private final Editor mySlave;
-    @NotNull private final TIntFunction myConvertor;
+  private static abstract class ScrollHelper implements VisibleAreaListener {
+    @NotNull private final List<? extends Editor> myEditors;
+    private final int myMasterIndex;
+    private final int mySlaveIndex;
 
     @Nullable private Anchor myAnchor;
 
-    public MyScrollHelper(@NotNull Editor master, @NotNull Editor slave, @NotNull TIntFunction convertor) {
-      myMaster = master;
-      mySlave = slave;
-      myConvertor = convertor;
+    public ScrollHelper(@NotNull List<? extends Editor> editors, int masterIndex, int slaveIndex) {
+      myEditors = editors;
+      myMasterIndex = masterIndex;
+      mySlaveIndex = slaveIndex;
     }
 
+    @NotNull
+    public static ScrollHelper create(@NotNull List<? extends Editor> editors,
+                                      int masterIndex,
+                                      int slaveIndex,
+                                      @NotNull final SyncScrollable scrollable,
+                                      @NotNull final Side side) {
+      return new ScrollHelper(editors, masterIndex, slaveIndex) {
+        @Override
+        protected int convertLine(int value) {
+          return scrollable.transfer(side, value);
+        }
+      };
+    }
+
+    protected abstract int convertLine(int value);
+
     public void setAnchor(int masterStartOffset, int masterEndOffset, int slaveStartOffset, int slaveEndOffset) {
       myAnchor = new Anchor(masterStartOffset, masterEndOffset, slaveStartOffset, slaveEndOffset);
     }
@@ -254,20 +330,38 @@ public class SyncScrollSupport {
       if (newRectangle.y != oldRectangle.y) syncVerticalScroll(false);
     }
 
+    public int getMasterIndex() {
+      return myMasterIndex;
+    }
+
+    public int getSlaveIndex() {
+      return mySlaveIndex;
+    }
+
+    @NotNull
+    public Editor getMaster() {
+      return myEditors.get(myMasterIndex);
+    }
+
+    @NotNull
+    public Editor getSlave() {
+      return myEditors.get(mySlaveIndex);
+    }
+
     private void syncVerticalScroll(boolean animated) {
-      if (myMaster.getDocument().getTextLength() == 0) return;
+      if (getMaster().getDocument().getTextLength() == 0) return;
 
-      Rectangle viewRect = myMaster.getScrollingModel().getVisibleArea();
+      Rectangle viewRect = getMaster().getScrollingModel().getVisibleArea();
       int middleY = viewRect.height / 3;
 
       int offset;
       if (myAnchor == null) {
-        LogicalPosition masterPos = myMaster.xyToLogicalPosition(new Point(viewRect.x, viewRect.y + middleY));
+        LogicalPosition masterPos = getMaster().xyToLogicalPosition(new Point(viewRect.x, viewRect.y + middleY));
         int masterCenterLine = masterPos.line;
-        int convertedCenterLine = myConvertor.execute(masterCenterLine);
+        int convertedCenterLine = convertLine(masterCenterLine);
 
-        Point point = mySlave.logicalPositionToXY(new LogicalPosition(convertedCenterLine, masterPos.column));
-        int correction = (viewRect.y + middleY) % myMaster.getLineHeight();
+        Point point = getSlave().logicalPositionToXY(new LogicalPosition(convertedCenterLine, masterPos.column));
+        int correction = (viewRect.y + middleY) % getMaster().getLineHeight();
         offset = point.y - middleY + correction;
       }
       else {
@@ -277,13 +371,13 @@ public class SyncScrollSupport {
         offset = myAnchor.slaveStartOffset + (int)((myAnchor.slaveEndOffset - myAnchor.slaveStartOffset) * progress);
       }
 
-      int deltaHeaderOffset = getHeaderOffset(mySlave) - getHeaderOffset(myMaster);
-      doScrollVertically(mySlave, offset + deltaHeaderOffset, animated);
+      int deltaHeaderOffset = getHeaderOffset(getSlave()) - getHeaderOffset(getMaster());
+      doScrollVertically(getSlave(), offset + deltaHeaderOffset, animated);
     }
 
     private void syncHorizontalScroll(boolean animated) {
-      int offset = myMaster.getScrollingModel().getVisibleArea().x;
-      doScrollHorizontally(mySlave, offset, animated);
+      int offset = getMaster().getScrollingModel().getVisibleArea().x;
+      doScrollHorizontally(getSlave(), offset, animated);
     }
   }
 
@@ -307,56 +401,83 @@ public class SyncScrollSupport {
   }
 
   @NotNull
-  private static IntPair getTargetOffsets(@NotNull Editor editor1, @NotNull Editor editor2,
-                                          int startLine1, int endLine1, int startLine2, int endLine2) {
-    int topOffset1 = editor1.logicalPositionToXY(new LogicalPosition(startLine1, 0)).y;
-    int bottomOffset1 = editor1.logicalPositionToXY(new LogicalPosition(endLine1 + 1, 0)).y;
-    int topOffset2 = editor2.logicalPositionToXY(new LogicalPosition(startLine2, 0)).y;
-    int bottomOffset2 = editor2.logicalPositionToXY(new LogicalPosition(endLine2 + 1, 0)).y;
+  private static int[] getTargetOffsets(@NotNull Editor editor1, @NotNull Editor editor2,
+                                        int startLine1, int endLine1, int startLine2, int endLine2) {
+    return getTargetOffsets(new Editor[]{editor1, editor2},
+                            new int[]{startLine1, startLine2},
+                            new int[]{endLine1, endLine2});
+  }
 
-    int rangeHeight1 = bottomOffset1 - topOffset1;
-    int rangeHeight2 = bottomOffset2 - topOffset2;
+  @NotNull
+  private static int[] getTargetOffsets(@NotNull Editor[] editors, int[] startLines, int[] endLines) {
+    int count = editors.length;
+    assert startLines.length == count;
+    assert endLines.length == count;
 
-    int gapLines1 = 2 * editor1.getLineHeight();
-    int gapLines2 = 2 * editor2.getLineHeight();
+    int[] topOffsets = new int[count];
+    int[] bottomOffsets = new int[count];
+    int[] rangeHeights = new int[count];
+    int[] gapLines = new int[count];
+    int[] editorHeights = new int[count];
+    int[] maximumOffsets = new int[count];
+    int[] topShifts = new int[count];
 
-    int editorHeight1 = editor1.getScrollingModel().getVisibleArea().height;
-    int editorHeight2 = editor2.getScrollingModel().getVisibleArea().height;
+    for (int i = 0; i < count; i++) {
+      topOffsets[i] = editors[i].logicalPositionToXY(new LogicalPosition(startLines[i], 0)).y;
+      bottomOffsets[i] = editors[i].logicalPositionToXY(new LogicalPosition(endLines[i] + 1, 0)).y;
+      rangeHeights[i] = bottomOffsets[i] - topOffsets[i];
 
-    int maximumOffset1 = ((EditorEx)editor1).getScrollPane().getVerticalScrollBar().getMaximum() - editorHeight1;
-    int maximumOffset2 = ((EditorEx)editor2).getScrollPane().getVerticalScrollBar().getMaximum() - editorHeight2;
+      gapLines[i] = 2 * editors[i].getLineHeight();
+      editorHeights[i] = editors[i].getScrollingModel().getVisibleArea().height;
 
-    // 'shift' here - distance between editor's top and first line of range
+      maximumOffsets[i] = ((EditorEx)editors[i]).getScrollPane().getVerticalScrollBar().getMaximum() - editorHeights[i];
 
-    // make whole range visible. If possible, locate it at 'center' (1/3 of height)
-    // If can't show whole range - show as much as we can
-    boolean canShow1 = 2 * gapLines1 + rangeHeight1 <= editorHeight1;
-    boolean canShow2 = 2 * gapLines2 + rangeHeight2 <= editorHeight2;
-    
-    int topShift1 = canShow1 ? Math.min(editorHeight1 - gapLines1 - rangeHeight1, editorHeight1 / 3) : gapLines1;
-    int topShift2 = canShow2 ? Math.min(editorHeight2 - gapLines2 - rangeHeight2, editorHeight2 / 3) : gapLines2;
+      // 'shift' here - distance between editor's top and first line of range
 
-    int topShift = Math.min(topShift1, topShift2);
+      // make whole range visible. If possible, locate it at 'center' (1/3 of height)
+      // If can't show whole range - show as much as we can
+      boolean canShow = 2 * gapLines[i] + rangeHeights[i] <= editorHeights[i];
+
+      topShifts[i] = canShow ? Math.min(editorHeights[i] - gapLines[i] - rangeHeights[i], editorHeights[i] / 3) : gapLines[i];
+    }
+
+    int topShift = min(topShifts);
 
     // check if we're at the top of file
-    topShift = Math.min(topShift, Math.min(topOffset1, topOffset2));
+    topShift = Math.min(topShift, min(topOffsets));
 
-    int offset1 = topOffset1 - topShift;
-    int offset2 = topOffset2 - topShift;
-    if (maximumOffset1 > offset1 && maximumOffset2 > offset2) return new IntPair(offset1, offset2);
+    int[] offsets = new int[count];
+    boolean haveEnoughSpace = true;
+    for (int i = 0; i < count; i++) {
+      offsets[i] = topOffsets[i] - topShift;
+      haveEnoughSpace &= maximumOffsets[i] > offsets[i];
+    }
+
+    if (haveEnoughSpace) return offsets;
 
     // One of the ranges is at end of file - we can't scroll where we want to.
-    topShift = Math.max(topOffset1 - maximumOffset1, topOffset2 - maximumOffset2);
+    topShift = 0;
+    for (int i = 0; i < count; i++) {
+      topShift = Math.max(topOffsets[i] - maximumOffsets[i], topShift);
+    }
+
+    for (int i = 0; i < count; i++) {
+      // Try to show as much of range as we can (even if it breaks alignment)
+      offsets[i] = topOffsets[i] - topShift + Math.max(topShift + rangeHeights[i] + gapLines[i] - editorHeights[i], 0);
 
-    // Try to show as much of range as we can (even if it breaks alignment)
-    offset1 = topOffset1 - topShift + Math.max(topShift + rangeHeight1 + gapLines1 - editorHeight1, 0);
-    offset2 = topOffset2 - topShift + Math.max(topShift + rangeHeight2 + gapLines2 - editorHeight2, 0);
+      // always show top of the range
+      offsets[i] = Math.min(offsets[i], topOffsets[i] - gapLines[i]);
+    }
 
-    // always show top of the range
-    offset1 = Math.min(offset1, topOffset1 - gapLines1);
-    offset2 = Math.min(offset2, topOffset2 - gapLines2);
+    return offsets;
+  }
 
-    return new IntPair(offset1, offset2);
+  private static int min(int[] values) {
+    int min = Integer.MAX_VALUE;
+    for (int value : values) {
+      if (value < min) min = value;
+    }
+    return min;
   }
 
   private static class Anchor {
index 9f88f365035b4dd5375187baf8b7984766969dc1..1698e1cdc02bc882784912a53441e92859703250 100644 (file)
@@ -18,6 +18,7 @@ package com.intellij.diff.tools.util.base;
 import com.intellij.diff.DiffContext;
 import com.intellij.openapi.actionSystem.DataProvider;
 import com.intellij.openapi.project.Project;
+import com.intellij.ui.components.panels.Wrapper;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -33,6 +34,9 @@ public abstract class DiffPanelBase extends JPanel implements DataProvider {
   @NotNull protected final JPanel myContentPanel;
   @NotNull protected final JPanel myNotificationsPanel;
 
+  @NotNull private final Wrapper myNorthPanel;
+  @NotNull private final Wrapper mySouthPanel;
+
   @NotNull protected final CardLayout myCardLayout;
 
   @NotNull protected String myCurrentCard;
@@ -51,23 +55,20 @@ public abstract class DiffPanelBase extends JPanel implements DataProvider {
     myNotificationsPanel = new JPanel();
     myNotificationsPanel.setLayout(new BoxLayout(myNotificationsPanel, BoxLayout.Y_AXIS));
 
-    add(myContentPanel, BorderLayout.CENTER);
-
-    JComponent topPanel = createTopPanel();
-    if (topPanel != null) add(topPanel, BorderLayout.NORTH);
+    myNorthPanel = new Wrapper();
+    mySouthPanel = new Wrapper();
 
-    JComponent bottomPanel = createBottomPanel();
-    if (bottomPanel != null) add(bottomPanel, BorderLayout.SOUTH);
+    add(myContentPanel, BorderLayout.CENTER);
+    add(myNorthPanel, BorderLayout.NORTH);
+    add(mySouthPanel, BorderLayout.SOUTH);
   }
 
-  @Nullable
-  public JComponent createTopPanel() {
-    return null;
+  public void setTopPanel(@Nullable JComponent component) {
+    myNorthPanel.setContent(component);
   }
 
-  @Nullable
-  public JComponent createBottomPanel() {
-    return null;
+  public void setBottomPanel(@Nullable JComponent component) {
+    mySouthPanel.setContent(component);
   }
 
   protected void setCurrentCard(@NotNull String card) {
index 38117e7fa4d0060839f0ab0dbbf3d82e1b9ac2a8..657e0d3a55614d77bee3273246246589ec834ce0 100644 (file)
@@ -125,7 +125,7 @@ public abstract class DiffViewerBase implements DiffViewer, DataProvider {
   }
 
   @CalledInAwt
-  public final void rediff(boolean trySync) {
+  public void rediff(boolean trySync) {
     if (isDisposed()) return;
     abortRediff();
 
index 963ceccf5f0a07f8f9d210ea50148ecde41a6c49..253ba5a7a9fcf03352a7fe88e395bc0b17314635 100644 (file)
@@ -158,7 +158,12 @@ public abstract class ThreesideTextDiffViewer extends ThreesideDiffViewer<TextEd
 
   protected void disableSyncScrollSupport(boolean disable) {
     if (mySyncScrollSupport != null) {
-      mySyncScrollSupport.setDisabled(disable);
+      if (disable) {
+        mySyncScrollSupport.enterDisableScrollSection();
+      }
+      else {
+        mySyncScrollSupport.exitDisableScrollSection();
+      }
     }
   }
 
@@ -230,6 +235,14 @@ public abstract class ThreesideTextDiffViewer extends ThreesideDiffViewer<TextEd
     return side.select(getContents());
   }
 
+  @Nullable
+  public ThreeSide getEditorSide(@Nullable Editor editor) {
+    if (getEditor(ThreeSide.BASE) == editor) return ThreeSide.BASE;
+    if (getEditor(ThreeSide.RIGHT) == editor) return ThreeSide.RIGHT;
+    if (getEditor(ThreeSide.LEFT) == editor) return ThreeSide.LEFT;
+    return null;
+  }
+
   //
   // Abstract
   //
@@ -265,10 +278,7 @@ public abstract class ThreesideTextDiffViewer extends ThreesideDiffViewer<TextEd
   private class MyOpenInEditorWithMouseAction extends OpenInEditorWithMouseAction {
     @Override
     protected OpenFileDescriptor getDescriptor(@NotNull Editor editor, int line) {
-      ThreeSide side = null;
-      if (editor == getEditor(ThreeSide.LEFT)) side = ThreeSide.LEFT;
-      if (editor == getEditor(ThreeSide.RIGHT)) side = ThreeSide.RIGHT;
-      if (editor == getEditor(ThreeSide.BASE)) side = ThreeSide.BASE;
+      ThreeSide side = getEditorSide(editor);
       if (side == null) return null;
 
       int offset = editor.logicalPositionToOffset(new LogicalPosition(line, 0));
index 63a3a44ec5a0689ae28211c40c60b3ac7787791e..066d2710ff58e611fa9496a45cec30f2977de9ec 100644 (file)
@@ -173,7 +173,12 @@ public abstract class TwosideTextDiffViewer extends TwosideDiffViewer<TextEditor
 
   protected void disableSyncScrollSupport(boolean disable) {
     if (mySyncScrollSupport != null) {
-      mySyncScrollSupport.setDisabled(disable);
+      if (disable) {
+        mySyncScrollSupport.enterDisableScrollSection();
+      }
+      else {
+        mySyncScrollSupport.exitDisableScrollSection();
+      }
     }
   }
 
index 9fe5e8e88d3cc6d462ed090dc3033079391fc4c8..dc6bd858dc07a5e6f2a0d783c733f57302cf9752 100644 (file)
@@ -30,6 +30,8 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class DiffDividerDrawUtil {
+  public static final BasicStroke BOLD_DOTTED_STROKE =
+    new BasicStroke(2.3f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{2, 2}, 0.0f);
 
   /*
    * Clip given graphics of divider component such that result graphics is aligned with base component by 'y' coordinate.
@@ -128,13 +130,18 @@ public class DiffDividerDrawUtil {
 
     paintable.process(new DividerPaintable.Handler() {
       @Override
-      public boolean process(int startLine1, int endLine1, int startLine2, int endLine2, @NotNull Color color) {
+      public boolean process(int startLine1, int endLine1, int startLine2, int endLine2, @NotNull Color color, boolean resolved) {
         if (leftInterval.startLine > endLine1 && rightInterval.startLine > endLine2) return true;
         if (leftInterval.endLine < startLine1 && rightInterval.endLine < startLine2) return false;
 
-        polygons.add(createPolygon(transformations, startLine1, endLine1, startLine2, endLine2, color));
+        polygons.add(createPolygon(transformations, startLine1, endLine1, startLine2, endLine2, color, resolved));
         return true;
       }
+
+      @Override
+      public boolean process(int startLine1, int endLine1, int startLine2, int endLine2, @NotNull Color color) {
+        return process(startLine1, endLine1, startLine2, endLine2, color, false);
+      }
     });
 
     return polygons;
@@ -190,11 +197,19 @@ public class DiffDividerDrawUtil {
                                               int startLine1, int endLine1,
                                               int startLine2, int endLine2,
                                               @NotNull Color color) {
+    return createPolygon(transformations, startLine1, endLine1, startLine2, endLine2, color, false);
+  }
+
+  @NotNull
+  private static DividerPolygon createPolygon(@NotNull Transformation[] transformations,
+                                              int startLine1, int endLine1,
+                                              int startLine2, int endLine2,
+                                              @NotNull Color color, boolean resolved) {
     int start1 = transformations[0].transform(startLine1);
     int end1 = transformations[0].transform(endLine1);
     int start2 = transformations[1].transform(startLine2);
     int end2 = transformations[1].transform(endLine2);
-    return new DividerPolygon(start1, start2, end1, end2, color);
+    return new DividerPolygon(start1, start2, end1, end2, color, resolved);
   }
 
   @NotNull
@@ -217,8 +232,10 @@ public class DiffDividerDrawUtil {
   public interface DividerPaintable {
     void process(@NotNull Handler handler);
 
-    interface Handler {
-      boolean process(int startLine1, int endLine1, int startLine2, int endLine2, @NotNull Color color);
+    abstract class Handler {
+      public abstract boolean process(int startLine1, int endLine1, int startLine2, int endLine2, @NotNull Color color);
+
+      public abstract boolean process(int startLine1, int endLine1, int startLine2, int endLine2, @NotNull Color color, boolean resolved);
     }
   }
 
@@ -240,13 +257,19 @@ public class DiffDividerDrawUtil {
     private final int myEnd1;
     private final int myEnd2;
     @NotNull private final Color myColor;
+    private final boolean myResolved;
 
     public DividerPolygon(int start1, int start2, int end1, int end2, @NotNull Color color) {
+      this(start1, start2, end1, end2, color, false);
+    }
+
+    public DividerPolygon(int start1, int start2, int end1, int end2, @NotNull Color color, boolean resolved) {
       myStart1 = start1;
       myStart2 = start2;
       myEnd1 = end1;
       myEnd2 = end2;
       myColor = color;
+      myResolved = resolved;
     }
 
     public void paint(Graphics2D g, int width, boolean paintBorder, boolean curve) {
@@ -259,12 +282,21 @@ public class DiffDividerDrawUtil {
       if (endY1 - startY1 < 2) endY1 = startY1 + 1;
       if (endY2 - startY2 < 2) endY2 = startY2 + 1;
 
+      Stroke oldStroke = g.getStroke();
+      if (myResolved) {
+        g.setStroke(BOLD_DOTTED_STROKE);
+      }
+
+      Color fillColor = myResolved ? null : myColor;
+      Color borderColor = myResolved ? myColor : null;
       if (curve) {
-        DiffDrawUtil.drawCurveTrapezium(g, 0, width, startY1, endY1, startY2, endY2, myColor, null);
+        DiffDrawUtil.drawCurveTrapezium(g, 0, width, startY1, endY1, startY2, endY2, fillColor, borderColor);
       }
       else {
-        DiffDrawUtil.drawTrapezium(g, 0, width, startY1, endY1, startY2, endY2, myColor, null);
+        DiffDrawUtil.drawTrapezium(g, 0, width, startY1, endY1, startY2, endY2, fillColor, borderColor);
       }
+
+      g.setStroke(oldStroke);
     }
 
     public void paintOnScrollbar(Graphics2D g, int width) {
@@ -277,13 +309,15 @@ public class DiffDividerDrawUtil {
 
       g.setColor(myColor);
       if (height > 2) {
-        g.fillRect(startX, startY, width, height);
+        if (!myResolved) {
+          g.fillRect(startX, startY, width, height);
+        }
 
-        DiffDrawUtil.drawChunkBorderLine(g, startX, endX, startY, myColor);
-        DiffDrawUtil.drawChunkBorderLine(g, startX, endX, endY, myColor);
+        DiffDrawUtil.drawChunkBorderLine(g, startX, endX, startY, myColor, false, myResolved);
+        DiffDrawUtil.drawChunkBorderLine(g, startX, endX, endY, myColor, false, myResolved);
       }
       else {
-        DiffDrawUtil.drawDoubleChunkBorderLine(g, startX, endX, startY, myColor);
+        DiffDrawUtil.drawChunkBorderLine(g, startX, endX, startY, myColor, true, myResolved);
       }
     }
 
index ac4f0f9403914bded852a87dad5b1c76c62b0835..6d17aef6c1f080c39d73236d04ec3fec77890e98 100644 (file)
@@ -86,6 +86,31 @@ public class DiffDrawUtil {
     UIUtil.drawLine(g, x1, y, x2, y, null, color);
   }
 
+  public static void drawDottedDoubleChunkBorderLine(@NotNull Graphics2D g, int x1, int x2, int y, @NotNull Color color) {
+    UIUtil.drawBoldDottedLine(g, x1, x2, y - 1, null, color, false);
+    UIUtil.drawBoldDottedLine(g, x1, x2, y, null, color, false);
+  }
+
+  public static void drawDottedChunkBorderLine(@NotNull Graphics2D g, int x1, int x2, int y, @NotNull Color color) {
+    UIUtil.drawBoldDottedLine(g, x1, x2, y - 1, null, color, false);
+  }
+
+  public static void drawChunkBorderLine(@NotNull Graphics2D g, int x1, int x2, int y, @NotNull Color color,
+                                         boolean doubleLine, boolean dottedLine) {
+    if (dottedLine && doubleLine) {
+      drawDottedDoubleChunkBorderLine(g, x1, x2, y, color);
+    }
+    else if (dottedLine) {
+      drawDottedChunkBorderLine(g, x1, x2, y, color);
+    }
+    else if (doubleLine) {
+      drawDoubleChunkBorderLine(g, x1, x2, y, color);
+    }
+    else {
+      drawChunkBorderLine(g, x1, x2, y, color);
+    }
+  }
+
   public static void drawTrapezium(@NotNull Graphics2D g,
                                    int x1, int x2,
                                    int start1, int end1,
@@ -196,8 +221,9 @@ public class DiffDrawUtil {
 
   private static void installGutterRenderer(@NotNull RangeHighlighter highlighter,
                                             @NotNull TextDiffType type,
-                                            boolean ignoredFoldingOutline) {
-    highlighter.setLineMarkerRenderer(new DiffLineMarkerRenderer(type, ignoredFoldingOutline));
+                                            boolean ignoredFoldingOutline,
+                                            boolean resolved) {
+    highlighter.setLineMarkerRenderer(new DiffLineMarkerRenderer(type, ignoredFoldingOutline, resolved));
   }
 
   private static void installEmptyRangeRenderer(@NotNull RangeHighlighter highlighter,
@@ -224,17 +250,22 @@ public class DiffDrawUtil {
     return createHighlighter(editor, start, end, type, ignored, HighlighterTargetArea.EXACT_RANGE);
   }
 
-
   @NotNull
   public static List<RangeHighlighter> createHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type,
                                                          boolean ignored, @NotNull HighlighterTargetArea area) {
-    TextAttributes attributes = start != end ? getTextAttributes(type, editor, ignored) : null;
-    TextAttributes stripeAttributes = start != end ? getStripeTextAttributes(type, editor) : null;
+    return createHighlighter(editor, start, end, type, ignored, area, false);
+  }
+
+  @NotNull
+  public static List<RangeHighlighter> createHighlighter(@NotNull Editor editor, int start, int end, @NotNull TextDiffType type,
+                                                         boolean ignored, @NotNull HighlighterTargetArea area, boolean resolved) {
+    TextAttributes attributes = start != end && !resolved ? getTextAttributes(type, editor, ignored) : null;
+    TextAttributes stripeAttributes = start != end && !resolved ? getStripeTextAttributes(type, editor) : null;
 
     RangeHighlighter highlighter = editor.getMarkupModel()
       .addRangeHighlighter(start, end, DEFAULT_LAYER, attributes, area);
 
-    installGutterRenderer(highlighter, type, ignored);
+    installGutterRenderer(highlighter, type, ignored, resolved);
 
     if (stripeAttributes == null) return Collections.singletonList(highlighter);
 
@@ -265,21 +296,23 @@ public class DiffDrawUtil {
   @NotNull
   public static List<RangeHighlighter> createLineMarker(@NotNull final Editor editor, int line, @NotNull final TextDiffType type,
                                                         @NotNull final SeparatorPlacement placement, final boolean doubleLine) {
+    return createLineMarker(editor, line, type, placement, doubleLine, false);
+  }
+
+  @NotNull
+  public static List<RangeHighlighter> createLineMarker(@NotNull final Editor editor, int line, @NotNull final TextDiffType type,
+                                                        @NotNull final SeparatorPlacement placement, final boolean doubleLine,
+                                                        final boolean applied) {
     LineSeparatorRenderer renderer = new LineSeparatorRenderer() {
       @Override
       public void drawLine(Graphics g, int x1, int x2, int y) {
         // TODO: change LineSeparatorRenderer interface ?
         Rectangle clip = g.getClipBounds();
         x2 = clip.x + clip.width;
-        if (doubleLine) {
-          drawDoubleChunkBorderLine((Graphics2D)g, x1, x2, y, type.getColor(editor));
-        }
-        else {
-          drawChunkBorderLine((Graphics2D)g, x1, x2, y, type.getColor(editor));
-        }
+        drawChunkBorderLine((Graphics2D)g, x1, x2, y, type.getColor(editor), doubleLine, applied);
       }
     };
-    return createLineMarker(editor, line, placement, type, renderer);
+    return createLineMarker(editor, line, placement, type, renderer, applied);
   }
 
   @NotNull
@@ -295,12 +328,12 @@ public class DiffDrawUtil {
         g.drawLine(x1, y, x2, y);
       }
     };
-    return createLineMarker(editor, line, placement, null, renderer);
+    return createLineMarker(editor, line, placement, null, renderer, false);
   }
 
   @NotNull
   public static List<RangeHighlighter> createLineMarker(@NotNull final Editor editor, int line, @NotNull final SeparatorPlacement placement,
-                                                        @Nullable TextDiffType type, @NotNull LineSeparatorRenderer renderer) {
+                                                        @Nullable TextDiffType type, @NotNull LineSeparatorRenderer renderer, boolean applied) {
     // We won't use addLineHighlighter as it will fail to add marker into empty document.
     //RangeHighlighter highlighter = editor.getMarkupModel().addLineHighlighter(line, HighlighterLayer.SELECTION - 1, null);
 
@@ -311,7 +344,7 @@ public class DiffDrawUtil {
     highlighter.setLineSeparatorPlacement(placement);
     highlighter.setLineSeparatorRenderer(renderer);
 
-    if (type == null) return Collections.singletonList(highlighter);
+    if (type == null || applied) return Collections.singletonList(highlighter);
 
     TextAttributes stripeAttributes = getStripeTextAttributes(type, editor);
     RangeHighlighter stripeHighlighter = editor.getMarkupModel()
index 3589116171a7731262b9552cafb29d3fbcb1f21a..1da4f7dab9f82e17f1efa3adaf5ae700960a1495 100644 (file)
@@ -26,17 +26,22 @@ import java.awt.*;
 public class DiffLineMarkerRenderer implements LineMarkerRenderer {
   @NotNull private final TextDiffType myDiffType;
   private final boolean myIgnoredFoldingOutline;
+  private final boolean myResolved;
 
   public DiffLineMarkerRenderer(@NotNull TextDiffType diffType) {
     this(diffType, false);
   }
 
   public DiffLineMarkerRenderer(@NotNull TextDiffType diffType, boolean ignoredFoldingOutline) {
+    this(diffType, ignoredFoldingOutline, false);
+  }
+
+  public DiffLineMarkerRenderer(@NotNull TextDiffType diffType, boolean ignoredFoldingOutline, boolean resolved) {
     myDiffType = diffType;
     myIgnoredFoldingOutline = ignoredFoldingOutline;
+    myResolved = resolved;
   }
 
-
   @Override
   public void paint(Editor editor, Graphics g, Rectangle range) {
     Color color = myDiffType.getColor(editor);
@@ -49,26 +54,28 @@ public class DiffLineMarkerRenderer implements LineMarkerRenderer {
     int height = range.height;
 
     if (height > 2) {
-      if (myIgnoredFoldingOutline) {
-        int xOutline = gutter.getWhitespaceSeparatorOffset();
+      if (!myResolved) {
+        if (myIgnoredFoldingOutline) {
+          int xOutline = gutter.getWhitespaceSeparatorOffset();
 
-        g.setColor(myDiffType.getIgnoredColor(editor));
-        g.fillRect(xOutline, y, x2 - xOutline, height);
+          g.setColor(myDiffType.getIgnoredColor(editor));
+          g.fillRect(xOutline, y, x2 - xOutline, height);
 
-        g.setColor(color);
-        g.fillRect(x1, y, xOutline - x1, height);
-      }
-      else {
-        g.setColor(color);
-        g.fillRect(x1, y, x2 - x1, height);
+          g.setColor(color);
+          g.fillRect(x1, y, xOutline - x1, height);
+        }
+        else {
+          g.setColor(color);
+          g.fillRect(x1, y, x2 - x1, height);
+        }
       }
-      DiffDrawUtil.drawChunkBorderLine(g2, x1, x2, y - 1, color);
-      DiffDrawUtil.drawChunkBorderLine(g2, x1, x2, y + height - 1, color);
+      DiffDrawUtil.drawChunkBorderLine(g2, x1, x2, y - 1, color, false, myResolved);
+      DiffDrawUtil.drawChunkBorderLine(g2, x1, x2, y + height - 1, color, false, myResolved);
     }
     else {
       // range is empty - insertion or deletion
       // Draw 2 pixel line in that case
-      DiffDrawUtil.drawDoubleChunkBorderLine(g2, x1, x2, y - 1, color);
+      DiffDrawUtil.drawChunkBorderLine(g2, x1, x2, y - 1, color, true, myResolved);
     }
   }
 }
index de07ba96268dbd68e68a641a4da307fbb63030d3..065cbdc4af5009e35d71c52080fdc5a467e62d7a 100644 (file)
@@ -20,4 +20,5 @@ public interface DiffPlaces {
   String CHANGES_VIEW = "ChangesView";
   String COMMIT_DIALOG = "CommitDialog";
   String TESTS_FAILED_ASSERTIONS = "TestsFiledAssertions";
+  String MERGE = "Merge";
 }
index 23a30d141232a5eb0a0be41de884ca3200ae3f02..176f47f6ff274095403d0030a26ae43fb545cb54 100644 (file)
@@ -38,6 +38,7 @@ import com.intellij.openapi.actionSystem.DefaultActionGroup;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.application.impl.LaterInvocator;
 import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.command.UndoConfirmationPolicy;
 import com.intellij.openapi.components.StoragePathMacros;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.diff.DiffBundle;
@@ -72,10 +73,7 @@ import com.intellij.util.LineSeparator;
 import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.ui.JBUI;
 import com.intellij.util.ui.UIUtil;
-import org.jetbrains.annotations.CalledInAwt;
-import org.jetbrains.annotations.NonNls;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.*;
 
 import javax.swing.*;
 import java.awt.*;
@@ -245,6 +243,10 @@ public class DiffUtil {
   // UI
   //
 
+  public static void registerAction(@NotNull AnAction action, @NotNull JComponent component) {
+    action.registerCustomShortcutSet(action.getShortcutSet(), component);
+  }
+
   @NotNull
   public static JPanel createMessagePanel(@NotNull String message) {
     Pair<JPanel, JLabel> pair = createMessagePanel();
@@ -715,18 +717,62 @@ public class DiffUtil {
   // Writable
   //
 
+  public static abstract class DiffCommandAction implements Runnable {
+    @Nullable protected final Project myProject;
+    @NotNull protected final Document myDocument;
+    @Nullable private final String myCommandName;
+    @Nullable private final String myCommandGroupId;
+    @NotNull private final UndoConfirmationPolicy myConfirmationPolicy;
+
+    public DiffCommandAction(@Nullable Project project,
+                             @NotNull Document document,
+                             @Nullable String commandName) {
+      this(project, document, commandName, null, UndoConfirmationPolicy.DEFAULT);
+    }
+
+    public DiffCommandAction(@Nullable Project project,
+                             @NotNull Document document,
+                             @Nullable String commandName,
+                             @Nullable String commandGroupId,
+                             @NotNull UndoConfirmationPolicy confirmationPolicy) {
+      myDocument = document;
+      myProject = project;
+      myCommandName = commandName;
+      myCommandGroupId = commandGroupId;
+      myConfirmationPolicy = confirmationPolicy;
+    }
+
+    @CalledInAwt
+    public final void run() {
+      if (!makeWritable(myProject, myDocument)) return;
+
+      ApplicationManager.getApplication().runWriteAction(new Runnable() {
+        public void run() {
+          CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+            @Override
+            public void run() {
+              execute();
+            }
+          }, myCommandName, myCommandGroupId, myConfirmationPolicy, myDocument);
+        }
+      });
+    }
+
+    @CalledWithWriteLock
+    protected abstract void execute();
+  }
+
   @CalledInAwt
   public static void executeWriteCommand(@NotNull final Document document,
                                          @Nullable final Project project,
                                          @Nullable final String name,
                                          @NotNull final Runnable task) {
-    if (!makeWritable(project, document)) return;
-
-    ApplicationManager.getApplication().runWriteAction(new Runnable() {
-      public void run() {
-        CommandProcessor.getInstance().executeCommand(project, task, name, null);
+    new DiffCommandAction(project, document, name) {
+      @Override
+      protected void execute() {
+        task.run();
       }
-    });
+    }.run();
   }
 
   public static boolean isEditable(@NotNull Editor editor) {
diff --git a/platform/icons/src/diff/applyNotConflictsLeft.png b/platform/icons/src/diff/applyNotConflictsLeft.png
new file mode 100644 (file)
index 0000000..dc8d5b2
Binary files /dev/null and b/platform/icons/src/diff/applyNotConflictsLeft.png differ
diff --git a/platform/icons/src/diff/applyNotConflictsLeft@2x.png b/platform/icons/src/diff/applyNotConflictsLeft@2x.png
new file mode 100644 (file)
index 0000000..996899a
Binary files /dev/null and b/platform/icons/src/diff/applyNotConflictsLeft@2x.png differ
diff --git a/platform/icons/src/diff/applyNotConflictsLeft@2x_dark.png b/platform/icons/src/diff/applyNotConflictsLeft@2x_dark.png
new file mode 100644 (file)
index 0000000..07fd5c7
Binary files /dev/null and b/platform/icons/src/diff/applyNotConflictsLeft@2x_dark.png differ
diff --git a/platform/icons/src/diff/applyNotConflictsLeft_dark.png b/platform/icons/src/diff/applyNotConflictsLeft_dark.png
new file mode 100644 (file)
index 0000000..0fb8f52
Binary files /dev/null and b/platform/icons/src/diff/applyNotConflictsLeft_dark.png differ
diff --git a/platform/icons/src/diff/applyNotConflictsRight.png b/platform/icons/src/diff/applyNotConflictsRight.png
new file mode 100644 (file)
index 0000000..ac824b7
Binary files /dev/null and b/platform/icons/src/diff/applyNotConflictsRight.png differ
diff --git a/platform/icons/src/diff/applyNotConflictsRight@2x.png b/platform/icons/src/diff/applyNotConflictsRight@2x.png
new file mode 100644 (file)
index 0000000..ac89f53
Binary files /dev/null and b/platform/icons/src/diff/applyNotConflictsRight@2x.png differ
diff --git a/platform/icons/src/diff/applyNotConflictsRight@2x_dark.png b/platform/icons/src/diff/applyNotConflictsRight@2x_dark.png
new file mode 100644 (file)
index 0000000..acd14a8
Binary files /dev/null and b/platform/icons/src/diff/applyNotConflictsRight@2x_dark.png differ
diff --git a/platform/icons/src/diff/applyNotConflictsRight_dark.png b/platform/icons/src/diff/applyNotConflictsRight_dark.png
new file mode 100644 (file)
index 0000000..e7e0c13
Binary files /dev/null and b/platform/icons/src/diff/applyNotConflictsRight_dark.png differ
index ebafed364bb0f245adc7f87c2f0ccf4fba5aad6a..ac5da0212f83143842eaba4bd35995b10eeb39d2 100644 (file)
@@ -57,6 +57,7 @@ import com.intellij.psi.search.scope.packageSet.NamedScope;
 import com.intellij.psi.search.scope.packageSet.NamedScopesHolder;
 import com.intellij.psi.search.scope.packageSet.PackageSet;
 import com.intellij.util.ArrayUtil;
+import com.intellij.util.containers.HashMap;
 import com.intellij.util.diff.FilesTooBigForDiffException;
 import com.intellij.util.ui.UIUtil;
 import gnu.trove.THashMap;
@@ -446,24 +447,15 @@ public class ColorAndFontOptions extends SearchableConfigurable.Parent.Abstract
     public NewColorAndFontPanel createPanel(@NotNull ColorAndFontOptions options) {
       final DiffOptionsPanel optionsPanel = new DiffOptionsPanel(options);
       SchemesPanel schemesPanel = new SchemesPanel(options);
-      PreviewPanel previewPanel;
-      try {
-        final DiffPreviewPanel diffPreviewPanel = new DiffPreviewPanel(myDisposable);
-        diffPreviewPanel.setMergeRequest(null);
-        schemesPanel.addListener(new ColorAndFontSettingsListener.Abstract(){
-          @Override
-          public void schemeChanged(final Object source) {
-            diffPreviewPanel.setColorScheme(getSelectedScheme());
-            optionsPanel.updateOptionsList();
-            diffPreviewPanel.updateView();
-          }
-        } );
-        previewPanel = diffPreviewPanel;
-      }
-      catch (FilesTooBigForDiffException e) {
-        LOG.info(e);
-        previewPanel = new PreviewPanel.Empty();
-      }
+      final DiffPreviewPanel previewPanel = new DiffPreviewPanel(myDisposable);
+
+      schemesPanel.addListener(new ColorAndFontSettingsListener.Abstract() {
+        @Override
+        public void schemeChanged(final Object source) {
+          previewPanel.setColorScheme(getSelectedScheme());
+          optionsPanel.updateOptionsList();
+        }
+      });
 
       return new NewColorAndFontPanel(schemesPanel, optionsPanel, previewPanel, DIFF_GROUP, null, null);
     }
index 7be3f929697885864ed17cd909bba9a295e6c75a..95aba9c74913dd503e211a7d9c707898a5ad8093 100644 (file)
@@ -18,126 +18,136 @@ package com.intellij.openapi.diff.impl.settings;
 
 import com.intellij.application.options.colors.ColorAndFontSettingsListener;
 import com.intellij.application.options.colors.PreviewPanel;
+import com.intellij.diff.DiffContentFactory;
+import com.intellij.diff.DiffContext;
+import com.intellij.diff.contents.DiffContent;
+import com.intellij.diff.requests.ContentDiffRequest;
+import com.intellij.diff.tools.simple.SimpleThreesideDiffChange;
+import com.intellij.diff.tools.simple.SimpleThreesideDiffViewer;
+import com.intellij.diff.tools.util.base.TextDiffSettingsHolder;
+import com.intellij.diff.util.DiffUtil;
+import com.intellij.diff.util.ThreeSide;
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.diff.DiffBundle;
-import com.intellij.openapi.diff.DiffContent;
-import com.intellij.openapi.diff.DiffRequest;
-import com.intellij.openapi.diff.impl.incrementalMerge.Change;
-import com.intellij.openapi.diff.impl.incrementalMerge.MergeList;
-import com.intellij.openapi.diff.impl.incrementalMerge.MergeSearchHelper;
-import com.intellij.openapi.diff.impl.incrementalMerge.ui.EditorPlace;
-import com.intellij.openapi.diff.impl.incrementalMerge.ui.MergePanel2;
+import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.LogicalPosition;
 import com.intellij.openapi.editor.colors.EditorColorsScheme;
 import com.intellij.openapi.editor.event.*;
+import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.fileTypes.FileType;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
 import com.intellij.util.EventDispatcher;
-import com.intellij.util.diff.FilesTooBigForDiffException;
+import com.intellij.util.containers.ContainerUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import javax.swing.*;
 import java.awt.*;
+import java.util.List;
 
 /**
  * The panel from the Settings, that allows to see changes to diff/merge coloring scheme right away.
  */
 public class DiffPreviewPanel implements PreviewPanel {
-  private final MergePanel2.AsComponent myMergePanelComponent;
-  private final JPanel myPanel = new JPanel(new BorderLayout());
+  private final SimpleThreesideDiffViewer myViewer;
 
   private final EventDispatcher<ColorAndFontSettingsListener> myDispatcher = EventDispatcher.create(ColorAndFontSettingsListener.class);
 
   public DiffPreviewPanel(@NotNull Disposable parent) {
-    myMergePanelComponent = new MergePanel2.AsComponent(parent);
-    myPanel.add(myMergePanelComponent, BorderLayout.CENTER);
-    myMergePanelComponent.setToolbarEnabled(false);
-    MergePanel2 mergePanel = getMergePanel();
-    mergePanel.setScrollToFirstDiff(false);
-
-    for (int i = 0; i < MergePanel2.EDITORS_COUNT; i++) {
-      final EditorMouseListener motionListener = new EditorMouseListener(i);
-      final EditorClickListener clickListener = new EditorClickListener(i);
-      mergePanel.getEditorPlace(i).addListener(new EditorPlace.EditorListener() {
-        @Override
-        public void onEditorCreated(EditorPlace place) {
-          Editor editor = place.getEditor();
-          editor.addEditorMouseMotionListener(motionListener);
-          editor.addEditorMouseListener(clickListener);
-          editor.getCaretModel().addCaretListener(clickListener);
-        }
-
-        @Override
-        public void onEditorReleased(Editor releasedEditor) {
-          releasedEditor.removeEditorMouseMotionListener(motionListener);
-          releasedEditor.removeEditorMouseListener(clickListener);
-        }
-      });
-      Editor editor = mergePanel.getEditor(i);
-      if (editor != null) {
-        editor.addEditorMouseMotionListener(motionListener);
-        editor.addEditorMouseListener(clickListener);
-      }
+    myViewer = new SimpleThreesideDiffViewer(new SampleContext(), new SampleRequest());
+    myViewer.init();
+    Disposer.register(parent, myViewer);
+
+    for (ThreeSide side : ThreeSide.values()) {
+      final EditorMouseListener motionListener = new EditorMouseListener(side);
+      final EditorClickListener clickListener = new EditorClickListener(side);
+      Editor editor = myViewer.getEditor(side);
+      editor.addEditorMouseMotionListener(motionListener);
+      editor.addEditorMouseListener(clickListener);
+      editor.getCaretModel().addCaretListener(clickListener);
     }
   }
 
   @Override
   public Component getPanel() {
-    return myPanel;
+    return myViewer.getComponent();
   }
 
   @Override
   public void updateView() {
-    MergeList mergeList = getMergePanel().getMergeList();
-    if (mergeList != null) mergeList.updateMarkup();
-    myMergePanelComponent.repaint();
-
+    List<SimpleThreesideDiffChange> changes = myViewer.getChanges();
+    for (SimpleThreesideDiffChange change : changes) {
+      change.destroyHighlighter();
+      change.installHighlighter();
+    }
   }
 
-  public void setMergeRequest(@Nullable Project project) throws FilesTooBigForDiffException {
-    getMergePanel().setDiffRequest(new SampleMerge(project));
+  public void setColorScheme(final EditorColorsScheme highlighterSettings) {
+    for (EditorEx editorEx : myViewer.getEditors()) {
+      editorEx.setColorsScheme(highlighterSettings);
+    }
   }
 
-  private MergePanel2 getMergePanel() {
-    return myMergePanelComponent.getMergePanel();
-  }
+  private static class SampleRequest extends ContentDiffRequest {
+    private final List<DiffContent> myContents;
 
-  public void setColorScheme(final EditorColorsScheme highlighterSettings) {
-    getMergePanel().setColorScheme(highlighterSettings);
-    getMergePanel().setHighlighterSettings(highlighterSettings);
-  }
+    public SampleRequest() {
+      com.intellij.openapi.diff.DiffContent[] contents = DiffPreviewProvider.getContents();
+      myContents = ContainerUtil.list(convert(contents[0]), convert(contents[1]), convert(contents[2]));
+    }
 
-  private class EditorMouseListener extends EditorMouseMotionAdapter {
-    private final int myIndex;
+    private static DiffContent convert(@NotNull com.intellij.openapi.diff.DiffContent content) {
+      Document document = content.getDocument();
+      FileType fileType = content.getContentType();
+      return DiffContentFactory.getInstance().create(null, document, fileType);
+    }
 
-    private EditorMouseListener(int index) {
-      myIndex = index;
+    @NotNull
+    @Override
+    public List<DiffContent> getContents() {
+      return myContents;
     }
 
+    @NotNull
     @Override
-    public void mouseMoved(EditorMouseEvent e) {
-      MergePanel2 mergePanel = getMergePanel();
-      Editor editor = mergePanel.getEditor(myIndex);
-      if (MergeSearchHelper.findChangeAt(e, mergePanel, myIndex) != null) EditorUtil.setHandCursor(editor);
+    public List<String> getContentTitles() {
+      return ContainerUtil.list(null, null, null);
+    }
+
+    @Nullable
+    @Override
+    public String getTitle() {
+      return DiffBundle.message("merge.color.options.dialog.title");
     }
   }
 
-  public static class SampleMerge extends DiffRequest {
-    public SampleMerge(@Nullable Project project) {
-      super(project);
+  private static class SampleContext extends DiffContext {
+    public SampleContext() {
+      TextDiffSettingsHolder.TextDiffSettings settings = new TextDiffSettingsHolder.TextDiffSettings();
+      putUserData(TextDiffSettingsHolder.KEY, settings);
     }
 
+    @Nullable
     @Override
-    @NotNull
-    public DiffContent[] getContents() {
-      return DiffPreviewProvider.getContents();
+    public Project getProject() {
+      return null;
     }
 
     @Override
-    public String[] getContentTitles() { return new String[]{"", "", ""}; }
+    public boolean isWindowFocused() {
+      return false;
+    }
+
     @Override
-    public String getWindowTitle() { return DiffBundle.message("merge.color.options.dialog.title"); }
+    public boolean isFocused() {
+      return false;
+    }
+
+    @Override
+    public void requestFocus() {
+    }
   }
 
   @Override
@@ -145,42 +155,71 @@ public class DiffPreviewPanel implements PreviewPanel {
     myDispatcher.addListener(listener);
   }
 
+  private class EditorMouseListener extends EditorMouseMotionAdapter {
+    @NotNull private final ThreeSide mySide;
+
+    private EditorMouseListener(@NotNull ThreeSide side) {
+      mySide = side;
+    }
+
+    @Override
+    public void mouseMoved(EditorMouseEvent e) {
+      if (getChange(mySide, e) != null) EditorUtil.setHandCursor(e.getEditor());
+    }
+  }
+
   private class EditorClickListener extends EditorMouseAdapter implements CaretListener {
-    private final int myIndex;
+    @NotNull private final ThreeSide mySide;
 
-    private EditorClickListener(int i) {
-      myIndex = i;
+    private EditorClickListener(@NotNull ThreeSide side) {
+      mySide = side;
     }
 
     @Override
     public void mouseClicked(EditorMouseEvent e) {
-      select(MergeSearchHelper.findChangeAt(e, getMergePanel(), myIndex));
+      selectChange(getChange(mySide, e));
     }
 
-    private void select(Change change) {
-      if (change == null) return;
-      myDispatcher.getMulticaster().selectionInPreviewChanged(change.getType().getTextDiffType().getDisplayName());
-     }
-
     @Override
     public void caretPositionChanged(CaretEvent e) {
-      select(MergeSearchHelper.findChangeAt(e, getMergePanel(), myIndex));
+      selectChange(getChange(mySide, e.getNewPosition().line));
     }
 
     @Override
     public void caretAdded(CaretEvent e) {
-
     }
 
     @Override
     public void caretRemoved(CaretEvent e) {
+    }
+  }
+
+  private void selectChange(@Nullable SimpleThreesideDiffChange change) {
+    if (change == null) return;
+    myDispatcher.getMulticaster().selectionInPreviewChanged(change.getDiffType().getName());
+  }
+
+  @Nullable
+  private SimpleThreesideDiffChange getChange(ThreeSide side, EditorMouseEvent e) {
+    EditorEx editor = myViewer.getEditor(side);
+    LogicalPosition logicalPosition = editor.xyToLogicalPosition(e.getMouseEvent().getPoint());
+    int offset = editor.logicalPositionToOffset(logicalPosition);
+    int line = editor.getDocument().getLineNumber(offset);
+    return getChange(side, line);
+  }
 
+  @Nullable
+  private SimpleThreesideDiffChange getChange(ThreeSide side, int line) {
+    for (SimpleThreesideDiffChange change : myViewer.getChanges()) {
+      int startLine = change.getStartLine(side);
+      int endLine = change.getEndLine(side);
+      if (DiffUtil.isSelectedByLine(line, startLine, endLine)) return change;
     }
+    return null;
   }
 
   @Override
   public void blinkSelectedHighlightType(final Object selected) {
-    
   }
 
   @Override
index d1d6cf6cb9f577aadca6ecbdba070df8e9d4695e..05d2a87ab4c12b41cec5e25a12d5ceea3cf40428 100644 (file)
@@ -23,6 +23,7 @@ import org.jetbrains.annotations.Nullable;
 /**
  * @author Konstantin Bulenkov
  */
+@Deprecated
 public class MergeApplication extends ApplicationStarterBase {
   public MergeApplication() {
     super("merge", 3 ,4);
index 87f5f81c783fca4f5301d6556fa13b7f3e2956a4..3c48edd309d63af3d2ab3d2e7fe4c133048ab981 100644 (file)
  */
 package com.intellij.openapi.diff.actions;
 
+import com.intellij.diff.DiffManager;
+import com.intellij.diff.DiffRequestFactory;
+import com.intellij.diff.InvalidDiffRequestException;
+import com.intellij.diff.merge.MergeRequest;
+import com.intellij.diff.util.DiffUserDataKeys;
 import com.intellij.openapi.actionSystem.AnAction;
 import com.intellij.openapi.actionSystem.AnActionEvent;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.actionSystem.PlatformDataKeys;
-import com.intellij.openapi.diff.*;
-import com.intellij.openapi.project.Project;
+import com.intellij.openapi.diff.DiffBundle;
 import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
 import com.intellij.openapi.ui.Messages;
-import com.intellij.openapi.util.text.LineTokenizer;
-import com.intellij.openapi.vfs.VfsUtil;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.containers.ContainerUtil;
 
-import java.io.IOException;
+import java.util.List;
 
 public class MergeFilesAction extends AnAction implements DumbAware {
   public void update(AnActionEvent e) {
@@ -53,37 +56,24 @@ public class MergeFilesAction extends AnAction implements DumbAware {
 
     DiffRequestFactory diffRequestFactory = DiffRequestFactory.getInstance();
 
-    VirtualFile file = files[1];
     try {
-      String originalText = createValidContent(VfsUtil.loadText(file));
-      String leftText = VfsUtil.loadText(files[0]);
-      String rightText = VfsUtil.loadText(files[2]);
-
       Project project = CommonDataKeys.PROJECT.getData(context);
-      final MergeRequest diffData = diffRequestFactory.createMergeRequest(leftText, rightText, originalText, file, project,
-                                                                          ActionButtonPresentation.APPLY,
-                                                                          ActionButtonPresentation.CANCEL_WITH_PROMPT);
-      diffData.setVersionTitles(new String[]{files[0].getPresentableUrl(),
-                                             files[1].getPresentableUrl(),
-                                             files[2].getPresentableUrl()});
-      diffData.setWindowTitle(DiffBundle.message("merge.files.dialog.title"));
-      diffData.setHelpId("cvs.merge");
-      DiffManager.getInstance().getDiffTool().show(diffData);
-    }
-    catch (IOException e1) {
-      Messages.showErrorDialog(DiffBundle.message("merge.dialog.cannot.load.file.error.message", e1.getLocalizedMessage()),
-                               DiffBundle.message("merge.files.dialog.title"));
+
+      String title = DiffBundle.message("merge.files.dialog.title");
+      List<String> titles = ContainerUtil.list(files[0].getPresentableUrl(),
+                                               files[1].getPresentableUrl(),
+                                               files[2].getPresentableUrl());
+
+      VirtualFile outputFile = files[1];
+      List<VirtualFile> contents = ContainerUtil.list(files[0], files[1], files[2]);
+
+      MergeRequest request = diffRequestFactory.createMergeRequestFromFiles(project, outputFile, contents, title, titles, null);
+      request.putUserData(DiffUserDataKeys.HELP_ID, "cvs.merge");
+
+      DiffManager.getInstance().showMerge(project, request);
     }
-  }
-  private static String createValidContent(String str) {
-    String[] strings = LineTokenizer.tokenize(str.toCharArray(), false, false);
-    StringBuffer result = new StringBuffer();
-    for (int i = 0; i < strings.length; i++) {
-      String string = strings[i];
-      if (i != 0) result.append('\n');
-      result.append(string);
+    catch (InvalidDiffRequestException err) {
+      Messages.showErrorDialog(err.getLocalizedMessage(), DiffBundle.message("merge.files.dialog.title"));
     }
-    return result.toString();
   }
-
 }