patch: apply-somehow - rework dialog

* make it more similar to the merge conflict resolve dialog
* allow editing in diff viewer
This commit is contained in:
Aleksey Pivovarov
2016-05-12 13:31:40 +03:00
committed by Aleksey Pivovarov
parent 14f8e50af2
commit 6e3d8650db
19 changed files with 1116 additions and 455 deletions
@@ -53,6 +53,9 @@ public abstract class DiffContentFactory {
@NotNull
public abstract DocumentContent create(@NotNull String text, @Nullable VirtualFile highlightFile);
@NotNull
public abstract DocumentContent create(@NotNull String text, @Nullable DocumentContent referent);
@NotNull
public abstract DocumentContent create(@Nullable Project project, @NotNull Document document);
@@ -62,6 +65,9 @@ public abstract class DiffContentFactory {
@NotNull
public abstract DocumentContent create(@Nullable Project project, @NotNull Document document, @Nullable VirtualFile file);
@NotNull
public abstract DocumentContent create(@NotNull Document document, @Nullable DocumentContent referent);
@NotNull
public abstract DiffContent create(@Nullable Project project, @NotNull VirtualFile file);
@@ -81,6 +81,20 @@ public class DiffContentFactoryImpl extends DiffContentFactory {
return createImpl(text, highlightFile != null ? highlightFile.getFileType() : null, highlightFile, null, true, true);
}
@NotNull
@Override
public DocumentContent create(@NotNull String text, @Nullable DocumentContent referent) {
if (referent == null) return create(text);
return createImpl(text, referent.getContentType(), referent.getHighlightFile(), null, false, true);
}
@NotNull
@Override
public DocumentContent create(@NotNull Document document, @Nullable DocumentContent referent) {
if (referent == null) return new DocumentContentImpl(document);
return new DocumentContentImpl(document, referent.getContentType(), referent.getHighlightFile(), null, null);
}
@Override
@NotNull
public DocumentContent create(@Nullable Project project, @NotNull Document document) {
@@ -39,7 +39,7 @@ import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
abstract class MergeModelBase<S extends MergeModelBase.State> implements Disposable {
public abstract class MergeModelBase<S extends MergeModelBase.State> implements Disposable {
public static final Logger LOG = Logger.getInstance(MergeModelBase.class);
@Nullable private final Project myProject;
@@ -40,9 +40,6 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.command.UndoConfirmationPolicy;
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.Caret;
import com.intellij.openapi.editor.Document;
@@ -54,12 +51,8 @@ import com.intellij.openapi.progress.util.ProgressWindow;
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.JBPopupFactory;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.HyperlinkAdapter;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.Alarm;
import com.intellij.util.containers.ContainerUtil;
@@ -68,8 +61,6 @@ import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.*;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.*;
@@ -300,12 +291,7 @@ public class TextMergeViewer implements MergeTool.MergeViewer {
DiffUtil.executeWriteCommand(outputDocument, getProject(), "Init merge content", () -> {
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);
}
DiffUtil.putNonundoableOperation(getProject(), outputDocument);
});
}
@@ -581,26 +567,16 @@ public class TextMergeViewer implements MergeTool.MergeViewer {
LOG.assertTrue(getFirstUnresolvedChange(true, null) == null);
ApplicationManager.getApplication().invokeLater(() -> {
if (isDisposed()) return;
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) {
if (isDisposed()) return;
destroyChangedBlocks();
myMergeContext.finishMerge(MergeResult.RESOLVED);
}
};
JComponent component = getEditor().getComponent();
Point point = new Point(component.getWidth() / 2, JBUI.scale(5));
Color bgColor = MessageType.INFO.getPopupBackground();
RelativePoint point = new RelativePoint(component, new Point(component.getWidth() / 2, JBUI.scale(5)));
Balloon balloon = JBPopupFactory.getInstance()
.createHtmlTextBalloonBuilder(message, null, bgColor, listener)
.setAnimationCycle(200)
.createBalloon();
balloon.show(new RelativePoint(component, point), Balloon.Position.below);
Disposer.register(this, balloon);
String message = DiffBundle.message("merge.all.changes.processed.message.text");
DiffUtil.showSuccessPopup(message, point, this, () -> {
if (isDisposed()) return;
destroyChangedBlocks();
myMergeContext.finishMerge(MergeResult.RESOLVED);
});
});
}
}
@@ -15,7 +15,6 @@
*/
package com.intellij.diff.tools.fragmented;
import com.intellij.diff.fragments.DiffFragment;
import com.intellij.diff.fragments.LineFragment;
import com.intellij.diff.util.*;
import com.intellij.diff.util.DiffUtil.UpdatedLineRange;
@@ -77,12 +76,7 @@ public class UnifiedDiffChange {
private void installHighlighter(@NotNull LineRange deleted, @NotNull LineRange inserted) {
assert myHighlighters.isEmpty();
if (myLineFragment.getInnerFragments() != null) {
doInstallHighlighterWithInner(deleted, inserted);
}
else {
doInstallHighlighterSimple(deleted, inserted);
}
doInstallHighlighters(deleted, inserted);
doInstallActionHighlighters();
}
@@ -99,35 +93,8 @@ public class UnifiedDiffChange {
}
}
private void doInstallHighlighterSimple(@NotNull LineRange deleted, @NotNull LineRange inserted) {
createLineHighlighters(deleted, inserted, false);
}
private void doInstallHighlighterWithInner(@NotNull LineRange deleted, @NotNull LineRange inserted) {
List<DiffFragment> innerFragments = myLineFragment.getInnerFragments();
assert innerFragments != null;
int deletedStartOffset = myEditor.getDocument().getLineStartOffset(deleted.start);
int insertedStartOffset = myEditor.getDocument().getLineStartOffset(inserted.start);
createLineHighlighters(deleted, inserted, true);
for (DiffFragment fragment : innerFragments) {
createInlineHighlighter(TextDiffType.DELETED,
deletedStartOffset + fragment.getStartOffset1(),
deletedStartOffset + fragment.getEndOffset1());
createInlineHighlighter(TextDiffType.INSERTED,
insertedStartOffset + fragment.getStartOffset2(),
insertedStartOffset + fragment.getEndOffset2());
}
}
private void createLineHighlighters(@NotNull LineRange deleted, @NotNull LineRange inserted, boolean ignored) {
myHighlighters.addAll(DiffDrawUtil.createUnifiedChunkHighlighters(myEditor, deleted, inserted, ignored));
}
private void createInlineHighlighter(@NotNull TextDiffType type, int start, int end) {
myHighlighters.addAll(DiffDrawUtil.createInlineHighlighter(myEditor, start, end, type));
private void doInstallHighlighters(@NotNull LineRange deleted, @NotNull LineRange inserted) {
myHighlighters.addAll(DiffDrawUtil.createUnifiedChunkHighlighters(myEditor, deleted, inserted, myLineFragment.getInnerFragments()));
}
public int getLine1() {
@@ -66,19 +66,20 @@ public class TextEditorHolder extends EditorHolder {
// Build
//
@NotNull
public static TextEditorHolder create(@Nullable Project project, @NotNull DocumentContent content) {
EditorEx editor = DiffUtil.createEditor(content.getDocument(), project, false, true);
DiffUtil.configureEditor(editor, content, project);
return new TextEditorHolder(editor);
}
public static class TextEditorHolderFactory extends EditorHolderFactory<TextEditorHolder> {
public static TextEditorHolderFactory INSTANCE = new TextEditorHolderFactory();
@Override
@NotNull
public TextEditorHolder create(@NotNull DiffContent content, @NotNull DiffContext context) {
if (!(content instanceof DocumentContent)) throw new IllegalArgumentException(content.toString());
Project project = context.getProject();
DocumentContent documentContent = (DocumentContent)content;
EditorEx editor = DiffUtil.createEditor(documentContent.getDocument(), project, false, true);
DiffUtil.configureEditor(editor, documentContent, project);
return new TextEditorHolder(editor);
return TextEditorHolder.create(context.getProject(), (DocumentContent)content);
}
@Override
@@ -15,6 +15,7 @@
*/
package com.intellij.diff.util;
import com.intellij.diff.fragments.DiffFragment;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.colors.EditorColors;
@@ -242,7 +243,9 @@ public class DiffDrawUtil {
public static List<RangeHighlighter> createUnifiedChunkHighlighters(@NotNull Editor editor,
@NotNull LineRange deleted,
@NotNull LineRange inserted,
boolean ignored) {
@Nullable List<DiffFragment> innerFragments) {
boolean ignored = innerFragments != null;
List<RangeHighlighter> list = new ArrayList<>();
if (!inserted.isEmpty() && !deleted.isEmpty()) {
list.addAll(createLineMarker(editor, deleted.start, TextDiffType.DELETED, SeparatorPlacement.TOP));
@@ -260,6 +263,22 @@ public class DiffDrawUtil {
list.addAll(createHighlighter(editor, deleted.start, deleted.end, TextDiffType.DELETED, ignored));
list.addAll(createLineMarker(editor, deleted.end - 1, TextDiffType.DELETED, SeparatorPlacement.BOTTOM));
}
if (innerFragments != null) {
int deletedStartOffset = editor.getDocument().getLineStartOffset(deleted.start);
int insertedStartOffset = editor.getDocument().getLineStartOffset(inserted.start);
for (DiffFragment fragment : innerFragments) {
int deletedWordStart = deletedStartOffset + fragment.getStartOffset1();
int deletedWordEnd = deletedStartOffset + fragment.getEndOffset1();
list.addAll(createInlineHighlighter(editor, deletedWordStart, deletedWordEnd, TextDiffType.DELETED));
int insertedWordStart = insertedStartOffset + fragment.getStartOffset2();
int insertedWordEnd = insertedStartOffset + fragment.getEndOffset2();
list.addAll(createInlineHighlighter(editor, insertedWordStart, insertedWordEnd, TextDiffType.INSERTED));
}
}
return list;
}
@@ -40,11 +40,15 @@ import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.lang.Language;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
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.command.undo.DocumentReference;
import com.intellij.openapi.command.undo.DocumentReferenceManager;
import com.intellij.openapi.command.undo.UndoManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.diff.impl.GenericDataProvider;
@@ -60,13 +64,17 @@ import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.PlainTextFileType;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.ui.DialogWrapperDialog;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.WindowWrapper;
import com.intellij.openapi.ui.popup.Balloon;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
@@ -75,11 +83,10 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.impl.IdeFrameImpl;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.JBColor;
import com.intellij.ui.ScreenUtil;
import com.intellij.ui.*;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ArrayUtil;
import com.intellij.util.DocumentUtil;
import com.intellij.util.LineSeparator;
import com.intellij.util.containers.ContainerUtil;
@@ -90,11 +97,11 @@ import org.jetbrains.annotations.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections;
import java.util.*;
import java.util.List;
public class DiffUtil {
@@ -129,7 +136,7 @@ public class DiffUtil {
}
@Nullable
public static EditorHighlighter createEditorHighlighter(@Nullable Project project, @NotNull DocumentContent content) {
private static EditorHighlighter createEditorHighlighter(@Nullable Project project, @NotNull DocumentContent content) {
FileType type = content.getContentType();
VirtualFile file = content.getHighlightFile();
Language language = content.getUserData(DiffUserDataKeys.LANGUAGE);
@@ -139,18 +146,19 @@ public class DiffUtil {
SyntaxHighlighter syntaxHighlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(language, project, file);
return highlighterFactory.createEditorHighlighter(syntaxHighlighter, EditorColorsManager.getInstance().getGlobalScheme());
}
else if ((file != null && file.getFileType() == type) || file instanceof LightVirtualFile) {
return highlighterFactory.createEditorHighlighter(project, file);
if (file != null) {
if ((type == null || type == PlainTextFileType.INSTANCE) || file.getFileType() == type || file instanceof LightVirtualFile) {
return highlighterFactory.createEditorHighlighter(project, file);
}
}
if (type != null) {
return highlighterFactory.createEditorHighlighter(project, type);
}
return null;
}
@NotNull
public static EditorHighlighter createEmptyEditorHighlighter() {
private static EditorHighlighter createEmptyEditorHighlighter() {
return new EmptyEditorHighlighter(EditorColorsManager.getInstance().getGlobalScheme().getAttributes(HighlighterColors.TEXT));
}
@@ -366,6 +374,30 @@ public class DiffUtil {
}
}
public static void showSuccessPopup(@NotNull String message,
@NotNull RelativePoint point,
@NotNull Disposable disposable,
@Nullable Runnable hyperlinkHandler) {
HyperlinkListener listener = null;
if (hyperlinkHandler != null) {
listener = new HyperlinkAdapter() {
@Override
protected void hyperlinkActivated(HyperlinkEvent e) {
hyperlinkHandler.run();
}
};
}
Color bgColor = MessageType.INFO.getPopupBackground();
Balloon balloon = JBPopupFactory.getInstance()
.createHtmlTextBalloonBuilder(message, null, bgColor, listener)
.setAnimationCycle(200)
.createBalloon();
balloon.show(point, Balloon.Position.below);
Disposer.register(disposable, balloon);
}
//
// Titles
//
@@ -441,7 +473,7 @@ public class DiffUtil {
@NotNull
public static JComponent createTitle(@NotNull String title) {
return createTitle(title, null, null, true);
return createTitle(title, null, null, false);
}
@NotNull
@@ -622,6 +654,31 @@ public class DiffUtil {
return ComparisonManager.getInstance().isEquals(chunk1, chunk2, comparisonPolicy);
}
@NotNull
public static <T> int[] getSortedIndexes(@NotNull List<T> values, @NotNull Comparator<T> comparator) {
final List<Integer> indexes = new ArrayList<>(values.size());
for (int i = 0; i < values.size(); i++) {
indexes.add(i);
}
ContainerUtil.sort(indexes, (i1, i2) -> {
T val1 = values.get(indexes.get(i1));
T val2 = values.get(indexes.get(i2));
return comparator.compare(val1, val2);
});
return ArrayUtil.toIntArray(indexes);
}
@NotNull
public static <T> int[] invertIndexes(@NotNull int[] indexes) {
int[] inverted = new int[indexes.length];
for (int i = 0; i < indexes.length; i++) {
inverted[indexes[i]] = i;
}
return inverted;
}
//
// Document modification
//
@@ -814,12 +871,11 @@ public class DiffUtil {
@NotNull
public static UpdatedLineRange updateRangeOnModification(int start, int end, int changeStart, int changeEnd, int shift) {
return updateRangeOnModification(start, end, changeStart, changeEnd, shift, false, false);
return updateRangeOnModification(start, end, changeStart, changeEnd, shift, false);
}
@NotNull
public static UpdatedLineRange updateRangeOnModification(int start, int end, int changeStart, int changeEnd, int shift,
boolean greedy, boolean strict) {
public static UpdatedLineRange updateRangeOnModification(int start, int end, int changeStart, int changeEnd, int shift, boolean greedy) {
if (end <= changeStart) { // change before
return new UpdatedLineRange(start, end, false);
}
@@ -828,7 +884,7 @@ public class DiffUtil {
}
if (start <= changeStart && end >= changeEnd) { // change inside
return new UpdatedLineRange(start, end + shift, strict);
return new UpdatedLineRange(start, end + shift, false);
}
// range is damaged. We don't know new boundaries.
@@ -869,28 +925,29 @@ public class DiffUtil {
public static TextDiffType getLineDiffType(@NotNull LineFragment fragment) {
boolean left = fragment.getStartLine1() != fragment.getEndLine1();
boolean right = fragment.getStartLine2() != fragment.getEndLine2();
return getType(left, right);
return getDiffType(left, right);
}
@NotNull
public static TextDiffType getDiffType(@NotNull DiffFragment fragment) {
boolean left = fragment.getEndOffset1() != fragment.getStartOffset1();
boolean right = fragment.getEndOffset2() != fragment.getStartOffset2();
return getType(left, right);
return getDiffType(left, right);
}
private static TextDiffType getType(boolean left, boolean right) {
if (left && right) {
@NotNull
public static TextDiffType getDiffType(boolean hasDeleted, boolean hasInserted) {
if (hasDeleted && hasInserted) {
return TextDiffType.MODIFIED;
}
else if (left) {
else if (hasDeleted) {
return TextDiffType.DELETED;
}
else if (right) {
else if (hasInserted) {
return TextDiffType.INSERTED;
}
else {
LOG.error("DiffFragment should not be empty");
LOG.error("Diff fragment should not be empty");
return TextDiffType.MODIFIED;
}
}
@@ -990,6 +1047,14 @@ public class DiffUtil {
return !ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(file).hasReadonlyFiles();
}
public static void putNonundoableOperation(@Nullable Project project, @NotNull Document document) {
UndoManager undoManager = project != null ? UndoManager.getInstance(project) : UndoManager.getGlobalInstance();
if (undoManager != null) {
DocumentReference ref = DocumentReferenceManager.getInstance().create(document);
undoManager.nonundoableActionPerformed(ref, false);
}
}
//
// Windows
//
@@ -22,6 +22,7 @@ import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.ui.ColorUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -106,12 +107,12 @@ public class TextDiffTypeFactory {
if (editor instanceof EditorEx) {
Color fg = attributes.getBackgroundColor();
Color bg = ((EditorEx)editor).getBackgroundColor();
return getMiddleColor(fg, bg);
return ColorUtil.mix(fg, bg, MIDDLE_COLOR_FACTOR);
}
else {
Color fg = attributes.getBackgroundColor();
Color bg = EditorColorsManager.getInstance().getGlobalScheme().getDefaultBackground();
return getMiddleColor(fg, bg);
return ColorUtil.mix(fg, bg, MIDDLE_COLOR_FACTOR);
}
}
@@ -136,14 +137,6 @@ public class TextDiffTypeFactory {
@NotNull
public static Color getMiddleColor(@NotNull Color fg, @NotNull Color bg) {
int red = avg(fg.getRed(), bg.getRed(), MIDDLE_COLOR_FACTOR);
int green = avg(fg.getGreen(), bg.getGreen(), MIDDLE_COLOR_FACTOR);
int blue = avg(fg.getBlue(), bg.getBlue(), MIDDLE_COLOR_FACTOR);
//noinspection UseJBColor
return new Color(red, green, blue);
}
private static int avg(int fg, int bg, double factor) {
return (int)(fg + Math.round(factor * (bg - fg)));
return ColorUtil.mix(fg, bg, MIDDLE_COLOR_FACTOR);
}
}
@@ -70,6 +70,7 @@ merge.dialog.all.conflicts.resolved.message.text=All conflicts resolved
merge.statistics.message={0, choice, 0#No changes|1#One change|2#{0, number} changes}. {1, choice, 0#No conflicts|1#One conflict|2#{0, number} conflicts}
merge.dialog.exit.without.applying.changes.confirmation.message=Are you sure you want to exit without applying changes?
cancel.visual.merge.dialog.title=Cancel Visual Merge
apply.somehow.status.message.all.applied=All hunks resolved
apply.somehow.status.message.already.applied={0, number} {0, choice, 1#hunk is|2#hunks are} already applied
apply.somehow.status.message.cant.apply=Can''t apply {0, number} {0, choice, 1#hunk|2#hunks}
apply.somehow.status.message.cant.apply.some=Can''t apply {0, number} of {1, number} hunks
@@ -77,6 +78,8 @@ merge.dialog.apply.partially.resolved.changes.confirmation.message=There {0, cho
apply.partially.resolved.merge.dialog.title=Apply Changes
merge.dialog.resolve.side.with.discard.title=Accept Changes
merge.dialog.resolve.side.with.discard.message=Are you sure you want to discard your changes and accept {0, choice, 0#left|1#right} side?
merge.all.changes.processed.message.text=All changes have been processed.<br><a href="">Save changes and finish merging</a>
apply.patch.all.changes.processed.message.text=All hunks have been processed.<br><a href=\"\">Save changes and finish resolve</a>
merge.all.changes.have.processed.save.and.finish.confirmation.text=All changes have been processed.\nWould you like to save changes and finish merging?
all.changes.processed.dialog.title=All Changes Processed
merge.save.and.finish.button=Save and &Finish
@@ -93,7 +93,7 @@ public class PatchDiffRequestFactory {
applier.execute();
final AppliedTextPatch appliedTextPatch = AppliedTextPatch.create(applier.getAppliedInfo());
return createBadDiffRequest(file, localContent, appliedTextPatch, null, null, null, null);
return createBadDiffRequest(project, file, localContent, appliedTextPatch, null, null, null, null);
}
else {
String localContent = texts.getLocal().toString();
@@ -133,7 +133,8 @@ public class PatchDiffRequestFactory {
}
@NotNull
public static DiffRequest createBadDiffRequest(@Nullable VirtualFile file,
public static DiffRequest createBadDiffRequest(@Nullable Project project,
@NotNull VirtualFile file,
@NotNull String localContent,
@NotNull AppliedTextPatch textPatch,
@Nullable String windowTitle,
@@ -145,7 +146,9 @@ public class PatchDiffRequestFactory {
if (resultTitle == null) resultTitle = VcsBundle.message("patch.apply.conflict.patched.somehow.version");
if (patchTitle == null) patchTitle = VcsBundle.message("patch.apply.conflict.patch");
return new ApplyPatchDiffRequest(textPatch, localContent, file, windowTitle, localTitle, resultTitle, patchTitle);
DocumentContent resultContent = DiffContentFactory.getInstance().createDocument(project, file);
if (resultContent == null) resultContent = DiffContentFactory.getInstance().create(localContent, file);
return new ApplyPatchDiffRequest(resultContent, textPatch, localContent, windowTitle, localTitle, resultTitle, patchTitle);
}
@NotNull
@@ -219,7 +222,8 @@ public class PatchDiffRequestFactory {
if (resultTitle == null) resultTitle = VcsBundle.message("patch.apply.conflict.patched.somehow.version");
if (patchTitle == null) patchTitle = VcsBundle.message("patch.apply.conflict.patch");
return new ApplyPatchMergeRequest(project, document, textPatch, localContent,
DocumentContent resultContent = DiffContentFactory.getInstance().create(project, document, file);
return new ApplyPatchMergeRequest(project, resultContent, textPatch, localContent,
windowTitle, localTitle, resultTitle, patchTitle, callback);
}
@@ -17,27 +17,34 @@ package com.intellij.openapi.vcs.changes.patch.tool;
import com.intellij.diff.comparison.ByWord;
import com.intellij.diff.comparison.ComparisonPolicy;
import com.intellij.diff.comparison.DiffTooBigException;
import com.intellij.diff.fragments.DiffFragment;
import com.intellij.diff.tools.util.SyncScrollSupport;
import com.intellij.diff.merge.MergeModelBase;
import com.intellij.diff.util.*;
import com.intellij.icons.AllIcons;
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.LogicalPosition;
import com.intellij.openapi.editor.ex.DocumentEx;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.MarkupModelEx;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.editor.markup.HighlighterLayer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.progress.DumbProgressIndicator;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vcs.changes.patch.AppliedTextPatch;
import com.intellij.openapi.vcs.changes.patch.AppliedTextPatch.HunkStatus;
import com.intellij.openapi.vcs.ex.LineStatusMarkerRenderer;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.JBColor;
import com.intellij.util.PairConsumer;
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.MouseEvent;
import java.util.ArrayList;
@@ -45,42 +52,109 @@ import java.util.List;
class ApplyPatchChange {
@NotNull private final ApplyPatchViewer myViewer;
private final int myIndex; // index in myModelChanges
@NotNull private final LineRange myPatchDeletionRange;
@NotNull private final LineRange myPatchInsertionRange;
@NotNull private final AppliedTextPatch.HunkStatus myStatus;
@NotNull private final HunkStatus myStatus;
@Nullable private final List<DiffFragment> myPatchInnerDifferences;
@NotNull private final List<MyGutterOperation> myOperations = new ArrayList<>();
@NotNull private final List<RangeHighlighter> myHighlighters = new ArrayList<>();
@Nullable private LineRange myAppliedTo;
private boolean myIsValid = true;
private boolean myResolved;
public ApplyPatchChange(@NotNull ApplyPatchViewer viewer, @NotNull PatchChangeBuilder.Hunk hunk) {
public ApplyPatchChange(@NotNull PatchChangeBuilder.Hunk hunk, int index, @NotNull ApplyPatchViewer viewer) {
myIndex = index;
myViewer = viewer;
myPatchDeletionRange = hunk.getPatchDeletionRange();
myPatchInsertionRange = hunk.getPatchInsertionRange();
myStatus = hunk.getStatus();
myAppliedTo = hunk.getAppliedToLines();
installHighlighters();
installPersistentHighlighters();
myPatchInnerDifferences = calcPatchInnerDifferences(hunk, viewer);
}
@Nullable
private static List<DiffFragment> calcPatchInnerDifferences(@NotNull PatchChangeBuilder.Hunk hunk,
@NotNull ApplyPatchViewer viewer) {
LineRange deletionRange = hunk.getPatchDeletionRange();
LineRange insertionRange = hunk.getPatchInsertionRange();
if (deletionRange.isEmpty() || insertionRange.isEmpty()) return null;
try {
DocumentEx patchDocument = viewer.getPatchEditor().getDocument();
CharSequence deleted = DiffUtil.getLinesContent(patchDocument, deletionRange.start, deletionRange.end);
CharSequence inserted = DiffUtil.getLinesContent(patchDocument, insertionRange.start, insertionRange.end);
return ByWord.compare(deleted, inserted, ComparisonPolicy.DEFAULT, DumbProgressIndicator.INSTANCE);
}
catch (DiffTooBigException ignore) {
return null;
}
}
public void reinstallHighlighters() {
destroyHighlighters();
installHighlighters();
myViewer.repaintDivider();
}
private void installHighlighters() {
createStatusHighlighter(myViewer.getPatchEditor(), myPatchDeletionRange.start, myPatchInsertionRange.end);
createResultHighlighters();
createPatchHighlighters();
createStatusHighlighter();
createOperations();
}
if (myAppliedTo != null) {
EditorEx resultEditor = myViewer.getResultEditor();
createStatusHighlighter(resultEditor, myAppliedTo.start, myAppliedTo.end);
private void createPatchHighlighters() {
EditorEx patchEditor = myViewer.getPatchEditor();
myHighlighters.addAll(DiffDrawUtil.createUnifiedChunkHighlighters(patchEditor, myPatchDeletionRange, myPatchInsertionRange,
myPatchInnerDifferences));
}
myHighlighters.addAll(DiffDrawUtil.createLineMarker(resultEditor, myAppliedTo.start, myAppliedTo.end,
TextDiffType.MODIFIED, true));
private void createResultHighlighters() {
LineRange resultRange = getResultRange();
if (resultRange == null) return;
EditorEx editor = myViewer.getResultEditor();
int startLine = resultRange.start;
int endLine = resultRange.end;
TextDiffType type = getDiffType();
boolean resolved = isRangeApplied();
myHighlighters.addAll(DiffDrawUtil.createHighlighter(editor, startLine, endLine, type, false, resolved, false));
myHighlighters.addAll(DiffDrawUtil.createLineMarker(editor, startLine, endLine, type, resolved));
}
private void createStatusHighlighter() {
int line1 = myPatchDeletionRange.start;
int line2 = myPatchInsertionRange.end;
Color color = getStatusColor();
if (isResolved()) {
color = ColorUtil.mix(color, myViewer.getPatchEditor().getGutterComponentEx().getBackground(), 0.6f);
}
String tooltip = getStatusText();
EditorEx patchEditor = myViewer.getPatchEditor();
Document document = patchEditor.getDocument();
MarkupModelEx markupModel = patchEditor.getMarkupModel();
TextRange textRange = DiffUtil.getLinesRange(document, line1, line2);
RangeHighlighter highlighter = markupModel.addRangeHighlighter(textRange.getStartOffset(), textRange.getEndOffset(),
HighlighterLayer.LAST, null, HighlighterTargetArea.LINES_IN_RANGE);
PairConsumer<Editor, MouseEvent> clickHandler = getResultRange() != null ?
(e, event) -> myViewer.scrollToChange(this, Side.RIGHT, false) :
null;
highlighter.setLineMarkerRenderer(LineStatusMarkerRenderer.createRenderer(line1, line2, color, tooltip, clickHandler));
myHighlighters.add(highlighter);
}
private void destroyHighlighters() {
@@ -88,42 +162,71 @@ class ApplyPatchChange {
highlighter.dispose();
}
myHighlighters.clear();
for (MyGutterOperation operation : myOperations) {
operation.dispose();
}
myOperations.clear();
}
private void createStatusHighlighter(@NotNull EditorEx editor, int line1, int line2) {
Color color = getStatusColor();
String tooltip = getStatusText();
//
// Getters
//
Document document = editor.getDocument();
MarkupModelEx markupModel = editor.getMarkupModel();
TextRange textRange = DiffUtil.getLinesRange(document, line1, line2);
RangeHighlighter highlighter = markupModel.addRangeHighlighter(textRange.getStartOffset(), textRange.getEndOffset(),
HighlighterLayer.LAST, null, HighlighterTargetArea.LINES_IN_RANGE);
PairConsumer<Editor, MouseEvent> clickHandler = myAppliedTo != null ? (e, event) -> handleStatusClick(editor, event) : null;
highlighter.setLineMarkerRenderer(LineStatusMarkerRenderer.createRenderer(line1, line2, color, tooltip, clickHandler));
myHighlighters.add(highlighter);
public int getIndex() {
return myIndex;
}
private void handleStatusClick(@NotNull EditorEx editor, @NotNull MouseEvent event) {
assert myAppliedTo != null;
EditorEx resultEditor = myViewer.getResultEditor();
EditorEx patchEditor = myViewer.getPatchEditor();
@NotNull
public HunkStatus getStatus() {
return myStatus;
}
Side clickSide = Side.fromLeft(editor == resultEditor);
int clickLine = clickSide.select(myAppliedTo.start, myPatchInsertionRange.start);
EditorEx clickEditor = clickSide.select(resultEditor, patchEditor);
int targetY = clickEditor.logicalPositionToXY(new LogicalPosition(clickLine, 0)).y;
int topShift = targetY - clickEditor.getScrollingModel().getVerticalScrollOffset();
@NotNull
public LineRange getPatchRange() {
return new LineRange(myPatchDeletionRange.start, myPatchInsertionRange.end);
}
int[] offsets = SyncScrollSupport.getTargetOffsets(resultEditor, patchEditor,
myAppliedTo.start, myAppliedTo.end,
myPatchInsertionRange.start, myPatchInsertionRange.end,
topShift);
DiffUtil.scrollToPoint(resultEditor, new Point(0, offsets[0]), false);
DiffUtil.scrollToPoint(patchEditor, new Point(0, offsets[1]), false);
@NotNull
public LineRange getPatchAffectedRange() {
return isRangeApplied() ? myPatchInsertionRange : myPatchDeletionRange;
}
@NotNull
public LineRange getPatchDeletionRange() {
return myPatchDeletionRange;
}
@NotNull
public LineRange getPatchInsertionRange() {
return myPatchInsertionRange;
}
@Nullable
public LineRange getResultRange() {
ApplyPatchViewer.MyModel model = myViewer.getModel();
int lineStart = model.getLineStart(myIndex);
int lineEnd = model.getLineEnd(myIndex);
if (lineStart != -1 || lineEnd != -1) return new LineRange(lineStart, lineEnd);
return null;
}
public boolean isResolved() {
return myResolved;
}
public void setResolved(boolean resolved) {
myResolved = resolved;
}
@NotNull
public TextDiffType getDiffType() {
return DiffUtil.getDiffType(!myPatchDeletionRange.isEmpty(), !myPatchInsertionRange.isEmpty());
}
public boolean isRangeApplied() {
return myResolved || getStatus() == HunkStatus.ALREADY_APPLIED;
}
@NotNull
@@ -154,76 +257,127 @@ class ApplyPatchChange {
}
}
private void installPersistentHighlighters() {
EditorEx patchEditor = myViewer.getPatchEditor();
DocumentEx patchDocument = patchEditor.getDocument();
//
// Operations
//
if (myPatchDeletionRange.isEmpty() || myPatchInsertionRange.isEmpty()) {
DiffDrawUtil.createUnifiedChunkHighlighters(patchEditor, myPatchDeletionRange, myPatchInsertionRange, false);
private void createOperations() {
if (myViewer.isReadOnly()) return;
if (isResolved()) return;
if (myStatus == HunkStatus.EXACTLY_APPLIED) {
ContainerUtil.addIfNotNull(myOperations, createOperation(OperationType.APPLY));
}
else {
DiffDrawUtil.createUnifiedChunkHighlighters(patchEditor, myPatchDeletionRange, myPatchInsertionRange, true);
ContainerUtil.addIfNotNull(myOperations, createOperation(OperationType.IGNORE));
}
CharSequence deleted = DiffUtil.getLinesContent(patchDocument, myPatchDeletionRange.start, myPatchDeletionRange.end);
CharSequence inserted = DiffUtil.getLinesContent(patchDocument, myPatchInsertionRange.start, myPatchInsertionRange.end);
List<DiffFragment> wordDiff = ByWord.compare(deleted, inserted, ComparisonPolicy.DEFAULT, DumbProgressIndicator.INSTANCE);
@Nullable
private MyGutterOperation createOperation(@NotNull OperationType type) {
if (isResolved()) return null;
int deletionStartShift = patchDocument.getLineStartOffset(myPatchDeletionRange.start);
int insertionStartShift = patchDocument.getLineStartOffset(myPatchInsertionRange.start);
for (DiffFragment fragment : wordDiff) {
int deletedWordStart = deletionStartShift + fragment.getStartOffset1();
int deletedWordEnd = deletionStartShift + fragment.getEndOffset1();
DiffDrawUtil.createInlineHighlighter(patchEditor, deletedWordStart, deletedWordEnd, TextDiffType.DELETED);
EditorEx editor = myViewer.getPatchEditor();
Document document = editor.getDocument();
int insertedWordStart = insertionStartShift + fragment.getStartOffset2();
int insertedWordEndEnd = insertionStartShift + fragment.getEndOffset2();
DiffDrawUtil.createInlineHighlighter(patchEditor, insertedWordStart, insertedWordEndEnd, TextDiffType.INSERTED);
int line = getPatchRange().start;
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(highlighter, type);
}
private class MyGutterOperation {
@NotNull private final RangeHighlighter myHighlighter;
@NotNull private final OperationType myType;
private MyGutterOperation(@NotNull RangeHighlighter highlighter, @NotNull OperationType type) {
myHighlighter = highlighter;
myType = type;
myHighlighter.setGutterIconRenderer(createRenderer());
}
public void dispose() {
myHighlighter.dispose();
}
@Nullable
public GutterIconRenderer createRenderer() {
switch (myType) {
case APPLY:
return createApplyRenderer();
case IGNORE:
return createIgnoreRenderer();
default:
throw new IllegalArgumentException(myType.name());
}
}
}
//
// Getters
//
@NotNull
public LineRange getPatchDeletionRange() {
return myPatchDeletionRange;
}
@NotNull
public LineRange getPatchInsertionRange() {
return myPatchInsertionRange;
}
@NotNull
public AppliedTextPatch.HunkStatus getStatus() {
return myStatus;
@Nullable
private GutterIconRenderer createApplyRenderer() {
return createIconRenderer(DiffBundle.message("merge.dialog.apply.change.action.name"), DiffUtil.getArrowIcon(Side.RIGHT), () -> {
myViewer.executeCommand("Accept change", () -> {
myViewer.replaceChange(this);
});
});
}
@Nullable
public LineRange getAppliedTo() {
return myAppliedTo;
private GutterIconRenderer createIgnoreRenderer() {
return createIconRenderer(DiffBundle.message("merge.dialog.ignore.change.action.name"), AllIcons.Diff.Remove, () -> {
myViewer.executeCommand("Ignore change", () -> {
myViewer.markChangeResolved(this);
});
});
}
public boolean isValid() {
return myIsValid;
@Nullable
private static GutterIconRenderer createIconRenderer(@NotNull final String text,
@NotNull final Icon icon,
@NotNull final Runnable perform) {
final String tooltipText = DiffUtil.createTooltipText(text, null);
return new DiffGutterRenderer(icon, tooltipText) {
@Override
protected void performAction(AnActionEvent e) {
perform.run();
}
};
}
private enum OperationType {
APPLY, IGNORE
}
//
// Shift
// State
//
public boolean processChange(int oldLine1, int oldLine2, int shift) {
if (myAppliedTo == null) return false;
int line1 = myAppliedTo.start;
int line2 = myAppliedTo.end;
@NotNull
public State storeState() {
LineRange resultRange = getResultRange();
return new State(
myIndex,
resultRange != null ? resultRange.start : -1,
resultRange != null ? resultRange.end : -1,
myResolved);
}
DiffUtil.UpdatedLineRange newRange = DiffUtil.updateRangeOnModification(line1, line2, oldLine1, oldLine2, shift, true, true);
public void restoreState(@NotNull State state) {
myResolved = state.myResolved;
}
myAppliedTo = new LineRange(newRange.startLine, newRange.endLine);
if (newRange.damaged) myIsValid = false;
public static class State extends MergeModelBase.State {
private final boolean myResolved;
return newRange.damaged || myAppliedTo.start != line1 || myAppliedTo.end != line2;
public State(int index,
int startLine,
int endLine,
boolean resolved) {
super(index, startLine, endLine);
myResolved = resolved;
}
}
}
@@ -15,48 +15,52 @@
*/
package com.intellij.openapi.vcs.changes.patch.tool;
import com.intellij.diff.contents.DocumentContent;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.openapi.vcs.changes.patch.AppliedTextPatch;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ApplyPatchDiffRequest extends DiffRequest {
public class ApplyPatchDiffRequest extends DiffRequest implements ApplyPatchRequest {
@NotNull private final DocumentContent myResultContent;
@NotNull private final AppliedTextPatch myAppliedPatch;
@NotNull private final String myLocalContent;
@Nullable private final VirtualFile myHighlightFile;
@Nullable private final String myWindowTitle;
@NotNull private final String myLocalTitle;
@NotNull private final String myResultTitle;
@NotNull private final String myPatchTitle;
public ApplyPatchDiffRequest(@NotNull AppliedTextPatch appliedPatch,
public ApplyPatchDiffRequest(@NotNull DocumentContent resultContent,
@NotNull AppliedTextPatch appliedPatch,
@NotNull String localContent,
@Nullable VirtualFile highlightFile,
@Nullable String windowTitle,
@NotNull String localTitle,
@NotNull String resultTitle,
@NotNull String patchTitle) {
myResultContent = resultContent;
myAppliedPatch = appliedPatch;
myLocalContent = localContent;
myHighlightFile = highlightFile;
myWindowTitle = windowTitle;
myLocalTitle = localTitle;
myResultTitle = resultTitle;
myPatchTitle = patchTitle;
}
@Override
@NotNull
public DocumentContent getResultContent() {
return myResultContent;
}
@Override
@NotNull
public String getLocalContent() {
return myLocalContent;
}
@Nullable
public VirtualFile getHighlightFile() {
return myHighlightFile;
}
@Override
@NotNull
public AppliedTextPatch getPatch() {
return myAppliedPatch;
@@ -68,16 +72,19 @@ public class ApplyPatchDiffRequest extends DiffRequest {
return myWindowTitle;
}
@Override
@NotNull
public String getLocalTitle() {
return myLocalTitle;
}
@Override
@NotNull
public String getResultTitle() {
return myResultTitle;
}
@Override
@NotNull
public String getPatchTitle() {
return myPatchTitle;
@@ -40,13 +40,19 @@ public class ApplyPatchDiffTool implements FrameDiffTool {
private static class MyApplyPatchViewer extends ApplyPatchViewer implements DiffViewer {
public MyApplyPatchViewer(@NotNull DiffContext context, @NotNull ApplyPatchDiffRequest request) {
super(context, request, null);
super(context, request);
}
@NotNull
@Override
public ToolbarComponents init() {
return super.doInit();
initPatchViewer();
ToolbarComponents components = new ToolbarComponents();
components.statusPanel = getStatusPanel();
components.toolbarActions = createToolbarActions();
return components;
}
}
}
@@ -15,11 +15,11 @@
*/
package com.intellij.openapi.vcs.changes.patch.tool;
import com.intellij.diff.contents.DocumentContent;
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.vcs.changes.patch.AppliedTextPatch;
@@ -27,13 +27,13 @@ import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ApplyPatchMergeRequest extends MergeRequest {
public class ApplyPatchMergeRequest extends MergeRequest implements ApplyPatchRequest {
@Nullable private final Project myProject;
@NotNull private final Document myDocument;
@NotNull private final CharSequence myOriginalContent;
@NotNull private final DocumentContent myResultContent;
@NotNull private final AppliedTextPatch myAppliedPatch;
@NotNull private final CharSequence myOriginalContent;
@NotNull private final String myLocalContent;
@Nullable private final String myWindowTitle;
@@ -44,7 +44,7 @@ public class ApplyPatchMergeRequest extends MergeRequest {
@Nullable private final Consumer<MergeResult> myCallback;
public ApplyPatchMergeRequest(@Nullable Project project,
@NotNull Document document,
@NotNull DocumentContent resultContent,
@NotNull AppliedTextPatch appliedPatch,
@NotNull String localContent,
@Nullable String windowTitle,
@@ -53,13 +53,13 @@ public class ApplyPatchMergeRequest extends MergeRequest {
@NotNull String patchTitle,
@Nullable Consumer<MergeResult> callback) {
myProject = project;
myDocument = document;
myResultContent = resultContent;
myAppliedPatch = appliedPatch;
myOriginalContent = ApplicationManager.getApplication().runReadAction(new Computable<CharSequence>() {
@Override
public CharSequence compute() {
return myDocument.getImmutableCharSequence();
return myResultContent.getDocument().getImmutableCharSequence();
}
});
myLocalContent = localContent;
@@ -77,16 +77,19 @@ public class ApplyPatchMergeRequest extends MergeRequest {
return myProject;
}
@Override
@NotNull
public Document getDocument() {
return myDocument;
public DocumentContent getResultContent() {
return myResultContent;
}
@Override
@NotNull
public String getLocalContent() {
return myLocalContent;
}
@Override
@NotNull
public AppliedTextPatch getPatch() {
return myAppliedPatch;
@@ -98,16 +101,19 @@ public class ApplyPatchMergeRequest extends MergeRequest {
return myWindowTitle;
}
@Override
@NotNull
public String getLocalTitle() {
return myLocalTitle;
}
@Override
@NotNull
public String getResultTitle() {
return myResultTitle;
}
@Override
@NotNull
public String getPatchTitle() {
return myPatchTitle;
@@ -137,7 +143,7 @@ public class ApplyPatchMergeRequest extends MergeRequest {
new WriteCommandAction.Simple(myProject) {
@Override
protected void run() throws Throwable {
myDocument.setText(applyContent);
myResultContent.getDocument().setText(applyContent);
}
}.execute();
}
@@ -16,16 +16,20 @@
package com.intellij.openapi.vcs.changes.patch.tool;
import com.intellij.diff.DiffContext;
import com.intellij.diff.FrameDiffTool;
import com.intellij.diff.merge.*;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.BooleanGetter;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.diff.util.DiffUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.*;
import static com.intellij.diff.merge.MergeUtil.createSimpleResolveAction;
public class ApplyPatchMergeTool implements MergeTool {
@NotNull
@@ -44,7 +48,7 @@ public class ApplyPatchMergeTool implements MergeTool {
@NotNull private final ApplyPatchMergeRequest myMergeRequest;
public MyApplyPatchViewer(@NotNull MergeContext context, @NotNull ApplyPatchMergeRequest request) {
super(createWrapperDiffContext(context), createWrapperDiffRequest(request), request.getDocument());
super(createWrapperDiffContext(context), request);
myMergeContext = context;
myMergeRequest = request;
}
@@ -54,28 +58,16 @@ public class ApplyPatchMergeTool implements MergeTool {
return new MergeUtil.ProxyDiffContext(mergeContext);
}
@NotNull
private static ApplyPatchDiffRequest createWrapperDiffRequest(@NotNull ApplyPatchMergeRequest request) {
VirtualFile file = FileDocumentManager.getInstance().getFile(request.getDocument());
return new ApplyPatchDiffRequest(request.getPatch(), request.getLocalContent(), file, request.getTitle(),
request.getLocalTitle(), request.getResultTitle(), request.getPatchTitle());
}
@NotNull
@Override
public ToolbarComponents init() {
initPatchViewer();
ToolbarComponents components = new ToolbarComponents();
components.statusPanel = getStatusPanel();
components.toolbarActions = createToolbarActions();
FrameDiffTool.ToolbarComponents init = super.doInit();
components.statusPanel = init.statusPanel;
components.toolbarActions = init.toolbarActions;
components.closeHandler = new BooleanGetter() {
@Override
public boolean get() {
return MergeUtil.showExitWithoutApplyingChangesDialog(MyApplyPatchViewer.this, myMergeRequest, myMergeContext);
}
};
components.closeHandler = () -> MergeUtil.showExitWithoutApplyingChangesDialog(this, myMergeRequest, myMergeContext);
return components;
}
@@ -83,18 +75,28 @@ public class ApplyPatchMergeTool implements MergeTool {
@Override
public Action getResolveAction(@NotNull final MergeResult result) {
if (result == MergeResult.LEFT || result == MergeResult.RIGHT) return null;
return createSimpleResolveAction(result, myMergeRequest, myMergeContext, this);
}
String caption = MergeUtil.getResolveActionTitle(result, myMergeRequest, myMergeContext);
return new AbstractAction(caption) {
@Override
public void actionPerformed(ActionEvent e) {
if (result == MergeResult.CANCEL &&
!MergeUtil.showExitWithoutApplyingChangesDialog(MyApplyPatchViewer.this, myMergeRequest, myMergeContext)) {
return;
}
myMergeContext.finishMerge(result);
}
};
@Override
protected void onChangeResolved() {
super.onChangeResolved();
if (!ContainerUtil.exists(getModelChanges(), (c) -> !c.isResolved())) {
ApplicationManager.getApplication().invokeLater(() -> {
if (isDisposed()) return;
JComponent component = getComponent();
int yOffset = new RelativePoint(getResultEditor().getComponent(), new Point(0, JBUI.scale(5))).getPoint(component).y;
RelativePoint point = new RelativePoint(component, new Point(component.getWidth() / 2, yOffset));
String message = DiffBundle.message("apply.patch.all.changes.processed.message.text");
DiffUtil.showSuccessPopup(message, point, this, () -> {
if (isDisposed()) return;
myMergeContext.finishMerge(MergeResult.RESOLVED);
});
});
}
}
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2000-2016 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.contents.DocumentContent;
import com.intellij.openapi.vcs.changes.patch.AppliedTextPatch;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
interface ApplyPatchRequest {
@NotNull
DocumentContent getResultContent();
@NotNull
String getLocalContent();
@NotNull
AppliedTextPatch getPatch();
@Nullable
String getTitle();
@NotNull
String getLocalTitle();
@NotNull
String getResultTitle();
@NotNull
String getPatchTitle();
}
@@ -17,110 +17,200 @@ package com.intellij.openapi.vcs.changes.patch.tool;
import com.intellij.diff.DiffContentFactory;
import com.intellij.diff.DiffContext;
import com.intellij.diff.FrameDiffTool;
import com.intellij.diff.DiffDialogHints;
import com.intellij.diff.DiffManager;
import com.intellij.diff.actions.ProxyUndoRedoAction;
import com.intellij.diff.actions.impl.FocusOppositePaneAction;
import com.intellij.diff.actions.impl.SetEditorSettingsAction;
import com.intellij.diff.contents.DocumentContent;
import com.intellij.diff.requests.DiffRequest;
import com.intellij.diff.merge.MergeModelBase;
import com.intellij.diff.requests.SimpleDiffRequest;
import com.intellij.diff.tools.fragmented.LineNumberConvertor;
import com.intellij.diff.tools.simple.SimpleDiffViewer;
import com.intellij.diff.tools.util.DiffSplitter;
import com.intellij.diff.tools.util.StatusPanel;
import com.intellij.diff.tools.holders.TextEditorHolder;
import com.intellij.diff.tools.util.*;
import com.intellij.diff.tools.util.base.TextDiffSettingsHolder;
import com.intellij.diff.tools.util.base.TextDiffViewerUtil;
import com.intellij.diff.tools.util.side.TwosideContentPanel;
import com.intellij.diff.util.*;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
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.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.command.UndoConfirmationPolicy;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.DiffBundle;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.event.VisibleAreaListener;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.EditorMarkupModel;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.BooleanGetter;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.changes.patch.AppliedTextPatch;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.*;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
class ApplyPatchViewer implements Disposable {
@NotNull private final ApplyPatchDiffRequest myPatchRequest;
class ApplyPatchViewer implements DataProvider, Disposable {
public static final Logger LOG = Logger.getInstance(ApplyPatchViewer.class);
@Nullable private final Project myProject;
@NotNull private final Document myResultDocument;
@NotNull private final DiffContext myContext;
@NotNull private final ApplyPatchRequest myPatchRequest;
@NotNull private final MySimpleDiffViewer myViewer;
@NotNull private final DiffSplitter myPatchSplitter;
@NotNull private final EditorEx myPatchEditor;
@NotNull private final TextEditorHolder myResultHolder;
@NotNull private final TextEditorHolder myPatchHolder;
@NotNull private final EditorEx myResultEditor;
@NotNull private final EditorEx myPatchEditor;
@NotNull private final SimpleDiffPanel myPanel;
@NotNull private final TwosideContentPanel myContentPanel;
@NotNull private final MyModel myModel;
@NotNull private final FocusTrackerSupport<Side> myFocusTrackerSupport;
@NotNull private final MyPrevNextDifferenceIterable myPrevNextDifferenceIterable;
@NotNull private final StatusPanel myStatusPanel;
@NotNull private final SetEditorSettingsAction myEditorSettingsAction;
// Changes with known AppliedTo. Ordered as in result-editor
@NotNull private final List<ApplyPatchChange> myResultChanges = new ArrayList<>();
// All changes. Ordered as in patch-editor
@NotNull private final List<ApplyPatchChange> myPatchChanges = new ArrayList<>();
// All changes. Ordered as in result-editor. Non-applied changes are at the very beginning with model ranges [-1. -1)
@NotNull private final List<ApplyPatchChange> myModelChanges = new ArrayList<>();
public ApplyPatchViewer(@NotNull DiffContext context,
@NotNull ApplyPatchDiffRequest patchRequest,
@Nullable Document resultDocument) {
myPatchRequest = patchRequest;
private boolean myDisposed;
public ApplyPatchViewer(@NotNull DiffContext context, @NotNull ApplyPatchRequest request) {
myProject = context.getProject();
myResultDocument = resultDocument != null ? resultDocument : new DocumentImpl("");
myContext = context;
myPatchRequest = request;
VirtualFile file = patchRequest.getHighlightFile();
DiffContentFactory contentFactory = DiffContentFactory.getInstance();
DocumentContent localContent = contentFactory.create(myPatchRequest.getLocalContent(), file);
DocumentContent mergedContent = contentFactory.create(myProject, myResultDocument, file);
SimpleDiffRequest diffRequest = new SimpleDiffRequest(myPatchRequest.getTitle(), localContent, mergedContent,
myPatchRequest.getLocalTitle(), myPatchRequest.getResultTitle());
if (resultDocument == null) diffRequest.putUserData(DiffUserDataKeys.FORCE_READ_ONLY, true);
DocumentContent resultContent = request.getResultContent();
DocumentContent patchContent = DiffContentFactory.getInstance().create(new DocumentImpl("", true), resultContent);
myViewer = new MySimpleDiffViewer(context, diffRequest);
myResultEditor = myViewer.getEditor2();
myResultHolder = TextEditorHolder.create(myProject, resultContent);
myPatchHolder = TextEditorHolder.create(myProject, patchContent);
myPatchEditor = DiffUtil.createEditor(new DocumentImpl("", true), myProject, true, true);
DiffUtil.setEditorHighlighter(myProject, myPatchEditor, mergedContent);
myResultEditor = myResultHolder.getEditor();
myPatchEditor = myPatchHolder.getEditor();
if (isReadOnly()) myResultEditor.setViewer(true);
myPatchEditor.setViewer(true);
DiffUtil.disableBlitting(myResultEditor);
DiffUtil.disableBlitting(myPatchEditor);
((EditorMarkupModel)myResultEditor.getMarkupModel()).setErrorStripeVisible(false);
myResultEditor.setVerticalScrollbarOrientation(EditorEx.VERTICAL_SCROLLBAR_LEFT);
myPatchEditor.getGutterComponentEx().setForceShowRightFreePaintersArea(true);
JPanel patchPanel = new JPanel(new BorderLayout(0, DiffUtil.TITLE_GAP));
patchPanel.add(myPatchEditor.getComponent(), BorderLayout.CENTER);
patchPanel.add(myViewer.getPatchTitle(), BorderLayout.NORTH);
((EditorMarkupModel)myPatchEditor.getMarkupModel()).setErrorStripeVisible(false);
myPatchSplitter = new DiffSplitter();
myPatchSplitter.setProportion(2f / 3);
myPatchSplitter.setFirstComponent(myViewer.getComponent());
myPatchSplitter.setSecondComponent(patchPanel);
myPatchSplitter.setHonorComponentsMinimumSize(false);
List<TextEditorHolder> holders = ContainerUtil.list(myResultHolder, myPatchHolder);
List<EditorEx> editors = ContainerUtil.list(myResultEditor, myPatchEditor);
JComponent resultTitle = DiffUtil.createTitle(myPatchRequest.getResultTitle());
JComponent patchTitle = DiffUtil.createTitle(myPatchRequest.getPatchTitle());
List<JComponent> titleComponents = DiffUtil.createSyncHeightComponents(ContainerUtil.list(resultTitle, patchTitle));
myContentPanel = new TwosideContentPanel(holders, titleComponents);
myPanel = new SimpleDiffPanel(myContentPanel, this, myContext);
myModel = new MyModel(myProject, myResultEditor.getDocument());
myFocusTrackerSupport = new FocusTrackerSupport.Twoside(holders);
myFocusTrackerSupport.setCurrentSide(Side.LEFT);
myPrevNextDifferenceIterable = new MyPrevNextDifferenceIterable();
myStatusPanel = new MyStatusPanel();
new MyFocusOppositePaneAction().install(myPanel);
new TextDiffViewerUtil.EditorActionsPopup(createEditorPopupActions()).install(editors);
new TextDiffViewerUtil.EditorFontSizeSynchronizer(editors).install(this);
TextDiffSettingsHolder.TextDiffSettings textSettings = TextDiffSettingsHolder.getInstance().getSettings("ApplyPatch");
myEditorSettingsAction = new SetEditorSettingsAction(textSettings, editors);
myEditorSettingsAction.applyDefaults();
if (!isReadOnly()) {
DiffUtil.registerAction(new ApplySelectedChangesAction(true), myPanel);
DiffUtil.registerAction(new IgnoreSelectedChangesAction(true), myPanel);
}
ProxyUndoRedoAction.register(myProject, myResultEditor, myContentPanel);
}
@NotNull
public JComponent getComponent() {
return myPatchSplitter;
protected List<AnAction> createToolbarActions() {
List<AnAction> group = new ArrayList<>();
if (!isReadOnly()) {
group.add(new ShowDiffWithLocalAction());
group.add(new ApplyNonConflictsAction());
group.add(myEditorSettingsAction);
}
return group;
}
@Nullable
public JComponent getPreferredFocusedComponent() {
return myViewer.getPreferredFocusedComponent();
@NotNull
private List<AnAction> createEditorPopupActions() {
List<AnAction> group = new ArrayList<>();
if (!isReadOnly()) {
group.add(new ApplySelectedChangesAction(false));
group.add(new IgnoreSelectedChangesAction(false));
}
group.add(Separator.getInstance());
group.addAll(TextDiffViewerUtil.createEditorPopupActions());
return group;
}
@Override
public void dispose() {
EditorFactory.getInstance().releaseEditor(myPatchEditor);
Disposer.dispose(myViewer);
if (myDisposed) return;
myDisposed = true;
Disposer.dispose(myResultHolder);
Disposer.dispose(myPatchHolder);
}
//
// Getters
//
public boolean isReadOnly() {
return !DiffUtil.canMakeWritable(myResultEditor.getDocument());
}
@NotNull
public MyModel getModel() {
return myModel;
}
@NotNull
public List<ApplyPatchChange> getModelChanges() {
return myModelChanges;
}
public boolean isDisposed() {
return myDisposed;
}
@NotNull
@@ -129,8 +219,13 @@ class ApplyPatchViewer implements Disposable {
}
@NotNull
public MySimpleDiffViewer getViewer() {
return myViewer;
public JComponent getComponent() {
return myPanel;
}
@Nullable
public JComponent getPreferredFocusedComponent() {
return myResultEditor.getContentComponent();
}
@NotNull
@@ -144,31 +239,44 @@ class ApplyPatchViewer implements Disposable {
}
@NotNull
public FrameDiffTool.ToolbarComponents doInit() {
Document patchDocument = myPatchEditor.getDocument();
public Side getCurrentSide() {
return myFocusTrackerSupport.getCurrentSide();
}
@Nullable
@Override
public Object getData(@NonNls String dataId) {
if (CommonDataKeys.PROJECT.is(dataId)) {
return myProject;
}
else if (DiffDataKeys.PREV_NEXT_DIFFERENCE_ITERABLE.is(dataId)) {
return myPrevNextDifferenceIterable;
}
return null;
}
//
// Impl
//
protected void initPatchViewer() {
final Document outputDocument = myResultEditor.getDocument();
DiffUtil.executeWriteCommand(outputDocument, myProject, "Init merge content", () -> {
outputDocument.setText(myPatchRequest.getLocalContent());
if (!isReadOnly()) DiffUtil.putNonundoableOperation(myProject, outputDocument);
});
PatchChangeBuilder builder = new PatchChangeBuilder();
builder.exec(myPatchRequest.getPatch().getHunks(), myPatchRequest.getLocalContent());
builder.exec(myPatchRequest.getPatch().getHunks());
DiffUtil.executeWriteCommand(myResultDocument, myProject, "Init merge content", () -> {
myResultDocument.setText(builder.getPatchApplyResult());
UndoManager undoManager = myProject != null ? UndoManager.getInstance(myProject) : UndoManager.getGlobalInstance();
if (undoManager != null) {
DocumentReference ref = DocumentReferenceManager.getInstance().create(myResultDocument);
undoManager.nonundoableActionPerformed(ref, false);
}
});
Document patchDocument = myPatchEditor.getDocument();
patchDocument.setText(builder.getPatchContent());
LineNumberConvertor convertor = builder.getLineConvertor();
myPatchEditor.getGutterComponentEx().setLineNumberConvertor(convertor.createConvertor1(), convertor.createConvertor2());
TIntArrayList lines = builder.getSeparatorLines();
for (int i = 0; i < lines.size(); i++) {
int offset = patchDocument.getLineStartOffset(lines.get(i));
@@ -176,52 +284,388 @@ class ApplyPatchViewer implements Disposable {
}
for (PatchChangeBuilder.Hunk hunk : builder.getHunks()) {
myPatchChanges.add(new ApplyPatchChange(this, hunk));
List<PatchChangeBuilder.Hunk> hunks = builder.getHunks();
int[] modelToPatchIndexes = DiffUtil.getSortedIndexes(hunks, (h1, h2) -> {
LineRange lines1 = h1.getAppliedToLines();
LineRange lines2 = h2.getAppliedToLines();
if (lines1 == null && lines2 == null) return 0;
if (lines1 == null) return -1;
if (lines2 == null) return 1;
return lines1.start - lines2.start;
});
int[] patchToModelIndexes = DiffUtil.invertIndexes(modelToPatchIndexes);
List<LineRange> modelRanges = new ArrayList<>();
for (int modelIndex = 0; modelIndex < hunks.size(); modelIndex++) {
int patchIndex = modelToPatchIndexes[modelIndex];
PatchChangeBuilder.Hunk hunk = hunks.get(patchIndex);
LineRange resultRange = hunk.getAppliedToLines();
ApplyPatchChange change = new ApplyPatchChange(hunk, modelIndex, this);
myModelChanges.add(change);
if (resultRange != null) myResultChanges.add(change);
modelRanges.add(resultRange != null ? resultRange : new LineRange(-1, -1));
}
myModel.setChanges(modelRanges);
for (int index : patchToModelIndexes) {
myPatchChanges.add(myModelChanges.get(index));
}
for (ApplyPatchChange change : myModelChanges) {
change.reinstallHighlighters();
}
myStatusPanel.update();
myPatchSplitter.setPainter(new MyDividerPainter());
myContentPanel.setPainter(new MyDividerPainter());
VisibleAreaListener areaListener = (e) -> myPatchSplitter.repaint();
VisibleAreaListener areaListener = (e) -> myContentPanel.repaint();
myResultEditor.getScrollingModel().addVisibleAreaListener(areaListener);
myPatchEditor.getScrollingModel().addVisibleAreaListener(areaListener);
myResultEditor.getDocument().addDocumentListener(new MyDocumentListener(), this);
FrameDiffTool.ToolbarComponents init = myViewer.init();
init.statusPanel = getStatusPanel();
return init;
myPatchEditor.getGutterComponentEx().revalidateMarkup();
}
private class MyDocumentListener implements DocumentListener {
private boolean myShouldRepaint;
public void scrollToChange(@NotNull ApplyPatchChange change, @NotNull Side masterSide, boolean forceScroll) {
if (change.getResultRange() == null) {
DiffUtil.moveCaret(myPatchEditor, change.getPatchRange().start);
myPatchEditor.getScrollingModel().scrollToCaret(forceScroll ? ScrollType.CENTER : ScrollType.MAKE_VISIBLE);
}
else {
LineRange resultRange = change.getResultRange();
LineRange patchRange = change.getPatchAffectedRange();
@Override
public void beforeDocumentChange(DocumentEvent e) {
myShouldRepaint = false;
if (myPatchChanges.isEmpty()) return;
LineRange lineRange = DiffUtil.getAffectedLineRange(e);
int shift = DiffUtil.countLinesShift(e);
for (ApplyPatchChange change : myPatchChanges) {
myShouldRepaint |= change.processChange(lineRange.start, lineRange.end, shift);
int topShift = -1;
if (!forceScroll) {
int masterLine = masterSide.select(resultRange.start, patchRange.start);
EditorEx masterEditor = masterSide.select(myResultEditor, myPatchEditor);
int targetY = masterEditor.logicalPositionToXY(new LogicalPosition(masterLine, 0)).y;
int scrollOffset = masterEditor.getScrollingModel().getVerticalScrollOffset();
topShift = targetY - scrollOffset;
}
int[] offsets = SyncScrollSupport.getTargetOffsets(myResultEditor, myPatchEditor,
resultRange.start, resultRange.end,
patchRange.start, patchRange.end,
topShift);
DiffUtil.moveCaret(myResultEditor, resultRange.start);
DiffUtil.moveCaret(myPatchEditor, patchRange.start);
DiffUtil.scrollToPoint(myResultEditor, new Point(0, offsets[0]), false);
DiffUtil.scrollToPoint(myPatchEditor, new Point(0, offsets[1]), false);
}
}
//
// Modification operations
//
public void repaintDivider() {
myContentPanel.repaintDivider();
}
public void executeCommand(@Nullable String commandName,
@NotNull final Runnable task) {
myModel.executeMergeCommand(commandName, null, UndoConfirmationPolicy.DEFAULT, false, null, task);
}
class MyModel extends MergeModelBase<ApplyPatchChange.State> {
public MyModel(@Nullable Project project, @NotNull Document document) {
super(project, document);
}
@Override
public void documentChanged(DocumentEvent e) {
if (myShouldRepaint) {
myShouldRepaint = false;
protected void reinstallHighlighters(int index) {
ApplyPatchChange change = myModelChanges.get(index);
change.reinstallHighlighters();
}
myPatchSplitter.repaintDivider();
for (ApplyPatchChange change : myPatchChanges) {
change.reinstallHighlighters();
@NotNull
@Override
protected ApplyPatchChange.State storeChangeState(int index) {
ApplyPatchChange change = myModelChanges.get(index);
return change.storeState();
}
@Override
protected void restoreChangeState(@NotNull ApplyPatchChange.State state) {
super.restoreChangeState(state);
ApplyPatchChange change = myModelChanges.get(state.myIndex);
boolean wasResolved = change.isResolved();
change.restoreState(state);
if (wasResolved != change.isResolved()) onChangeResolved();
}
}
protected void onChangeResolved() {
if (isDisposed()) return;
myStatusPanel.update();
}
public void markChangeResolved(@NotNull ApplyPatchChange change) {
if (change.isResolved()) return;
change.setResolved(true);
myModel.invalidateHighlighters(change.getIndex());
onChangeResolved();
}
public void replaceChange(@NotNull ApplyPatchChange change) {
LineRange resultRange = change.getResultRange();
LineRange patchRange = change.getPatchInsertionRange();
if (resultRange == null || change.isResolved()) return;
if (change.getStatus() != AppliedTextPatch.HunkStatus.EXACTLY_APPLIED) return;
List<String> newContent = DiffUtil.getLines(myPatchEditor.getDocument(), patchRange.start, patchRange.end);
myModel.replaceChange(change.getIndex(), newContent);
markChangeResolved(change);
}
private class ApplySelectedChangesAction extends ApplySelectedChangesActionBase {
private ApplySelectedChangesAction(boolean shortcut) {
super(shortcut);
getTemplatePresentation().setText("Accept");
getTemplatePresentation().setIcon(AllIcons.Actions.Checked);
copyShortcutFrom(ActionManager.getInstance().getAction("Diff.ApplyRightSide"));
}
@Override
protected boolean isEnabled(@NotNull ApplyPatchChange change) {
return !change.isResolved() && change.getStatus() == AppliedTextPatch.HunkStatus.EXACTLY_APPLIED;
}
@Override
protected void apply(@NotNull List<ApplyPatchChange> changes) {
for (int i = changes.size() - 1; i >= 0; i--) {
replaceChange(changes.get(i));
}
}
}
private class IgnoreSelectedChangesAction extends ApplySelectedChangesActionBase {
private IgnoreSelectedChangesAction(boolean shortcut) {
super(shortcut);
getTemplatePresentation().setText("Ignore");
getTemplatePresentation().setIcon(AllIcons.Diff.Remove);
copyShortcutFrom(ActionManager.getInstance().getAction("Diff.IgnoreRightSide"));
}
@Override
protected boolean isEnabled(@NotNull ApplyPatchChange change) {
return !change.isResolved();
}
@Override
protected void apply(@NotNull List<ApplyPatchChange> changes) {
for (ApplyPatchChange change : changes) {
markChangeResolved(change);
}
}
}
private abstract class ApplySelectedChangesActionBase extends DumbAwareAction {
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);
Side side = Side.fromValue(ContainerUtil.list(myResultEditor, myPatchEditor), editor);
if (side == null) {
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 Side side = Side.fromValue(ContainerUtil.list(myResultEditor, myPatchEditor), editor);
if (editor == null || side == null) return;
final List<ApplyPatchChange> selectedChanges = getSelectedChanges(side);
if (selectedChanges.isEmpty()) return;
String title = e.getPresentation().getText() + " in patch resolve";
executeCommand(title, () -> {
apply(selectedChanges);
});
}
private boolean isSomeChangeSelected(@NotNull Side side) {
EditorEx editor = side.select(myResultEditor, myPatchEditor);
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<ApplyPatchChange> changes = myModelChanges;
for (ApplyPatchChange change : changes) {
if (!isEnabled(change)) continue;
LineRange range = side.select(change.getResultRange(), change.getPatchRange());
if (range == null) continue;
if (DiffUtil.isSelectedByLine(line, range.start, range.end)) return true;
}
return false;
}
@NotNull
@CalledInAwt
private List<ApplyPatchChange> getSelectedChanges(@NotNull Side side) {
final BitSet lines = DiffUtil.getSelectedLines(side.select(myResultEditor, myPatchEditor));
List<ApplyPatchChange> affectedChanges = new ArrayList<>();
for (ApplyPatchChange change : myModelChanges) {
if (!isEnabled(change)) continue;
LineRange range = side.select(change.getResultRange(), change.getPatchRange());
if (range == null) continue;
if (DiffUtil.isSelectedByLine(lines, range.start, range.end)) {
affectedChanges.add(change);
}
}
return affectedChanges;
}
protected abstract boolean isEnabled(@NotNull ApplyPatchChange change);
@CalledWithWriteLock
protected abstract void apply(@NotNull List<ApplyPatchChange> changes);
}
private class ApplyNonConflictsAction extends DumbAwareAction {
public ApplyNonConflictsAction() {
ActionUtil.copyFrom(this, "Diff.ApplyNonConflicts");
}
@Override
public void update(AnActionEvent e) {
boolean enabled = ContainerUtil.exists(myModelChanges, c -> {
if (c.isResolved()) return false;
if (c.getStatus() == AppliedTextPatch.HunkStatus.NOT_APPLIED) return false;
return true;
});
e.getPresentation().setEnabled(enabled);
}
@Override
public void actionPerformed(AnActionEvent e) {
List<ApplyPatchChange> changes = myModelChanges;
if (changes.isEmpty()) return;
executeCommand("Apply Non Conflicted Changes", () -> {
for (int i = changes.size() - 1; i >= 0; i--) {
ApplyPatchChange change = changes.get(i);
switch (change.getStatus()) {
case ALREADY_APPLIED:
markChangeResolved(change);
break;
case EXACTLY_APPLIED:
replaceChange(change);
break;
case NOT_APPLIED:
break;
}
}
});
}
}
//
// Actions
//
private class MyFocusOppositePaneAction extends FocusOppositePaneAction {
public MyFocusOppositePaneAction() {
super(false);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
EditorEx targetEditor = getCurrentSide().other().select(myResultEditor, myPatchEditor);
DiffUtil.requestFocus(myProject, targetEditor.getContentComponent());
}
}
private class ShowDiffWithLocalAction extends DumbAwareAction {
public ShowDiffWithLocalAction() {
super("Compare with local content", null, AllIcons.Diff.Diff);
}
@Override
public void actionPerformed(AnActionEvent e) {
DocumentContent resultContent = myPatchRequest.getResultContent();
DocumentContent localContent = DiffContentFactory.getInstance().create(myPatchRequest.getLocalContent(), resultContent);
SimpleDiffRequest request = new SimpleDiffRequest(myPatchRequest.getTitle(),
localContent, resultContent,
myPatchRequest.getLocalTitle(), myPatchRequest.getResultTitle());
LogicalPosition currentPosition = DiffUtil.getCaretPosition(myResultEditor);
request.putUserData(DiffUserDataKeys.SCROLL_TO_LINE, Pair.create(Side.RIGHT, currentPosition.line));
DiffManager.getInstance().showDiff(myProject, request, new DiffDialogHints(null, myPanel));
}
}
//
// Helpers
//
private class MyPrevNextDifferenceIterable extends PrevNextDifferenceIterableBase<ApplyPatchChange> {
@NotNull
@Override
protected List<ApplyPatchChange> getChanges() {
return getCurrentSide().select(myResultChanges, myPatchChanges);
}
@NotNull
@Override
protected EditorEx getEditor() {
return getCurrentSide().select(myResultEditor, myPatchEditor);
}
@Override
protected int getStartLine(@NotNull ApplyPatchChange change) {
//noinspection ConstantConditions
return getCurrentSide().select(change.getResultRange(), change.getPatchAffectedRange()).start;
}
@Override
protected int getEndLine(@NotNull ApplyPatchChange change) {
//noinspection ConstantConditions
return getCurrentSide().select(change.getResultRange(), change.getPatchAffectedRange()).end;
}
@Override
protected void scrollToChange(@NotNull ApplyPatchChange change) {
ApplyPatchViewer.this.scrollToChange(change, getCurrentSide(), true);
}
}
@@ -240,17 +684,15 @@ class ApplyPatchViewer implements Disposable {
@Override
public void process(@NotNull Handler handler) {
for (ApplyPatchChange change : myPatchChanges) {
LineRange appliedTo = change.getAppliedTo();
if (appliedTo == null) continue;
for (ApplyPatchChange change : myResultChanges) {
LineRange resultRange = change.getResultRange();
LineRange patchRange = change.getPatchRange();
assert resultRange != null;
int patchLine1 = change.getPatchDeletionRange().start;
int patchLine2 = change.getPatchInsertionRange().end;
Color color = change.getDiffType().getColor(myPatchEditor);
Color color = TextDiffType.MODIFIED.getColor(myPatchEditor);
// do not abort - ranges are ordered in patch order, but they can be not ordered in terms of appliedTo
handler.process(appliedTo.start, appliedTo.end, patchLine1, patchLine2, color);
// do not abort - ranges are ordered in patch order, but they can be not ordered in terms of resultRange
handler.process(resultRange.start, resultRange.end, patchRange.start, patchRange.end, color, change.isResolved());
}
}
}
@@ -259,10 +701,13 @@ class ApplyPatchViewer implements Disposable {
@Nullable
@Override
protected String getMessage() {
int total = myPatchChanges.size();
int totalUnresolved = 0;
int alreadyApplied = 0;
int notApplied = 0;
for (ApplyPatchChange change : myPatchChanges) {
if (change.isResolved()) continue;
totalUnresolved++;
switch (change.getStatus()) {
case ALREADY_APPLIED:
alreadyApplied++;
@@ -275,42 +720,17 @@ class ApplyPatchViewer implements Disposable {
}
}
if (total == notApplied) {
if (totalUnresolved == 0) {
return DiffBundle.message("apply.somehow.status.message.all.applied", notApplied);
}
if (totalUnresolved == notApplied) {
return DiffBundle.message("apply.somehow.status.message.cant.apply", notApplied);
}
else {
String message = DiffBundle.message("apply.somehow.status.message.cant.apply.some", notApplied, total);
String message = DiffBundle.message("apply.somehow.status.message.cant.apply.some", notApplied, totalUnresolved);
if (alreadyApplied == 0) return message;
return message + ". " + DiffBundle.message("apply.somehow.status.message.already.applied", alreadyApplied);
}
}
}
class MySimpleDiffViewer extends SimpleDiffViewer {
private JComponent myPatchTitleComponent;
public MySimpleDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
super(context, request);
}
@NotNull
@Override
protected List<JComponent> createTitles() {
List<JComponent> requestTitles = DiffUtil.createTextTitles(myRequest, getEditors());
JComponent patchTitleLabel = DiffUtil.createTitle(myPatchRequest.getPatchTitle(), null, null, false);
assert requestTitles.size() == 2;
List<JComponent> titles = ContainerUtil.append(requestTitles, patchTitleLabel);
List<JComponent> syncTitles = DiffUtil.createSyncHeightComponents(titles);
JComponent title1 = syncTitles.get(0);
JComponent title2 = syncTitles.get(1);
myPatchTitleComponent = syncTitles.get(2);
return ContainerUtil.list(title1, title2);
}
public JComponent getPatchTitle() {
return myPatchTitleComponent;
}
}
}
@@ -21,12 +21,14 @@ import com.intellij.diff.util.LineRange;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.vcs.changes.patch.AppliedTextPatch;
import com.intellij.openapi.vcs.changes.patch.AppliedTextPatch.AppliedSplitPatchHunk;
import com.intellij.openapi.vcs.changes.patch.AppliedTextPatch.HunkStatus;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.TIntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
class PatchChangeBuilder {
@@ -34,18 +36,30 @@ class PatchChangeBuilder {
@NotNull private final List<Hunk> myHunks = new ArrayList<>();
@NotNull private final LineNumberConvertor.Builder myConvertor = new LineNumberConvertor.Builder();
@NotNull private final TIntArrayList myChangedLines = new TIntArrayList();
private CharSequence myPatchedContent;
private int totalLines = 0;
@NotNull
public static CharSequence getPatchedContent(@NotNull AppliedTextPatch patch, @NotNull String localContent) {
PatchChangeBuilder builder = new PatchChangeBuilder();
builder.exec(patch.getHunks(), localContent);
return builder.getPatchApplyResult();
builder.exec(patch.getHunks());
DocumentImpl document = new DocumentImpl(localContent, true);
List<Hunk> appliedHunks = ContainerUtil.filter(builder.getHunks(), (h) -> h.getStatus() == HunkStatus.EXACTLY_APPLIED);
ContainerUtil.sort(appliedHunks, Comparator.comparingInt(h -> h.getAppliedToLines().start));
for (int i = appliedHunks.size() - 1; i >= 0; i--) {
Hunk hunk = appliedHunks.get(i);
LineRange appliedTo = hunk.getAppliedToLines();
List<String> inserted = hunk.getInsertedLines();
DiffUtil.applyModification(document, appliedTo.start, appliedTo.end, inserted);
}
return document.getText();
}
public void exec(@NotNull List<AppliedSplitPatchHunk> splitHunks, @NotNull CharSequence localContent) {
public void exec(@NotNull List<AppliedSplitPatchHunk> splitHunks) {
int lastBeforeLine = -1;
for (AppliedSplitPatchHunk hunk : splitHunks) {
List<String> contextBefore = hunk.getContextBefore();
@@ -87,30 +101,6 @@ class PatchChangeBuilder {
myHunks.add(new Hunk(hunk.getInsertedLines(), deletionRange, insertionRange, hunk.getAppliedTo(), hunk.getStatus()));
}
DocumentImpl document = new DocumentImpl(localContent, true);
List<Hunk> appliedHunks = ContainerUtil.filter(myHunks, (h) -> h.getOriginalAppliedToLines() != null);
ContainerUtil.sort(appliedHunks, (h1, h2) -> Integer.compare(h1.getOriginalAppliedToLines().start,
h2.getOriginalAppliedToLines().start));
int shift = 0;
for (Hunk hunk : appliedHunks) {
LineRange appliedTo = hunk.getOriginalAppliedToLines();
List<String> inserted = hunk.getInsertedLines();
int insertedLines = inserted.size();
int deletedLines = appliedTo.end - appliedTo.start;
hunk.setAppliedToLines(new LineRange(appliedTo.start + shift, appliedTo.start + shift + insertedLines));
if (hunk.getStatus() == AppliedTextPatch.HunkStatus.EXACTLY_APPLIED) {
DiffUtil.applyModification(document, appliedTo.start + shift, appliedTo.end + shift, inserted);
shift += insertedLines - deletedLines;
}
}
myPatchedContent = document.getText();
}
private void addContext(@NotNull List<String> context, int beforeLineNumber, int afterLineNumber) {
@@ -156,11 +146,6 @@ class PatchChangeBuilder {
return myChangedLines;
}
@NotNull
public CharSequence getPatchApplyResult() {
return myPatchedContent;
}
static class Hunk {
@NotNull private final List<String> myInsertedLines;
@@ -168,15 +153,13 @@ class PatchChangeBuilder {
@NotNull private final LineRange myPatchInsertionRange;
@Nullable private final LineRange myAppliedToLines;
@NotNull private final AppliedTextPatch.HunkStatus myStatus;
@Nullable private LineRange myUpdatedAppliedToLines;
@NotNull private final HunkStatus myStatus;
public Hunk(@NotNull List<String> insertedLines,
@NotNull LineRange patchDeletionRange,
@NotNull LineRange patchInsertionRange,
@Nullable LineRange appliedToLines,
@NotNull AppliedTextPatch.HunkStatus status) {
@NotNull HunkStatus status) {
myInsertedLines = insertedLines;
myPatchDeletionRange = patchDeletionRange;
myPatchInsertionRange = patchInsertionRange;
@@ -195,26 +178,17 @@ class PatchChangeBuilder {
}
@NotNull
public AppliedTextPatch.HunkStatus getStatus() {
public HunkStatus getStatus() {
return myStatus;
}
@Nullable
public LineRange getAppliedToLines() {
return myUpdatedAppliedToLines;
}
private void setAppliedToLines(@Nullable LineRange value) {
myUpdatedAppliedToLines = value;
return myAppliedToLines;
}
@NotNull
private List<String> getInsertedLines() {
return myInsertedLines;
}
private LineRange getOriginalAppliedToLines() {
return myAppliedToLines;
}
}
}