From 0458527b1c3a8a772c5cc3479daca30af9a9da9c Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Tue, 25 Aug 2015 18:55:58 +0300 Subject: [PATCH] vcs: use custom MergeTool for patch conflicts without base revision --- .../com/intellij/diff/DiffContentFactory.java | 3 + .../intellij/diff/DiffContentFactoryImpl.java | 7 +- .../src/META-INF/VcsExtensions.xml | 2 + .../actions/TestDiffRequestProcessor.java | 2 +- .../vcs/changes/patch/ApplyPatchAction.java | 64 ++----- .../changes/patch/ApplyPatchMergeRequest.java | 138 +++++++++++++++ .../changes/patch/ApplyPatchMergeTool.java | 161 ++++++++++++++++++ 7 files changed, 326 insertions(+), 51 deletions(-) create mode 100644 platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeRequest.java create mode 100644 platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeTool.java diff --git a/platform/diff-api/src/com/intellij/diff/DiffContentFactory.java b/platform/diff-api/src/com/intellij/diff/DiffContentFactory.java index bd41446c1c1f..8dd004c11532 100644 --- a/platform/diff-api/src/com/intellij/diff/DiffContentFactory.java +++ b/platform/diff-api/src/com/intellij/diff/DiffContentFactory.java @@ -50,6 +50,9 @@ public abstract class DiffContentFactory { @NotNull public abstract DocumentContent create(@NotNull String text, @Nullable FileType type, boolean respectLineSeparators); + @NotNull + public abstract DocumentContent create(@NotNull String text, @Nullable VirtualFile highlightFile); + @NotNull public abstract DocumentContent create(@Nullable Project project, @NotNull Document document); diff --git a/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java b/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java index 653017dcf568..d6f207e2ad93 100644 --- a/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java +++ b/platform/diff-impl/src/com/intellij/diff/DiffContentFactoryImpl.java @@ -58,7 +58,7 @@ public class DiffContentFactoryImpl extends DiffContentFactory { @Override @NotNull public DocumentContent create(@NotNull String text) { - return create(text, null); + return create(text, (FileType)null); } @Override @@ -73,6 +73,11 @@ public class DiffContentFactoryImpl extends DiffContentFactory { return createImpl(text, type, null, null, respectLineSeparators, true); } + @NotNull + public DocumentContent create(@NotNull String text, @Nullable VirtualFile highlightFile) { + return createImpl(text, highlightFile != null ? highlightFile.getFileType() : null, highlightFile, null, true, true); + } + @Override @NotNull public DocumentContent create(@Nullable Project project, @NotNull Document document) { diff --git a/platform/platform-resources/src/META-INF/VcsExtensions.xml b/platform/platform-resources/src/META-INF/VcsExtensions.xml index a76f7f43f371..d07e6da2c252 100644 --- a/platform/platform-resources/src/META-INF/VcsExtensions.xml +++ b/platform/platform-resources/src/META-INF/VcsExtensions.xml @@ -26,6 +26,8 @@ + + diff --git a/platform/testRunner/src/com/intellij/execution/testframework/actions/TestDiffRequestProcessor.java b/platform/testRunner/src/com/intellij/execution/testframework/actions/TestDiffRequestProcessor.java index aff16950c712..9e8aeb14e34e 100644 --- a/platform/testRunner/src/com/intellij/execution/testframework/actions/TestDiffRequestProcessor.java +++ b/platform/testRunner/src/com/intellij/execution/testframework/actions/TestDiffRequestProcessor.java @@ -86,7 +86,7 @@ public class TestDiffRequestProcessor extends DiffRequestProcessor { } else { title = ExecutionBundle.message(titleKey); - content = DiffContentFactory.getInstance().create(contentString, null); + content = DiffContentFactory.getInstance().create(contentString); } return Pair.create(title, content); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java index 3a2a8ad5aefb..8c77f259577e 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchAction.java @@ -22,7 +22,10 @@ */ package com.intellij.openapi.vcs.changes.patch; -import com.intellij.diff.*; +import com.intellij.diff.DiffContentFactory; +import com.intellij.diff.DiffManager; +import com.intellij.diff.DiffRequestFactory; +import com.intellij.diff.InvalidDiffRequestException; import com.intellij.diff.chains.DiffRequestProducerException; import com.intellij.diff.contents.DiffContent; import com.intellij.diff.contents.DocumentContent; @@ -47,8 +50,6 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.WindowWrapper; -import com.intellij.openapi.util.BooleanGetter; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.Getter; import com.intellij.openapi.util.Ref; @@ -59,7 +60,6 @@ import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vcs.changes.CommitContext; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.EditorNotificationPanel; import com.intellij.util.Consumer; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; @@ -254,52 +254,24 @@ public class ApplyPatchAction extends DumbAwareAction { final Document document = FileDocumentManager.getInstance().getDocument(file); if (texts.getLocal() == null || document == null) return ApplyPatchStatus.FAILURE; - final CharSequence oldContent = document.getImmutableCharSequence(); - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - document.setText(texts.getPatched()); - } - }); - final String fullPath = file.getParent() == null ? file.getPath() : file.getParent().getPath(); final String windowTitle = "Result Of Patch Apply To " + file.getName() + " (" + fullPath + ")"; - final List titles = ContainerUtil.list(VcsBundle.message("diff.title.local"), "Patched (with problems)"); - final DiffContentFactory contentFactory = DiffContentFactory.getInstance(); - final DocumentContent originalContent = contentFactory.create(texts.getLocal().toString(), file.getFileType()); - final DiffContent mergedContent = contentFactory.create(project, document); - - final List contents = ContainerUtil.list(originalContent, mergedContent); - - final DiffRequest request = new SimpleDiffRequest(windowTitle, contents, titles); - DiffUtil.addNotification(new DiffIsApproximateNotification(), request); - - final DiffDialogHints dialogHints = new DiffDialogHints(WindowWrapper.Mode.MODAL); - dialogHints.setCancelAction(new BooleanGetter() { + final Ref successRef = new Ref(); + Consumer callback = new Consumer() { @Override - public boolean get() { - ApplicationManager.getApplication().runWriteAction(new Runnable() { - @Override - public void run() { - document.setText(oldContent); - FileDocumentManager.getInstance().saveDocument(document); - } - }); - return true; - } - }); - dialogHints.setOkAction(new BooleanGetter() { - @Override - public boolean get() { + public void consume(MergeResult result) { FileDocumentManager.getInstance().saveDocument(document); - return true; + successRef.set(true); } - }); + }; - DiffManager.getInstance().showDiff(project, request, dialogHints); + MergeRequest request = new ApplyPatchMergeRequest(project, document, file, texts.getLocal().toString(), texts.getPatched(), windowTitle, + VcsBundle.message("diff.title.local"), "Patched (with problems)", callback); - return ApplyPatchStatus.SUCCESS; + DiffManager.getInstance().showMerge(project, request); + + return successRef.get() == Boolean.TRUE ? ApplyPatchStatus.SUCCESS : ApplyPatchStatus.FAILURE; } @NotNull @@ -322,14 +294,8 @@ public class ApplyPatchAction extends DumbAwareAction { final List contents = ContainerUtil.list(localContent, mergedContent); final DiffRequest request = new SimpleDiffRequest(windowTitle, contents, titles); - DiffUtil.addNotification(new DiffIsApproximateNotification(), request); + DiffUtil.addNotification(new ApplyPatchMergeTool.DiffIsApproximateNotification(), request); return request; } - - private static class DiffIsApproximateNotification extends EditorNotificationPanel { - public DiffIsApproximateNotification() { - myLabel.setText("Couldn't find context for patch. Some fragments were applied at the best possible place. Please check carefully."); - } - } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeRequest.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeRequest.java new file mode 100644 index 000000000000..63cb798a746f --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeRequest.java @@ -0,0 +1,138 @@ +/* + * 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.openapi.vcs.changes.patch; + +import com.intellij.diff.merge.MergeRequest; +import com.intellij.diff.merge.MergeResult; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class ApplyPatchMergeRequest extends MergeRequest { + @Nullable private final Project myProject; + @NotNull private final Document myDocument; + + @NotNull private final CharSequence myOriginalContent; + @NotNull private final String myLocalContent; + @NotNull private final String myPatchedContent; + + @NotNull private final String myWindowTitle; + @NotNull private final String myLocalTitle; + @NotNull private final String myPatchedTitle; + + @Nullable private final Consumer myCallback; + + public ApplyPatchMergeRequest(@Nullable Project project, + @NotNull Document document, + @NotNull String localContent, + @NotNull String patchedContent, + @NotNull String windowTitle, + @NotNull String localTitle, + @NotNull String patchedTitle, + @Nullable Consumer callback) { + myProject = project; + myDocument = document; + + myOriginalContent = ApplicationManager.getApplication().runReadAction(new Computable() { + @Override + public CharSequence compute() { + return myDocument.getImmutableCharSequence(); + } + }); + myLocalContent = localContent; + myPatchedContent = patchedContent; + + myWindowTitle = windowTitle; + myLocalTitle = localTitle; + myPatchedTitle = patchedTitle; + + myCallback = callback; + } + + @Nullable + public Project getProject() { + return myProject; + } + + @NotNull + public Document getDocument() { + return myDocument; + } + + @NotNull + public String getLocalContent() { + return myLocalContent; + } + + @NotNull + public String getPatchedContent() { + return myPatchedContent; + } + + @Nullable + @Override + public String getTitle() { + return myWindowTitle; + } + + @NotNull + public String getLocalTitle() { + return myLocalTitle; + } + + @NotNull + public String getPatchedTitle() { + return myPatchedTitle; + } + + @Override + public void applyResult(@NotNull MergeResult result) { + final CharSequence applyContent; + switch (result) { + case CANCEL: + applyContent = myOriginalContent; + break; + case LEFT: + applyContent = myLocalContent; + break; + case RIGHT: + applyContent = myPatchedContent; + break; + case RESOLVED: + applyContent = null; + break; + default: + throw new IllegalArgumentException(result.name()); + } + + if (applyContent != null) { + new WriteCommandAction.Simple(myProject) { + @Override + protected void run() throws Throwable { + myDocument.setText(applyContent); + } + }.execute(); + } + + if (myCallback != null) myCallback.consume(result); + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeTool.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeTool.java new file mode 100644 index 000000000000..2e4e0ce44d1b --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchMergeTool.java @@ -0,0 +1,161 @@ +/* + * 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.openapi.vcs.changes.patch; + +import com.intellij.diff.DiffContentFactory; +import com.intellij.diff.FrameDiffTool; +import com.intellij.diff.contents.DocumentContent; +import com.intellij.diff.merge.*; +import com.intellij.diff.requests.SimpleDiffRequest; +import com.intellij.diff.tools.simple.SimpleDiffViewer; +import com.intellij.diff.util.DiffUtil; +import com.intellij.openapi.command.undo.DocumentReference; +import com.intellij.openapi.command.undo.DocumentReferenceManager; +import com.intellij.openapi.command.undo.UndoManager; +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.ui.Messages; +import com.intellij.openapi.util.BooleanGetter; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.EditorNotificationPanel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +public class ApplyPatchMergeTool implements MergeTool { + @NotNull + @Override + public MergeViewer createComponent(@NotNull MergeContext context, @NotNull MergeRequest request) { + return new MyViewer(context, (ApplyPatchMergeRequest)request); + } + + @Override + public boolean canShow(@NotNull MergeContext context, @NotNull MergeRequest request) { + return request instanceof ApplyPatchMergeRequest; + } + + private static class MyViewer implements MergeViewer { + @NotNull private final MergeContext myMergeContext; + @NotNull private final ApplyPatchMergeRequest myMergeRequest; + + @NotNull private final SimpleDiffViewer myViewer; + + public MyViewer(@NotNull MergeContext context, @NotNull ApplyPatchMergeRequest request) { + myMergeContext = context; + myMergeRequest = request; + + MergeUtil.ProxyDiffContext diffContext = new MergeUtil.ProxyDiffContext(myMergeContext); + + VirtualFile file = FileDocumentManager.getInstance().getFile(myMergeRequest.getDocument()); + final DiffContentFactory contentFactory = DiffContentFactory.getInstance(); + final DocumentContent localContent = contentFactory.create(myMergeRequest.getLocalContent(), file); + final DocumentContent mergedContent = contentFactory.create(myMergeRequest.getProject(), myMergeRequest.getDocument()); + + SimpleDiffRequest diffRequest = new SimpleDiffRequest(myMergeRequest.getTitle(), localContent, mergedContent, + myMergeRequest.getLocalTitle(), myMergeRequest.getPatchedTitle()); + DiffUtil.addNotification(new DiffIsApproximateNotification(), diffRequest); + + myViewer = new SimpleDiffViewer(diffContext, diffRequest); + } + + @NotNull + @Override + public JComponent getComponent() { + return myViewer.getComponent(); + } + + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return myViewer.getPreferredFocusedComponent(); + } + + @Override + public ToolbarComponents init() { + final Project project = myMergeContext.getProject(); + final Document document = myMergeRequest.getDocument(); + + DiffUtil.executeWriteCommand(document, project, "Init merge content", new Runnable() { + @Override + public void run() { + document.setText(myMergeRequest.getPatchedContent()); + + UndoManager undoManager = project != null ? UndoManager.getInstance(project) : UndoManager.getGlobalInstance(); + if (undoManager != null) { + DocumentReference ref = DocumentReferenceManager.getInstance().create(document); + undoManager.nonundoableActionPerformed(ref, false); + } + } + }); + + 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.LEFT || result == MergeResult.RIGHT) 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); + } + } + + public static class DiffIsApproximateNotification extends EditorNotificationPanel { + public DiffIsApproximateNotification() { + myLabel.setText("Couldn't find context for patch. Some fragments were applied at the best possible place. " + + "Please check carefully."); + } + } +}