diff --git a/platform/icons/src/vcs/resetStrip.png b/platform/icons/src/vcs/resetStrip.png new file mode 100644 index 000000000000..b28c04eb4303 Binary files /dev/null and b/platform/icons/src/vcs/resetStrip.png differ diff --git a/platform/icons/src/vcs/stripDown.png b/platform/icons/src/vcs/stripDown.png new file mode 100644 index 000000000000..02e5c67f31b9 Binary files /dev/null and b/platform/icons/src/vcs/stripDown.png differ diff --git a/platform/icons/src/vcs/stripNull.png b/platform/icons/src/vcs/stripNull.png new file mode 100644 index 000000000000..c9a2726a40be Binary files /dev/null and b/platform/icons/src/vcs/stripNull.png differ diff --git a/platform/icons/src/vcs/stripUp.png b/platform/icons/src/vcs/stripUp.png new file mode 100644 index 000000000000..987afe62409c Binary files /dev/null and b/platform/icons/src/vcs/stripUp.png differ diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java index 70a90cc7a7ca..f3b82f282967 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/ApplyPatchDifferentiatedDialog.java @@ -372,7 +372,12 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper { final MyShowDiff diffAction = new MyShowDiff(); diffAction.registerCustomShortcutSet(CommonShortcuts.getDiff(), myCenterPanel); group.add(diffAction); - + + group.add(new StripUp()); + group.add(new StripDown()); + group.add(new ResetStrip()); + group.add(new ZeroStrip()); + final ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar("APPLY_PATCH", group, true); myCenterPanel.add(toolbar.getComponent(), gb); @@ -488,11 +493,22 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper { } private void updateTree(boolean doInitCheck) { + final List patchesToSelect = changes2patches(myChangesTreeList.getSelectedChanges()); final List changes = getAllChanges(); final Collection included = getIncluded(doInitCheck, changes); + myChangesTreeList.setChangesToDisplay(changes); myChangesTreeList.setIncludedChanges(included); myChangesTreeList.repaint(); + if ((! doInitCheck) && patchesToSelect != null) { + final List toSelect = new ArrayList(patchesToSelect.size()); + for (FilePatchInProgress.PatchChange change : changes) { + if (patchesToSelect.contains(change.getPatchInProgress())) { + toSelect.add(change); + } + } + myChangesTreeList.select(toSelect); + } myContainBasedChanges = false; for (FilePatchInProgress patch : myPatches) { @@ -540,16 +556,15 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper { } else { // todo maybe written pretty final Collection includedNow = myChangesTreeList.getIncludedChanges(); - final Set> toBeIncluded = new HashSet>(); + final Set toBeIncluded = new HashSet(); for (FilePatchInProgress.PatchChange change : includedNow) { final FilePatchInProgress patch = change.getPatchInProgress(); - toBeIncluded.add(new Pair(patch.getPatch().getBeforeName(), patch.getPatch().getAfterName())); + toBeIncluded.add(patch); } for (FilePatchInProgress.PatchChange change : changes) { final FilePatchInProgress patch = change.getPatchInProgress(); - final Pair pair = new Pair(patch.getPatch().getBeforeName(), patch.getPatch().getAfterName()); acceptChange(totalTrinity, change); - if (toBeIncluded.contains(pair) && patch.baseExistsOrAdded()) { + if (toBeIncluded.contains(patch) && patch.baseExistsOrAdded()) { acceptChange(includedTrinity, change); included.add(change); } @@ -581,6 +596,14 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper { } } + private List changes2patches(final List selectedChanges) { + return ObjectsConvertor.convert(selectedChanges, new Convertor() { + public FilePatchInProgress convert(FilePatchInProgress.PatchChange o) { + return o.getPatchInProgress(); + } + }); + } + private class MapPopup extends BaseListPopupStep { private final Runnable myNewBaseSelector; @@ -759,6 +782,96 @@ public class ApplyPatchDifferentiatedDialog extends DialogWrapper { myCallback.consume(this); } + private class ZeroStrip extends AnAction { + private ZeroStrip() { + super("Remove Directories", "Remove Directories", IconLoader.getIcon("/vcs/stripNull.png")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + for (FilePatchInProgress.PatchChange change : selectedChanges) { + change.getPatchInProgress().setZero(); + } + updateTree(false); + } + } + + private class StripDown extends AnAction { + private StripDown() { + super("Restore Directory", "Restore Directory", IconLoader.getIcon("/vcs/stripDown.png")); + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(isEnabled()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + if (! isEnabled()) return; + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + for (FilePatchInProgress.PatchChange change : selectedChanges) { + change.getPatchInProgress().down(); + } + updateTree(false); + } + + private boolean isEnabled() { + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + if (selectedChanges.isEmpty()) return false; + for (FilePatchInProgress.PatchChange change : selectedChanges) { + if (! change.getPatchInProgress().canDown()) return false; + } + return true; + } + } + + private class StripUp extends AnAction { + private StripUp() { + super("Strip Directory", "Strip Directory", IconLoader.getIcon("/vcs/stripUp.png")); + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(isEnabled()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + if (! isEnabled()) return; + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + for (FilePatchInProgress.PatchChange change : selectedChanges) { + change.getPatchInProgress().up(); + } + updateTree(false); + } + + private boolean isEnabled() { + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + if (selectedChanges.isEmpty()) return false; + for (FilePatchInProgress.PatchChange change : selectedChanges) { + if (! change.getPatchInProgress().canUp()) return false; + } + return true; + } + } + + private class ResetStrip extends AnAction { + private ResetStrip() { + super("Reset Directories", "Reset Directories", IconLoader.getIcon("/vcs/resetStrip.png")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + final List selectedChanges = myChangesTreeList.getSelectedChanges(); + for (FilePatchInProgress.PatchChange change : selectedChanges) { + change.getPatchInProgress().reset(); + } + updateTree(false); + } + } + private class MyShowDiff extends AnAction { private MyShowDiff() { super("Show Diff", "Show Diff", IconLoader.getIcon("/actions/diff.png")); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java index 70f7f195131f..4f0ac8341799 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/FilePatchInProgress.java @@ -19,9 +19,13 @@ import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diff.DiffRequestFactory; import com.intellij.openapi.diff.MergeRequest; +import com.intellij.openapi.diff.impl.patch.ApplyPatchException; +import com.intellij.openapi.diff.impl.patch.ApplyPatchStatus; +import com.intellij.openapi.diff.impl.patch.FilePatch; import com.intellij.openapi.diff.impl.patch.TextFilePatch; import com.intellij.openapi.diff.impl.patch.formove.PathMerger; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.Change; @@ -37,13 +41,12 @@ import com.intellij.vcsUtil.VcsUtil; import org.jetbrains.annotations.Nullable; import java.io.File; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.io.IOException; +import java.util.*; -public class FilePatchInProgress { +public class FilePatchInProgress implements Strippable { private final TextFilePatch myPatch; + private final PatchStrippable myStrippable; private final FilePatchStatus myStatus; private VirtualFile myBase; @@ -59,6 +62,7 @@ public class FilePatchInProgress { public FilePatchInProgress(final TextFilePatch patch, final Collection autoBases, final VirtualFile baseDir) { myPatch = patch; + myStrippable = new PatchStrippable(patch); if (autoBases != null) { myAutoBases = new ArrayList(); final String path = myPatch.getBeforeName() == null ? myPatch.getAfterName() : myPatch.getBeforeName(); @@ -270,4 +274,229 @@ public class FilePatchInProgress { public Pair getKey() { return new Pair(myPatch.getBeforeName(), myPatch.getAfterName()); } + + private void refresh() { + myStrippable.applyBackToPatch(myPatch); + setNewBase(myBase); + } + + public void reset() { + myStrippable.reset(); + refresh(); + } + + public boolean canDown() { + return myStrippable.canDown(); + } + + public boolean canUp() { + return myStrippable.canUp(); + } + + public void up() { + myStrippable.up(); + refresh(); + } + + public void down() { + myStrippable.down(); + refresh(); + } + + public void setZero() { + myStrippable.setZero(); + refresh(); + } + + public String getCurrentPath() { + return myStrippable.getCurrentPath(); + } + + private static class StripCapablePath implements Strippable { + private final int myStripMax; + private int myCurrentStrip; + private final StringBuilder mySourcePath; + private final int[] myParts; + + private StripCapablePath(final String path) { + final String corrected = path.trim().replace('\\', '/'); + mySourcePath = new StringBuilder(corrected); + final String[] steps = corrected.split("/"); + myStripMax = steps.length - 1; + myParts = new int[steps.length]; + int pos = 0; + for (int i = 0; i < steps.length; i++) { + final String step = steps[i]; + myParts[i] = pos; + pos += step.length() + 1; // plus 1 for separator + } + myCurrentStrip = 0; + } + + public void reset() { + myCurrentStrip = 0; + } + + // down - restore dirs... + public boolean canDown() { + return myCurrentStrip > 0; + } + + public boolean canUp() { + return myCurrentStrip < myStripMax; + } + + public void up() { + if (canUp()) { + ++ myCurrentStrip; + } + } + + public void down() { + if (canDown()) { + -- myCurrentStrip; + } + } + + public void setZero() { + myCurrentStrip = myStripMax; + } + + public String getCurrentPath() { + return mySourcePath.substring(myParts[myCurrentStrip]); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StripCapablePath that = (StripCapablePath)o; + + if (!mySourcePath.equals(that.mySourcePath)) return false; + + return true; + } + + @Override + public int hashCode() { + return mySourcePath.hashCode(); + } + } + + private static class PatchStrippable implements Strippable { + private final Strippable[] myParts; + private final int myBeforeIdx; + private final int myAfterIdx; + + private PatchStrippable(final FilePatch patch) { + final boolean onePath = patch.isDeletedFile() || patch.isNewFile() || Comparing.equal(patch.getAfterName(), patch.getBeforeName()); + final int size = onePath ? 1 : 2; + myParts = new Strippable[size]; + + int cnt = 0; + if (patch.getAfterName() != null) { + myAfterIdx = 0; + myParts[cnt] = new StripCapablePath(patch.getAfterName()); + ++ cnt; + } else { + myAfterIdx = -1; + } + if (cnt < size) { + myParts[cnt] = new StripCapablePath(patch.getBeforeName()); + myBeforeIdx = cnt; + } else { + myBeforeIdx = 0; + } + } + + public void reset() { + for (Strippable part : myParts) { + part.reset(); + } + } + + public boolean canDown() { + boolean result = true; + for (Strippable part : myParts) { + result &= part.canDown(); + } + return result; + } + + public boolean canUp() { + boolean result = true; + for (Strippable part : myParts) { + result &= part.canUp(); + } + return result; + } + + public void up() { + for (Strippable part : myParts) { + part.up(); + } + } + + public void down() { + for (Strippable part : myParts) { + part.down(); + } + } + + public void setZero() { + for (Strippable part : myParts) { + part.setZero(); + } + } + + public String getCurrentPath() { + return myParts[0].getCurrentPath(); + } + + public void applyBackToPatch(final FilePatch patch) { + final String beforeName = patch.getBeforeName(); + if (beforeName != null) { + patch.setBeforeName(myParts[myBeforeIdx].getCurrentPath()); + } + final String afterName = patch.getAfterName(); + if (afterName != null) { + patch.setAfterName(myParts[myAfterIdx].getCurrentPath()); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PatchStrippable that = (PatchStrippable)o; + + if (!Arrays.equals(myParts, that.myParts)) return false; + + return true; + } + + @Override + public int hashCode() { + return Arrays.hashCode(myParts); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FilePatchInProgress that = (FilePatchInProgress)o; + + if (!myStrippable.equals(that.myStrippable)) return false; + + return true; + } + + @Override + public int hashCode() { + return myStrippable.hashCode(); + } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/Strippable.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/Strippable.java new file mode 100644 index 000000000000..9a8ef5895ca8 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/patch/Strippable.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2009 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; + +public interface Strippable { + void reset();// down - restore dirs... + + boolean canDown(); + + boolean canUp(); + + void up(); + + void down(); + + void setZero(); + + String getCurrentPath(); +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java index 369838c98818..de27d74fb1fc 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java @@ -17,6 +17,7 @@ package com.intellij.openapi.vcs.changes.ui; import com.intellij.ide.util.PropertiesComponent; import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.EmptyRunnable; @@ -41,9 +42,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeCellRenderer; -import javax.swing.tree.TreePath; +import javax.swing.tree.*; import java.awt.*; import java.awt.event.*; import java.io.File; @@ -205,8 +204,10 @@ public abstract class ChangesTreeList extends JPanel { } public void setShowFlatten(final boolean showFlatten) { + final List wasSelected = getSelectedChanges(); myShowFlatten = showFlatten; myCards.show(this, myShowFlatten ? LIST_CARD : TREE_CARD); + select(wasSelected); if (myList.hasFocus() || myTree.hasFocus()) { SwingUtilities.invokeLater(new Runnable() { public void run() { @@ -243,7 +244,7 @@ public abstract class ChangesTreeList extends JPanel { final DefaultTreeModel model = buildTreeModel(changes, myChangeDecorator); myTree.setModel(model); - SwingUtilities.invokeLater(new Runnable() { + final Runnable runnable = new Runnable() { public void run() { if (myProject.isDisposed()) return; TreeUtil.expandAll(myTree); @@ -256,7 +257,7 @@ public abstract class ChangesTreeList extends JPanel { listSelection = count; break; } - count ++; + count++; } ChangesBrowserNode root = (ChangesBrowserNode)model.getRoot(); @@ -290,7 +291,12 @@ public abstract class ChangesTreeList extends JPanel { } } } - }); + }; + if (ApplicationManager.getApplication().isDispatchThread()) { + runnable.run(); + } else { + SwingUtilities.invokeLater(runnable); + } } protected abstract DefaultTreeModel buildTreeModel(final List changes, final ChangeNodeDecorator changeNodeDecorator); @@ -658,12 +664,49 @@ public abstract class ChangesTreeList extends JPanel { } } else { - final int count = myTree.getRowCount(); - if (count > 0) { - myTree.setSelectionInterval(0, count-1); + final int countTree = myTree.getRowCount(); + if (countTree > 0) { + myTree.setSelectionInterval(0, countTree-1); } } } } + public void select(final List changes) { + final DefaultTreeModel treeModel = (DefaultTreeModel) myTree.getModel(); + final TreeNode root = (TreeNode) treeModel.getRoot(); + final List treeSelection = new ArrayList(changes.size()); + TreeUtil.traverse(root, new TreeUtil.Traverse() { + public boolean accept(Object node) { + final T change = (T) ((DefaultMutableTreeNode) node).getUserObject(); + if (changes.contains(change)) { + treeSelection.add(new TreePath(((DefaultMutableTreeNode) node).getPath())); + } + return true; + } + }); + myTree.setSelectionPaths(treeSelection.toArray(new TreePath[treeSelection.size()])); + + // list + final ListModel model = myList.getModel(); + final int size = model.getSize(); + final List listSelection = new ArrayList(changes.size()); + for (int i = 0; i < size; i++) { + final T el = (T) model.getElementAt(i); + if (changes.contains(el)) { + listSelection.add(i); + } + } + myList.setSelectedIndices(int2int(listSelection)); + } + + private static int[] int2int(List treeSelection) { + final int[] toPass = new int[treeSelection.size()]; + int i = 0; + for (Integer integer : treeSelection) { + toPass[i] = integer; + ++ i; + } + return toPass; + } }