[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);
This commit is contained in:
Nadya Zabrodina
2017-05-17 17:22:54 +03:00
parent 8b481db96f
commit be35049bde
4 changed files with 252 additions and 5 deletions
@@ -34,6 +34,7 @@
<diff.merge.MergeTool implementation="com.intellij.openapi.vcs.changes.patch.tool.ApplyPatchMergeTool"/>
<diff.DiffTool implementation="com.intellij.openapi.vcs.changes.patch.tool.ApplyPatchDiffTool"/>
<diff.DiffTool implementation="com.intellij.openapi.vcs.changes.patch.tool.PatchDiffTool"/>
<selectInTarget implementation="com.intellij.openapi.vcs.changes.SelectInChangesViewTarget"/>
@@ -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;
}
}
@@ -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<PatchChangeBuilder.Hunk> 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<PatchChangeBuilder.Hunk> {
@NotNull
@Override
protected List<PatchChangeBuilder.Hunk> 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;
}
}
}
}
@@ -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<String, List<TextFilePatch>> myFilePatchesMap;
private final Project myProject;