From be35049bdeac2485e34d060ed760bbd9858067e6 Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Thu, 13 Apr 2017 20:06:31 +0300 Subject: [PATCH] [patch]: create patch content viewer and diff tool * show patch content as a diff if no local file was found for shelved change (IDEA-166238, IDEA-155689); --- .../src/META-INF/VcsExtensions.xml | 1 + .../changes/patch/tool/PatchDiffRequest.java | 43 ++++ .../vcs/changes/patch/tool/PatchDiffTool.java | 183 ++++++++++++++++++ .../shelf/DiffShelvedChangesAction.java | 30 ++- 4 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/tool/PatchDiffRequest.java create mode 100644 platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/tool/PatchDiffTool.java diff --git a/platform/platform-resources/src/META-INF/VcsExtensions.xml b/platform/platform-resources/src/META-INF/VcsExtensions.xml index c22b90ae306f..157d455862e6 100644 --- a/platform/platform-resources/src/META-INF/VcsExtensions.xml +++ b/platform/platform-resources/src/META-INF/VcsExtensions.xml @@ -34,6 +34,7 @@ + diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/tool/PatchDiffRequest.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/tool/PatchDiffRequest.java new file mode 100644 index 000000000000..332c1d49c945 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/tool/PatchDiffRequest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2017 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.tool; + +import com.intellij.diff.requests.DiffRequest; +import com.intellij.openapi.vcs.changes.patch.AppliedTextPatch; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class PatchDiffRequest extends DiffRequest { + @NotNull private final AppliedTextPatch myAppliedPatch; + + @Nullable private final String myWindowTitle; + + public PatchDiffRequest(@NotNull AppliedTextPatch patch, @Nullable String windowTitle) { + myAppliedPatch = patch; + myWindowTitle = windowTitle; + } + + @Nullable + @Override + public String getTitle() { + return myWindowTitle; + } + + @NotNull + public AppliedTextPatch getPatch() { + return myAppliedPatch; + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/tool/PatchDiffTool.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/tool/PatchDiffTool.java new file mode 100644 index 000000000000..757dfc849d25 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/tool/PatchDiffTool.java @@ -0,0 +1,183 @@ +/* + * Copyright 2000-2017 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.tool; + +import com.intellij.diff.DiffContext; +import com.intellij.diff.FrameDiffTool; +import com.intellij.diff.requests.DiffRequest; +import com.intellij.diff.tools.util.DiffDataKeys; +import com.intellij.diff.tools.util.PrevNextDifferenceIterableBase; +import com.intellij.diff.tools.util.SimpleDiffPanel; +import com.intellij.diff.util.DiffDrawUtil; +import com.intellij.diff.util.DiffUtil; +import com.intellij.diff.util.LineRange; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.DataProvider; +import com.intellij.openapi.application.WriteAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.BooleanGetter; +import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.ui.components.panels.Wrapper; +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.List; + +public class PatchDiffTool implements FrameDiffTool { + @NotNull + @Override + public String getName() { + return "Patch content viewer"; + } + + @Override + public boolean canShow(@NotNull DiffContext context, @NotNull DiffRequest request) { + return request instanceof PatchDiffRequest; + } + + @NotNull + @Override + public DiffViewer createComponent(@NotNull DiffContext context, @NotNull DiffRequest request) { + return new MyPatchViewer(context, (PatchDiffRequest)request); + } + + private static class MyPatchViewer implements DiffViewer, DataProvider { + + + private final Project myProject; + private final SimpleDiffPanel myPanel; + private final EditorEx myEditor; + private final DiffContext myContext; + private final PatchDiffRequest myRequest; + private final MyPrevNextDifferenceIterable myPrevNextDifferenceIterable; + private final List myHunks = new ArrayList<>(); + + public MyPatchViewer(DiffContext context, PatchDiffRequest request) { + myProject = context.getProject(); + myContext = context; + myRequest = request; + Document document = EditorFactory.getInstance().createDocument(""); + myEditor = DiffUtil.createEditor(document, myProject, true, true); + myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable(); + + Wrapper editorPanel = new Wrapper(new BorderLayout(0, DiffUtil.TITLE_GAP), myEditor.getComponent()); + editorPanel.add(DiffUtil.createTitle(VcsBundle.message("patch.apply.conflict.patch")), BorderLayout.NORTH); + myPanel = new SimpleDiffPanel(editorPanel, this, context); + } + + @NotNull + @Override + public JComponent getComponent() { + return myPanel; + } + + @Nullable + @Override + public JComponent getPreferredFocusedComponent() { + return myEditor.getContentComponent(); + } + + @NotNull + @Override + public ToolbarComponents init() { + myPanel.setPersistentNotifications(DiffUtil.getCustomNotifications(myContext, myRequest)); + onInit(); + return new FrameDiffTool.ToolbarComponents(); + } + + @Override + public void dispose() { + EditorFactory.getInstance().releaseEditor(myEditor); + } + + private void onInit() { + PatchChangeBuilder builder = new PatchChangeBuilder(); + builder.exec(myRequest.getPatch().getHunks()); + myHunks.addAll(builder.getHunks()); + + Document patchDocument = myEditor.getDocument(); + WriteAction.run(() -> patchDocument.setText(builder.getPatchContent().toString())); + + myEditor.getGutterComponentEx() + .setLineNumberConvertor(builder.getLineConvertor1().createConvertor(), builder.getLineConvertor2().createConvertor()); + + for (int line : builder.getSeparatorLines().toNativeArray()) { + int offset = patchDocument.getLineStartOffset(line); + DiffDrawUtil.createLineSeparatorHighlighter(myEditor, offset, offset, BooleanGetter.TRUE); + } + // highlighting + for (PatchChangeBuilder.Hunk hunk : myHunks) { + DiffDrawUtil.createUnifiedChunkHighlighters(myEditor, hunk.getPatchDeletionRange(), hunk.getPatchInsertionRange(), null); + } + } + + @Nullable + @Override + public Object getData(@NonNls String dataId) { + if (CommonDataKeys.PROJECT.is(dataId)) return myProject; + if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) return myPrevNextDifferenceIterable; + if (DiffDataKeys.CURRENT_EDITOR.is(dataId)) return myEditor; + if (DiffDataKeys.CURRENT_CHANGE_RANGE.is(dataId)) { + return myPrevNextDifferenceIterable.getHunkRangeByLine(myEditor.getCaretModel().getLogicalPosition().line); + } + return null; + } + + private class MyPrevNextDifferenceIterable extends PrevNextDifferenceIterableBase { + @NotNull + @Override + protected List getChanges() { + return myHunks; + } + + @NotNull + @Override + protected EditorEx getEditor() { + return myEditor; + } + + @Override + protected int getStartLine(@NotNull PatchChangeBuilder.Hunk change) { + return change.getPatchDeletionRange().start; + } + + @Override + protected int getEndLine(@NotNull PatchChangeBuilder.Hunk change) { + return change.getPatchInsertionRange().end; + } + + @Nullable + LineRange getHunkRangeByLine(int line) { + for (PatchChangeBuilder.Hunk hunk : getChanges()) { + int start = hunk.getPatchDeletionRange().start; + int end = hunk.getPatchInsertionRange().end; + if (start <= line && end > line) { + return new LineRange(start, end); + } + if (start > line) return null; + } + return null; + } + } + } +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/DiffShelvedChangesAction.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/DiffShelvedChangesAction.java index 34f02a38687a..67d8ca187ea5 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/DiffShelvedChangesAction.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/shelf/DiffShelvedChangesAction.java @@ -26,7 +26,6 @@ import com.intellij.diff.contents.DiffContent; import com.intellij.diff.requests.DiffRequest; import com.intellij.diff.requests.SimpleDiffRequest; import com.intellij.diff.requests.UnknownFileTypeDiffRequest; -import com.intellij.diff.tools.util.DiffNotifications; import com.intellij.diff.util.DiffUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -34,6 +33,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.diff.impl.patch.*; import com.intellij.openapi.diff.impl.patch.apply.ApplyFilePatchBase; +import com.intellij.openapi.diff.impl.patch.apply.GenericPatchApplier; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileTypes.UnknownFileType; import com.intellij.openapi.progress.ProcessCanceledException; @@ -51,7 +51,9 @@ import com.intellij.openapi.vcs.changes.ChangeListManager; import com.intellij.openapi.vcs.changes.CommitContext; import com.intellij.openapi.vcs.changes.FilePathsHelper; import com.intellij.openapi.vcs.changes.actions.diff.ChangeGoToChangePopupAction; +import com.intellij.openapi.vcs.changes.patch.AppliedTextPatch; import com.intellij.openapi.vcs.changes.patch.ApplyPatchForBaseRevisionTexts; +import com.intellij.openapi.vcs.changes.patch.tool.PatchDiffRequest; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; import com.intellij.util.containers.ContainerUtil; @@ -64,6 +66,7 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.util.*; +import static com.intellij.diff.tools.util.DiffNotifications.createNotification; import static com.intellij.openapi.vcs.changes.patch.PatchDiffRequestFactory.createConflictDiffRequest; import static com.intellij.openapi.vcs.changes.patch.PatchDiffRequestFactory.createDiffRequest; import static com.intellij.util.ObjectUtils.assertNotNull; @@ -188,7 +191,16 @@ public class DiffShelvedChangesAction extends AnAction implements DumbAware { @Override public DiffRequest process(@NotNull UserDataHolder context, @NotNull ProgressIndicator indicator) throws DiffRequestProducerException, ProcessCanceledException { - throw new DiffRequestProducerException("Cannot find base for '" + (beforePath != null ? beforePath : afterPath) + "'"); + try { + TextFilePatch patch = preloader.getPatch(shelvedChange, new CommitContext()); + PatchDiffRequest patchDiffRequest = new PatchDiffRequest(createAppliedTextPatch(patch), getName()); + DiffUtil.addNotification(createNotification("Cannot find local file for '" + chooseNotNull(beforePath, afterPath) + "'"), + patchDiffRequest); + return patchDiffRequest; + } + catch (VcsException e) { + throw new DiffRequestProducerException("Can't show diff for '" + getName() + "'", e); + } } }); continue; @@ -261,9 +273,7 @@ public class DiffShelvedChangesAction extends AnAction implements DumbAware { ? createConflictDiffRequest(project, file, patch, SHELVED_VERSION, texts, getName()) : createDiffRequest(project, shelvedChange.getChange(project), getName(), context, indicator); if (!withLocal) { - DiffUtil - .addNotification(DiffNotifications.createNotification(DIFF_WITH_BASE_ERROR + " Showing difference with local version"), - diffRequest); + DiffUtil.addNotification(createNotification(DIFF_WITH_BASE_ERROR + " Showing difference with local version"), diffRequest); } return diffRequest; } @@ -272,6 +282,16 @@ public class DiffShelvedChangesAction extends AnAction implements DumbAware { } } + /** + * Simple way to reuse patch parser from GPA -> apply onto empty text + */ + @NotNull + private static AppliedTextPatch createAppliedTextPatch(@NotNull TextFilePatch patch) { + final GenericPatchApplier applier = new GenericPatchApplier("", patch.getHunks()); + applier.execute(); + return AppliedTextPatch.create(applier.getAppliedInfo()); + } + private static class PatchesPreloader { private final Map> myFilePatchesMap; private final Project myProject;