[patch]: implement analyze and apply patch on the fly

This commit is contained in:
Nadya Zabrodina
2016-04-29 15:09:01 +03:00
parent 8e18d53531
commit 204afe9ca2
4 changed files with 118 additions and 5 deletions
@@ -47,6 +47,9 @@ public class PatchReader {
@NonNls private static final String CONTEXT_HUNK_PREFIX = "***************";
@NonNls private static final String CONTEXT_FILE_PREFIX = "*** ";
@NonNls private static final String UNIFIED_BEFORE_HUNK_PREFIX = "--- ";
@NonNls private static final String UNIFIED_AFTER_HUNK_PREFIX = "+++ ";
@NonNls private static final Pattern ourUnifiedHunkStartPattern = Pattern.compile("@@ -(\\d+)(,(\\d+))? \\+(\\d+)(,(\\d+))? @@.*");
@NonNls private static final Pattern ourContextBeforeHunkStartPattern = Pattern.compile("\\*\\*\\* (\\d+),(\\d+) \\*\\*\\*\\*");
@NonNls private static final Pattern ourContextAfterHunkStartPattern = Pattern.compile("--- (\\d+),(\\d+) ----");
@@ -629,4 +632,36 @@ public class PatchReader {
return myHunkCount == 1 && myDeleted;
}
}
public static boolean isPatchContent(@Nullable String content) {
if (content == null) return false;
List<String> lines = LineTokenizer.tokenizeIntoList(content, false);
final ListIterator<String> iterator = lines.listIterator();
DiffFormat currentFormat = null;
while (iterator.hasNext()) {
String line = iterator.next();
if (line.startsWith(CONTEXT_HUNK_PREFIX)) {
currentFormat = DiffFormat.CONTEXT;
}
else if (currentFormat == DiffFormat.CONTEXT && ourContextBeforeHunkStartPattern.matcher(line).matches()) {
break;
}
else if (line.startsWith(UNIFIED_BEFORE_HUNK_PREFIX) && currentFormat == null) {
currentFormat = DiffFormat.UNIFIED;
}
else if (currentFormat == DiffFormat.UNIFIED && line.startsWith(UNIFIED_AFTER_HUNK_PREFIX)) break;
}
if (currentFormat == null) return false; // can't detect format
// check that contains at least one chunk
while (iterator.hasNext()) {
String line = iterator.next();
if (currentFormat == DiffFormat.CONTEXT) {
if (ourContextAfterHunkStartPattern.matcher(line).matches()) return true;
}
else {
if (ourUnifiedHunkStartPattern.matcher(line).matches()) return true;
}
}
return false;
}
}
@@ -33,6 +33,7 @@ public class VcsApplicationSettings implements PersistentStateComponent<VcsAppli
public boolean SHOW_WHITESPACES_IN_LST = false;
public boolean SHOW_LST_GUTTER_MARKERS = true;
public boolean SHOW_LST_WORD_DIFFERENCES = true;
public boolean DETECT_PATCH_ON_THE_FLY = true;
public static VcsApplicationSettings getInstance() {
return ServiceManager.getService(VcsApplicationSettings.class);
@@ -2,23 +2,39 @@ package com.intellij.openapi.vcs.changes.patch;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationActivationListener;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.application.ex.ClipboardUtil;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsApplicationSettings;
import com.intellij.openapi.vcs.changes.ChangeListManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.ObjectUtils;
import com.intellij.util.messages.MessageBusConnection;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Collections;
public class ApplyPatchFromClipboardAction extends DumbAwareAction {
private static final PatchClipboardListener LISTENER = new PatchClipboardListener();
public ApplyPatchFromClipboardAction() {
MessageBusConnection connection = ApplicationManagerEx.getApplicationEx().getMessageBus().connect();
connection.subscribe(ApplicationActivationListener.TOPIC, LISTENER);
}
@Override
public void update(AnActionEvent e) {
Project project = e.getData(CommonDataKeys.PROJECT);
String text = ClipboardUtil.getTextInClipboard();
e.getPresentation().setEnabled(project != null && PatchFileUtil.isPatchContent(text));
// allow to apply from clipboard even we do not detect it as a patch, because during applying we parse content more precisely
e.getPresentation().setEnabled(project != null && text != null);
}
public void actionPerformed(AnActionEvent e) {
@@ -28,8 +44,34 @@ public class ApplyPatchFromClipboardAction extends DumbAwareAction {
String clipboardText = ClipboardUtil.getTextInClipboard();
assert clipboardText != null;
VirtualFile vFile = new LightVirtualFile("clipboardPatchFile", clipboardText);
new ApplyPatchDifferentiatedDialog(project, new ApplyPatchDefaultExecutor(project), Collections.emptyList(),
ApplyPatchMode.APPLY_PATCH_IN_MEMORY, vFile).show();
new MyApplyPatchFromClipboardDialog(project, clipboardText).show();
}
@NotNull
private static JCheckBox createAnalyzeOnTheFlyOptionPanel() {
final JCheckBox removeOptionCheckBox = new JCheckBox("Analyze and Apply Patch from Clipboard on the Fly");
removeOptionCheckBox.setMnemonic(KeyEvent.VK_L);
removeOptionCheckBox.setSelected(VcsApplicationSettings.getInstance().DETECT_PATCH_ON_THE_FLY);
removeOptionCheckBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
VcsApplicationSettings.getInstance().DETECT_PATCH_ON_THE_FLY = removeOptionCheckBox.isSelected();
}
});
return removeOptionCheckBox;
}
public static class MyApplyPatchFromClipboardDialog extends ApplyPatchDifferentiatedDialog {
private static final JCheckBox FLY_OPTION_PANEL = createAnalyzeOnTheFlyOptionPanel();
public MyApplyPatchFromClipboardDialog(@NotNull Project project, @NotNull String clipboardText) {
super(project, new ApplyPatchDefaultExecutor(project), Collections.emptyList(), ApplyPatchMode.APPLY_PATCH_IN_MEMORY,
new LightVirtualFile("clipboardPatchFile", clipboardText));
}
@Override
protected JComponent createSouthPanel() {
return addDoNotShowCheckBox(ObjectUtils.assertNotNull(super.createSouthPanel()), FLY_OPTION_PANEL);
}
}
}
@@ -0,0 +1,35 @@
/*
* 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;
import com.intellij.openapi.application.ClipboardAnalyzeListener;
import com.intellij.openapi.diff.impl.patch.PatchReader;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsApplicationSettings;
import org.jetbrains.annotations.NotNull;
public class PatchClipboardListener extends ClipboardAnalyzeListener {
@Override
protected void handle(@NotNull Project project, @NotNull String value) {
new ApplyPatchFromClipboardAction.MyApplyPatchFromClipboardDialog(project, value).show();
}
@Override
public boolean canHandle(@NotNull String value) {
if (!VcsApplicationSettings.getInstance().DETECT_PATCH_ON_THE_FLY) return false;
return PatchReader.isPatchContent(value);
}
}