diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ContentEntryTreeEditor.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ContentEntryTreeEditor.java index a6326de4eaf0..148fa8c5a243 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ContentEntryTreeEditor.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/ContentEntryTreeEditor.java @@ -160,7 +160,7 @@ public class ContentEntryTreeEditor { }; - myFileSystemTree = new FileSystemTreeImpl(myProject, myDescriptor, myTree, getContentEntryCellRenderer(), init) { + myFileSystemTree = new FileSystemTreeImpl(myProject, myDescriptor, myTree, getContentEntryCellRenderer(), init, null) { protected AbstractTreeBuilder createTreeBuilder(JTree tree, DefaultTreeModel treeModel, AbstractTreeStructure treeStructure, Comparator comparator, FileChooserDescriptor descriptor, final Runnable onInitialized) { diff --git a/platform/platform-api/src/com/intellij/ui/CollectionListModel.java b/platform/platform-api/src/com/intellij/ui/CollectionListModel.java index 82d707427ce0..13fea2fd8cc6 100644 --- a/platform/platform-api/src/com/intellij/ui/CollectionListModel.java +++ b/platform/platform-api/src/com/intellij/ui/CollectionListModel.java @@ -18,6 +18,8 @@ package com.intellij.ui; import javax.swing.*; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.List; /** @@ -74,4 +76,12 @@ public class CollectionListModel extends AbstractListModel { int i = myItems.indexOf(element); fireContentsChanged(this, i, i); } + + public void sort(final Comparator comparator) { + Collections.sort(myItems, comparator); + } + + public List getItems() { + return Collections.unmodifiableList(myItems); + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSystemTreeImpl.java b/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSystemTreeImpl.java index 575cbd0c5e8b..3407d47cd246 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSystemTreeImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileChooser/ex/FileSystemTreeImpl.java @@ -17,7 +17,6 @@ package com.intellij.openapi.fileChooser.ex; import com.intellij.ide.util.treeView.AbstractTreeBuilder; import com.intellij.ide.util.treeView.AbstractTreeStructure; -import com.intellij.ide.util.treeView.AbstractTreeUi; import com.intellij.ide.util.treeView.NodeDescriptor; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.ActionGroup; @@ -39,11 +38,14 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.ui.*; +import com.intellij.ui.PopupHandler; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.ui.TreeSpeedSearch; +import com.intellij.ui.UIBundle; import com.intellij.ui.treeStructure.SimpleNodeRenderer; +import com.intellij.ui.treeStructure.Tree; import com.intellij.util.containers.ConvertingIterator; import com.intellij.util.containers.Convertor; -import com.intellij.ui.treeStructure.Tree; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.Nullable; @@ -73,13 +75,14 @@ public class FileSystemTreeImpl implements FileSystemTree { private final MyExpansionListener myExpansionListener = new MyExpansionListener(); public FileSystemTreeImpl(@Nullable Project project, FileChooserDescriptor descriptor) { - this(project, descriptor, new Tree(), null, null); + this(project, descriptor, new Tree(), null, null, null); myTree.setRootVisible(descriptor.isTreeRootVisible()); myTree.setShowsRootHandles(true); } public FileSystemTreeImpl(@Nullable Project project, FileChooserDescriptor descriptor, Tree tree, TreeCellRenderer renderer, - final Runnable onInitialized) { + final Runnable onInitialized, + Convertor speedSearchConvertor) { myProject = project; myTreeStructure = new FileTreeStructure(project, descriptor); myDescriptor = descriptor; @@ -114,7 +117,11 @@ public class FileSystemTreeImpl implements FileSystemTree { } }); - new TreeSpeedSearch(myTree); + if (speedSearchConvertor != null) { + new TreeSpeedSearch(myTree, speedSearchConvertor); + } else { + new TreeSpeedSearch(myTree); + } myTree.setLineStyleAngled(); TreeUtil.installActions(myTree); @@ -220,6 +227,10 @@ public class FileSystemTreeImpl implements FileSystemTree { } } + public AbstractTreeBuilder getTreeBuilder() { + return myTreeBuilder; + } + /** * @deprecated since tree updating is an asynchronous operation */ 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 453ebc59aafd..1760be6b98a0 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 @@ -27,12 +27,9 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.FilePath; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.vcs.VcsBundle; import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vcs.changes.ChangesUtil; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.*; import com.intellij.ui.components.JBList; import com.intellij.ui.components.panels.NonOpaquePanel; @@ -52,7 +49,6 @@ import javax.swing.border.Border; import javax.swing.tree.*; import java.awt.*; import java.awt.event.*; -import java.io.File; import java.util.*; import java.util.List; @@ -640,50 +636,32 @@ public abstract class ChangesTreeList extends JPanel { public MyListCellRenderer() { super(new BorderLayout()); myCheckbox = new JCheckBox(); - myTextRenderer = new ColoredListCellRenderer() { - protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { - final FilePath path = TreeModelBuilder.getPathForObject(value); - if (path.isDirectory()) { - setIcon(PlatformIcons.DIRECTORY_CLOSED_ICON); - } else { - setIcon(path.getFileType().getIcon()); - } - final FileStatus fileStatus; - if (value instanceof Change) { - fileStatus = ((Change) value).getFileStatus(); - } - else { - final VirtualFile virtualFile = path.getVirtualFile(); - if (virtualFile != null) { - fileStatus = FileStatusManager.getInstance(myProject).getStatus(virtualFile); - } - else { - fileStatus = FileStatus.NOT_CHANGED; - } - } - append(path.getName(), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, fileStatus.getColor(), null)); + myTextRenderer = new VirtualFileListCellRenderer(myProject) { + @Override + protected void putParentPath(Object value, FilePath path, FilePath self) { + super.putParentPath(value, path, self); final boolean applyChangeDecorator = (value instanceof Change) && myChangeDecorator != null; - final File parentFile = path.getIOFile().getParentFile(); - if (parentFile != null) { - final String parentPath = parentFile.getPath(); - List> parts = null; - if (applyChangeDecorator) { - parts = myChangeDecorator.stressPartsOfFileName((Change)value, parentPath); - } - if (parts == null) { - parts = Collections.singletonList(new Pair(parentPath, ChangeNodeDecorator.Stress.PLAIN)); - } - - append(" ("); - for (Pair part : parts) { - append(part.getFirst(), part.getSecond().derive(SimpleTextAttributes.GRAYED_ATTRIBUTES)); - } - append(")", SimpleTextAttributes.GRAYED_ATTRIBUTES); - } if (applyChangeDecorator) { myChangeDecorator.decorate((Change) value, this, isShowFlatten()); } } + + @Override + protected void putParentPathImpl(Object value, String parentPath, FilePath self) { + final boolean applyChangeDecorator = (value instanceof Change) && myChangeDecorator != null; + List> parts = null; + if (applyChangeDecorator) { + parts = myChangeDecorator.stressPartsOfFileName((Change)value, parentPath); + } + if (parts == null) { + super.putParentPathImpl(value, parentPath, self); + return; + } + + for (Pair part : parts) { + append(part.getFirst(), part.getSecond().derive(SimpleTextAttributes.GRAYED_ATTRIBUTES)); + } + } }; myCheckbox.setBackground(null); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/VirtualFileListCellRenderer.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/VirtualFileListCellRenderer.java new file mode 100644 index 000000000000..6f146ae7c556 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/VirtualFileListCellRenderer.java @@ -0,0 +1,100 @@ +/* + * Copyright 2000-2011 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.ui; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.FileStatus; +import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.PlatformIcons; + +import javax.swing.*; +import java.io.File; + +/** + * @author irengrig + * Date: 7/8/11 + * Time: 12:21 PM + */ +public class VirtualFileListCellRenderer extends ColoredListCellRenderer { + private final FileStatusManager myFileStatusManager; + private final boolean myIgnoreFileStatus; + + public VirtualFileListCellRenderer(final Project project) { + this(project, false); + } + + public VirtualFileListCellRenderer(final Project project, final boolean ignoreFileStatus) { + myIgnoreFileStatus = ignoreFileStatus; + myFileStatusManager = FileStatusManager.getInstance(project); + } + + @Override + protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { + final FilePath path = TreeModelBuilder.getPathForObject(value); + renderIcon(path); + final FileStatus fileStatus = myIgnoreFileStatus ? FileStatus.NOT_CHANGED : getStatus(value, path); + append(getName(path), new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, fileStatus.getColor(), null)); + putParentPath(value, path, path); + } + + protected String getName(FilePath path) { + return path.getName(); + } + + protected FileStatus getStatus(Object value, FilePath path) { + final FileStatus fileStatus; + if (value instanceof Change) { + fileStatus = ((Change) value).getFileStatus(); + } + else { + final VirtualFile virtualFile = path.getVirtualFile(); + if (virtualFile != null) { + fileStatus = myFileStatusManager.getStatus(virtualFile); + } + else { + fileStatus = FileStatus.NOT_CHANGED; + } + } + return fileStatus; + } + + protected void renderIcon(FilePath path) { + if (path.isDirectory()) { + setIcon(PlatformIcons.DIRECTORY_CLOSED_ICON); + } else { + setIcon(path.getFileType().getIcon()); + } + } + + protected void putParentPath(Object value, FilePath path, FilePath self) { + final File parentFile = path.getIOFile().getParentFile(); + if (parentFile != null) { + final String parentPath = parentFile.getPath(); + append(" (", SimpleTextAttributes.GRAYED_ATTRIBUTES); + putParentPathImpl(value, parentPath, self); + append(")", SimpleTextAttributes.GRAYED_ATTRIBUTES); + } + } + + protected void putParentPathImpl(Object value, String parentPath, FilePath self) { + append(parentPath, SimpleTextAttributes.GRAYED_ATTRIBUTES); + } +} diff --git a/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectedState.java b/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectedState.java index 76fd0120a5e5..3267b2bdf48e 100644 --- a/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectedState.java +++ b/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectedState.java @@ -22,6 +22,7 @@ import com.intellij.util.containers.hash.HashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -91,4 +92,9 @@ public class SelectedState { public Set getSelected() { return Collections.unmodifiableSet(mySelected); } + + public void setSelection(Collection files) { + mySelected.clear(); + mySelected.addAll(files); + } } diff --git a/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectionManager.java b/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectionManager.java index c5daa37a0793..9a53d07f9dd7 100644 --- a/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectionManager.java +++ b/platform/vcs-impl/src/com/intellij/util/treeWithCheckedNodes/SelectionManager.java @@ -16,14 +16,20 @@ package com.intellij.util.treeWithCheckedNodes; import com.intellij.openapi.util.Ref; +import com.intellij.openapi.vcs.impl.CollectionsDelta; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.PairProcessor; +import com.intellij.util.PlusMinus; import com.intellij.util.Processor; import com.intellij.util.TreeNodeState; import com.intellij.util.containers.Convertor; +import org.jetbrains.annotations.Nullable; import javax.swing.tree.DefaultMutableTreeNode; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; /** * @author irengrig @@ -35,6 +41,8 @@ import javax.swing.tree.DefaultMutableTreeNode; public class SelectionManager { private final SelectedState myState; private final Convertor myNodeConvertor; + @Nullable + private PlusMinus mySelectionChangeListener; public SelectionManager(int selectedSize, int queueSize, final Convertor nodeConvertor) { myNodeConvertor = nodeConvertor; @@ -43,14 +51,17 @@ public class SelectionManager { public void toggleSelection(final DefaultMutableTreeNode node) { final StateWorker stateWorker = new StateWorker(node, myNodeConvertor); - if (stateWorker.getVf() == null) return; + final VirtualFile vf = stateWorker.getVf(); + if (vf == null) return; final TreeNodeState state = getStateImpl(stateWorker); if (TreeNodeState.HAVE_SELECTED_ABOVE.equals(state)) return; if (TreeNodeState.CLEAR.equals(state) && (! myState.canAddSelection())) return; + final HashSet old = new HashSet(myState.getSelected()); + final TreeNodeState futureState = - myState.putAndPass(stateWorker.getVf(), TreeNodeState.SELECTED.equals(state) ? TreeNodeState.CLEAR : TreeNodeState.SELECTED); + myState.putAndPass(vf, TreeNodeState.SELECTED.equals(state) ? TreeNodeState.CLEAR : TreeNodeState.SELECTED); // for those possibly duplicate nodes (i.e. when we have root for module and root for VCS root, each file is shown twice in a tree -> // clear all suspicious cached) @@ -58,7 +69,7 @@ public class SelectionManager { myState.clearAllCachedMatching(new Processor() { @Override public boolean process(VirtualFile virtualFile) { - return VfsUtil.isAncestor(virtualFile, stateWorker.getVf(), false); + return VfsUtil.isAncestor(virtualFile, vf, false); } }); } @@ -73,6 +84,7 @@ public class SelectionManager { return true; } }); + // todo vf, vf - what is correct? myState.clearAllCachedMatching(new Processor() { @Override public boolean process(VirtualFile vf) { @@ -84,6 +96,38 @@ public class SelectionManager { myState.remove(selected); } } + final Set selectedAfter = myState.getSelected(); + if (mySelectionChangeListener != null && ! old.equals(selectedAfter)) { + final Set removed = CollectionsDelta.notInSecond(old, selectedAfter); + final Set newlyAdded = CollectionsDelta.notInSecond(selectedAfter, old); + if (newlyAdded != null) { + for (VirtualFile file : newlyAdded) { + if (mySelectionChangeListener != null) { + mySelectionChangeListener.plus(file); + } + } + } + if (removed != null) { + for (VirtualFile file : removed) { + if (mySelectionChangeListener != null) { + mySelectionChangeListener.minus(file); + } + } + } + } + } + + public boolean canAddSelection() { + return myState.canAddSelection(); + } + + public void setSelection(Collection files) { + myState.setSelection(files); + for (VirtualFile file : files) { + if (mySelectionChangeListener != null) { + mySelectionChangeListener.plus(file); + } + } } public TreeNodeState getState(final DefaultMutableTreeNode node) { @@ -120,6 +164,19 @@ public class SelectionManager { return TreeNodeState.CLEAR; } + public void removeSelection(final VirtualFile elementAt) { + myState.remove(elementAt); + myState.clearAllCachedMatching(new Processor() { + @Override + public boolean process(VirtualFile virtualFile) { + return VfsUtil.isAncestor(virtualFile, elementAt, false) || VfsUtil.isAncestor(elementAt, virtualFile, false); + } + }); + if (mySelectionChangeListener != null) { + mySelectionChangeListener.minus(elementAt); + } + } + private static class StateWorker { private final DefaultMutableTreeNode myNode; private final Convertor myConvertor; @@ -148,4 +205,12 @@ public class SelectionManager { } } } + + public PlusMinus getSelectionChangeListener() { + return mySelectionChangeListener; + } + + public void setSelectionChangeListener(PlusMinus selectionChangeListener) { + mySelectionChangeListener = selectionChangeListener; + } } diff --git a/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java b/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java index 9ee650711da9..3e88c30499aa 100644 --- a/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java +++ b/plugins/git4idea/src/git4idea/changes/GitChangeUtils.java @@ -304,12 +304,15 @@ public class GitChangeUtils { @Nullable public static SHAHash commitExists(final Project project, final VirtualFile root, final String anyReference, - final String... parameters) { + List paths, final String... parameters) { GitSimpleHandler h = new GitSimpleHandler(project, root, GitCommand.LOG); h.setNoSSH(true); h.setSilent(true); h.addParameters(parameters); h.addParameters("--max-count=1", "--pretty=%H", "--encoding=UTF-8", anyReference, "--"); + if (paths != null && ! paths.isEmpty()) { + h.addRelativeFiles(paths); + } try { final String output = h.run().trim(); if (StringUtil.isEmptyOrSpaces(output)) return null; diff --git a/plugins/git4idea/src/git4idea/history/GitHistoryProvider.java b/plugins/git4idea/src/git4idea/history/GitHistoryProvider.java index 53d34e68f409..6963eef9ca00 100644 --- a/plugins/git4idea/src/git4idea/history/GitHistoryProvider.java +++ b/plugins/git4idea/src/git4idea/history/GitHistoryProvider.java @@ -166,7 +166,7 @@ public class GitHistoryProvider implements VcsHistoryProvider, VcsCacheableHisto final VirtualFile root = GitUtil.getGitRoot(filePath); if (root == null) return false; - final SHAHash shaHash = GitChangeUtils.commitExists(myProject, root, beforeVersionId, "--all"); + final SHAHash shaHash = GitChangeUtils.commitExists(myProject, root, beforeVersionId, null, "--all"); if (shaHash == null) { throw new VcsException("Can not apply patch to " + filePath.getPath() + ".\nCan not find revision '" + beforeVersionId + "'."); } diff --git a/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java b/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java index 7574d621557a..86ddc5d4a2dc 100644 --- a/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java +++ b/plugins/git4idea/src/git4idea/history/GitHistoryUtils.java @@ -410,11 +410,11 @@ public class GitHistoryUtils { } public static void historyWithLinks(final Project project, - FilePath path, - final SymbolicRefs refs, - final AsynchConsumer gitCommitConsumer, - final Getter isCanceled, - final String... parameters) throws VcsException { + FilePath path, + final SymbolicRefs refs, + final AsynchConsumer gitCommitConsumer, + final Getter isCanceled, + Collection paths, final String... parameters) throws VcsException { // adjust path using change manager path = getLastCommitName(project, path); final VirtualFile root = GitUtil.getGitRoot(path); @@ -425,9 +425,14 @@ public class GitHistoryUtils { h.setStdoutSuppressed(true); h.addParameters(parameters); parser.parseStatusBeforeName(true); - h.addParameters("--name-status", parser.getPretty(), "--encoding=UTF-8", "--full-history", "--sparse"); + h.addParameters("--name-status", parser.getPretty(), "--encoding=UTF-8", "--full-history"); h.endOptions(); - h.addRelativePaths(path); + if (paths != null && ! paths.isEmpty()) { + h.addRelativeFiles(paths); + } else { + h.addRelativePaths(path); + h.addParameters("--sparse"); + } final VcsException[] exc = new VcsException[1]; final Semaphore semaphore = new Semaphore(); @@ -609,7 +614,7 @@ public class GitHistoryUtils { public static void hashesWithParents(Project project, FilePath path, final AsynchConsumer consumer, final Getter isCanceled, - final String... parameters) throws VcsException { + Collection paths, final String... parameters) throws VcsException { // adjust path using change manager path = getLastCommitName(project, path); final VirtualFile root = GitUtil.getGitRoot(path); @@ -619,10 +624,15 @@ public class GitHistoryUtils { h.setNoSSH(true); h.setStdoutSuppressed(true); h.addParameters(parameters); - h.addParameters(parser.getPretty(), "--encoding=UTF-8", "--full-history", "--sparse"); + h.addParameters(parser.getPretty(), "--encoding=UTF-8", "--full-history"); h.endOptions(); - h.addRelativePaths(path); + if (paths != null && ! paths.isEmpty()) { + h.addRelativeFiles(paths); + } else { + h.addParameters("--sparse"); + h.addRelativePaths(path); + } final Semaphore semaphore = new Semaphore(); h.addLineListener(new GitLineHandlerListener() { diff --git a/plugins/git4idea/src/git4idea/history/browser/ChangesFilter.java b/plugins/git4idea/src/git4idea/history/browser/ChangesFilter.java index 93c97a148ab4..d7903c386f19 100644 --- a/plugins/git4idea/src/git4idea/history/browser/ChangesFilter.java +++ b/plugins/git4idea/src/git4idea/history/browser/ChangesFilter.java @@ -19,9 +19,7 @@ import com.intellij.openapi.util.Ref; import com.intellij.openapi.vcs.AreaMap; import com.intellij.openapi.vcs.FilePath; import com.intellij.openapi.vcs.changes.FilePathsHelper; -import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.ArrayUtil; import com.intellij.util.PairProcessor; import git4idea.GitUtil; import org.jetbrains.annotations.NotNull; @@ -32,21 +30,13 @@ import java.util.regex.Pattern; public class ChangesFilter { - public static void filtersToParameters(Collection filters, List parameters) { + public static void filtersToParameters(Collection filters, List parameters, Collection paths) { for (Filter filter : filters) { filter.getCommandParametersFilter().applyToCommandLine(parameters); + filter.getCommandParametersFilter().applyToPaths(paths); } } - public static String[] filtersToParameterArray(Collection filters) { - if (filters == null || filters.isEmpty()) return ArrayUtil.EMPTY_STRING_ARRAY; - final ArrayList strings = new ArrayList(); - for (Filter filter : filters) { - filter.getCommandParametersFilter().applyToCommandLine(strings); - } - return ArrayUtil.toStringArray(strings); - } - public abstract static class Merger { private final Collection myFilters; private MemoryFilter myResult; @@ -141,6 +131,7 @@ public class ChangesFilter { public interface CommandParametersFilter { void applyToCommandLine(final List sink); + void applyToPaths(Collection paths); } public interface Filter { @@ -163,6 +154,10 @@ public class ChangesFilter { public void applyToCommandLine(List sink) { sink.add("--author=" + myRegexp); } + + @Override + public void applyToPaths(Collection paths) { + } }; myMemoryFilter = new MemoryFilter() { public boolean applyInMemory(GitCommit commit) { @@ -211,6 +206,10 @@ public class ChangesFilter { public void applyToCommandLine(List sink) { sink.add("--committer=" + myRegexp); } + + @Override + public void applyToPaths(Collection paths) { + } }; myMemoryFilter = new MemoryFilter() { public boolean applyInMemory(GitCommit commit) { @@ -257,6 +256,10 @@ public class ChangesFilter { public void applyToCommandLine(List sink) { sink.add("--before=" + formatDate(myDate)); } + + @Override + public void applyToPaths(Collection paths) { + } }; myMemoryFilter = new MemoryFilter() { public boolean applyInMemory(GitCommit commit) { @@ -303,6 +306,10 @@ public class ChangesFilter { public void applyToCommandLine(List sink) { sink.add("--after=" + formatDate(myDate)); } + + @Override + public void applyToPaths(Collection paths) { + } }; myMemoryFilter = new MemoryFilter() { public boolean applyInMemory(GitCommit commit) { @@ -374,8 +381,13 @@ public class ChangesFilter { }; } - // todo optimization here - public boolean addPath(final VirtualFile vf) { + public void addFiles(final Collection files) { + for (VirtualFile file : files) { + myMap.put(FilePathsHelper.convertWithLastSeparator(file), file); + } + } + + /*public boolean addPath(final VirtualFile vf) { final Collection filesWeAlreadyHave = myMap.values(); final Collection childrenToRemove = new ArrayList(); for (VirtualFile current : filesWeAlreadyHave) { @@ -396,7 +408,7 @@ public class ChangesFilter { myMap.put(FilePathsHelper.convertWithLastSeparator(vf), vf); return true; - } + } */ public boolean containsFile(final VirtualFile vf) { return myMap.contains(FilePathsHelper.convertWithLastSeparator(vf)); @@ -412,7 +424,16 @@ public class ChangesFilter { // can be applied only in memory public CommandParametersFilter getCommandParametersFilter() { - return null; + return new CommandParametersFilter() { + @Override + public void applyToCommandLine(List sink) { + } + + @Override + public void applyToPaths(Collection paths) { + paths.addAll(myMap.values()); + } + }; } @NotNull @@ -435,6 +456,10 @@ public class ChangesFilter { sink.add("--grep=" + myRegexp); sink.add("--regexp-ignore-case"); } + + @Override + public void applyToPaths(Collection paths) { + } }; myMemoryFilter = new MemoryFilter() { public boolean applyInMemory(GitCommit commit) { diff --git a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java index 68d13be93f67..f4e046aa785e 100644 --- a/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java +++ b/plugins/git4idea/src/git4idea/history/browser/LowLevelAccessImpl.java @@ -67,7 +67,8 @@ public class LowLevelAccessImpl implements LowLevelAccess { final AsynchConsumer consumer, Getter isCanceled, int useMaxCnt) throws VcsException { final List parameters = new ArrayList(); - ChangesFilter.filtersToParameters(filters, parameters); + final Collection paths = new HashSet(); + ChangesFilter.filtersToParameters(filters, parameters, paths); if (! startingPoints.isEmpty()) { for (String startingPoint : startingPoints) { @@ -80,7 +81,7 @@ public class LowLevelAccessImpl implements LowLevelAccess { parameters.add("--max-count=" + useMaxCnt); } - GitHistoryUtils.hashesWithParents(myProject, new FilePathImpl(myRoot), consumer, isCanceled, ArrayUtil.toStringArray(parameters)); + GitHistoryUtils.hashesWithParents(myProject, new FilePathImpl(myRoot), consumer, isCanceled, paths, ArrayUtil.toStringArray(parameters)); } @Override @@ -143,7 +144,8 @@ public class LowLevelAccessImpl implements LowLevelAccess { parameters.add("--max-count=" + useMaxCnt); } - ChangesFilter.filtersToParameters(filters, parameters); + final Collection paths = new HashSet(); + ChangesFilter.filtersToParameters(filters, parameters, paths); if (! startingPoints.isEmpty()) { for (String startingPoint : startingPoints) { @@ -158,7 +160,7 @@ public class LowLevelAccessImpl implements LowLevelAccess { } GitHistoryUtils.historyWithLinks(myProject, new FilePathImpl(myRoot), - refs, consumer, isCanceled, ArrayUtil.toStringArray(parameters)); + refs, consumer, isCanceled, paths, ArrayUtil.toStringArray(parameters)); } public List getBranchesWithCommit(final SHAHash hash) throws VcsException { diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/ByRootLoader.java b/plugins/git4idea/src/git4idea/history/wholeTree/ByRootLoader.java index e1da5bff25e1..31fb1d7d6341 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/ByRootLoader.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/ByRootLoader.java @@ -20,6 +20,7 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; import com.intellij.util.continuation.ContinuationContext; import com.intellij.util.continuation.TaskDescriptor; @@ -100,8 +101,11 @@ public class ByRootLoader extends TaskDescriptor { public void consume(List filters) { ProgressManager.checkCanceled(); try { + final List parameters = new ArrayList(); + final List paths = new ArrayList(); + ChangesFilter.filtersToParameters(filters, parameters, paths); final List> stash = GitHistoryUtils.loadStashStackAsCommits(myProject, myRootHolder.getRoot(), - mySymbolicRefs, ChangesFilter.filtersToParameterArray(filters)); + mySymbolicRefs, parameters.toArray(new String[parameters.size()])); if (stash == null) return; for (Pair pair : stash) { ProgressManager.checkCanceled(); @@ -120,7 +124,7 @@ public class ByRootLoader extends TaskDescriptor { myMediator.acceptException(e); } } - }, true); + }, true, myRootHolder.getRoot()); myDetailsCache.putStash(myRootHolder.getRoot(), stashMap); ProgressManager.checkCanceled(); @@ -141,7 +145,11 @@ public class ByRootLoader extends TaskDescriptor { public void consume(List filters) { for (String hash : hashes) { try { - final SHAHash shaHash = GitChangeUtils.commitExists(myProject, myRootHolder.getRoot(), hash, ChangesFilter.filtersToParameterArray(filters)); + final List parameters = new ArrayList(); + final List paths = new ArrayList(); + ChangesFilter.filtersToParameters(filters, parameters, paths); + final SHAHash shaHash = GitChangeUtils.commitExists(myProject, myRootHolder.getRoot(), hash, paths, + parameters.toArray(new String[parameters.size()])); if (shaHash == null) continue; if (controlSet.contains(shaHash)) continue; controlSet.add(shaHash); @@ -167,7 +175,7 @@ public class ByRootLoader extends TaskDescriptor { } } } - }, false); + }, false, myRootHolder.getRoot()); if (! result.isEmpty()) { final StepType stepType = myMediator.appendResult(myTicket, result, null); diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogFilters.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogFilters.java index 639386002470..16d42dda46f8 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogFilters.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogFilters.java @@ -16,14 +16,12 @@ package git4idea.history.wholeTree; import com.google.common.collect.Sets; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; import git4idea.history.browser.ChangesFilter; import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Set; +import java.util.*; /** * @author irengrig @@ -36,7 +34,7 @@ public class GitLogFilters { @Nullable private final Set myCommitterFilters; @Nullable - private final Set myStructureFilters; + private final Map myStructureFilters; @Nullable private final List myPossibleReferencies; @@ -46,14 +44,14 @@ public class GitLogFilters { public GitLogFilters(@Nullable ChangesFilter.Comment commentFilter, @Nullable Set committerFilters, - @Nullable Set structureFilters, @Nullable List possibleReferencies) { + @Nullable Map structureFilters, @Nullable List possibleReferencies) { myCommentFilter = commentFilter; myCommitterFilters = committerFilters; myStructureFilters = structureFilters; myPossibleReferencies = possibleReferencies; } - public void callConsumer(final Consumer> consumer, boolean takeComment) { + public void callConsumer(final Consumer> consumer, boolean takeComment, final VirtualFile root) { final List> filters = new ArrayList>(); if (takeComment && myCommentFilter != null) { filters.add(Collections.singletonMap(myCommentFilter, myCommentFilter).keySet()); @@ -62,7 +60,10 @@ public class GitLogFilters { filters.add(myCommitterFilters); } if (myStructureFilters != null) { - filters.add(myStructureFilters); + final ChangesFilter.Filter filter = myStructureFilters.get(root); + if (filter != null) { + filters.add(Collections.singleton(filter)); + } } final Set> cartesian = Sets.cartesianProduct(filters); if (cartesian.isEmpty()) { @@ -85,7 +86,7 @@ public class GitLogFilters { } @Nullable - public Set getStructureFilters() { + public Map getStructureFilters() { return myStructureFilters; } @@ -98,4 +99,12 @@ public class GitLogFilters { public List getPossibleReferencies() { return myPossibleReferencies; } + + public boolean haveStructureFilter() { + return myStructureFilters != null; + } + + public boolean haveStructuresForRoot(VirtualFile root) { + return haveStructureFilter() && myStructureFilters.containsKey(root); + } } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java index c9946bd0bace..689f24368453 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/GitLogUI.java @@ -17,6 +17,7 @@ import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.diff.impl.patch.formove.FilePathComparator; import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; @@ -36,6 +37,7 @@ import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkRenderer; import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener; import com.intellij.openapi.vcs.ui.SearchFieldAction; import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; +import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.ColoredTableCellRenderer; import com.intellij.ui.PopupHandler; @@ -45,6 +47,7 @@ import com.intellij.ui.table.JBTable; import com.intellij.util.Consumer; import com.intellij.util.PairConsumer; import com.intellij.util.Processor; +import com.intellij.util.SmartList; import com.intellij.util.containers.Convertor; import com.intellij.util.containers.MultiMap; import com.intellij.util.text.DateFormatUtil; @@ -107,6 +110,8 @@ public class GitLogUI implements Disposable { private MyFilterUi myUserFilterI; private MyCherryPick myCherryPickAction; private MyRefreshAction myRefreshAction; + private MyStructureFilter myStructureFilter; + private StructureFilterAction myStructureFilterAction; private AnAction myCopyHashAction; // todo group somewhere?? private Consumer myDetailsLoaderImpl; @@ -603,6 +608,7 @@ public class GitLogUI implements Disposable { } group.add(myBranchSelectorAction.asTextAction()); group.add(myUsersFilterAction.asTextAction()); + group.add(myStructureFilterAction.asTextAction()); group.add(myCherryPickAction); group.add(ActionManager.getInstance().getAction("ChangesView.CreatePatchFromChanges")); group.add(myRefreshAction); @@ -618,16 +624,20 @@ public class GitLogUI implements Disposable { reloadRequest(); } }); - myUserFilterI = new MyFilterUi(new Runnable() { + final Runnable reloadCallback = new Runnable() { @Override public void run() { reloadRequest(); } - }); + }; + myUserFilterI = new MyFilterUi(reloadCallback); myUsersFilterAction = new UsersFilterAction(myProject, myUserFilterI); group.add(new MyTextFieldAction()); group.add(myBranchSelectorAction); group.add(myUsersFilterAction); + myStructureFilter = new MyStructureFilter(reloadCallback); + myStructureFilterAction = new StructureFilterAction(myProject, myStructureFilter); + group.add(myStructureFilterAction); myCherryPickAction = new MyCherryPick(); group.add(myCherryPickAction); group.add(ActionManager.getInstance().getAction("ChangesView.CreatePatchFromChanges")); @@ -1118,7 +1128,7 @@ public class GitLogUI implements Disposable { myCommentSearchContext.clear(); myUsersSearchContext.clear(); - if (commentFilterEmpty && (myUserFilterI.myFilter == null)) { + if (commentFilterEmpty && (myUserFilterI.myFilter == null) && myStructureFilter.myAllSelected) { myUsersSearchContext.clear(); myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, new GitLogFilters()); } else { @@ -1140,9 +1150,33 @@ public class GitLogUI implements Disposable { userFilters.add(new ChangesFilter.Author(regexp)); } } + Map structureFilters = null; + if (! myStructureFilter.myAllSelected) { + structureFilters = new HashMap(); + final Collection selected = new ArrayList(myStructureFilter.getSelected()); + final ArrayList copy = new ArrayList(myRootsUnderVcs); + Collections.sort(copy, FilePathComparator.getInstance()); + Collections.reverse(copy); + for (VirtualFile root : copy) { + final Collection selectedForRoot = new SmartList(); + final Iterator iterator = selected.iterator(); + while (iterator.hasNext()) { + VirtualFile next = iterator.next(); + if (VfsUtil.isAncestor(root, next, false)) { + selectedForRoot.add(next); + iterator.remove(); + } + } + if (! selectedForRoot.isEmpty()) { + final ChangesFilter.StructureFilter structureFilter = new ChangesFilter.StructureFilter(); + structureFilter.addFiles(selectedForRoot); + structureFilters.put(root, structureFilter); + } + } + } final List possibleReferencies = commentFilterEmpty ? null : Arrays.asList(myPreviousFilter.split("[\\s]")); - myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, new GitLogFilters(comment, userFilters, null, + myMediator.reload(new RootsHolder(myRootsUnderVcs), startingPoints, new GitLogFilters(comment, userFilters, structureFilters, possibleReferencies)); } myCommentSearchContext.addHighlighter(myDetailsPanel.getHtmlHighlighter()); @@ -1302,4 +1336,37 @@ public class GitLogUI implements Disposable { myMe = me == null ? "" : me.trim(); } } + + private static class MyStructureFilter implements StructureFilterI { + private boolean myAllSelected; + private final List myFiles; + private final Runnable myReloadCallback; + + private MyStructureFilter(Runnable reloadCallback) { + myReloadCallback = reloadCallback; + myFiles = new ArrayList(); + myAllSelected = true; + } + + @Override + public void allSelected() { + if (myAllSelected) return; + myAllSelected = true; + myReloadCallback.run(); + } + + @Override + public void select(Collection files) { + myAllSelected = false; + if (Comparing.haveEqualElements(files, myFiles)) return; + myFiles.clear(); + myFiles.addAll(files); + myReloadCallback.run(); + } + + @Override + public Collection getSelected() { + return myFiles; + } + } } diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java b/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java index b8589c22912d..edaec7fa2d80 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/LoadController.java @@ -65,15 +65,21 @@ public class LoadController implements Loader { new LoaderAndRefresherImpl.OneRootHolder(root) : new LoaderAndRefresherImpl.ManyCaseHolder(i, rootsHolder); + final boolean haveStructureFilter = filters.haveStructureFilter(); + // check if no files under root are selected + if (haveStructureFilter && ! filters.haveStructuresForRoot(root)) { + ++ i; + continue; + } filters.callConsumer(new Consumer>() { @Override public void consume(final List filters) { final LoaderAndRefresherImpl loaderAndRefresher = new LoaderAndRefresherImpl(ticket, filters, myMediator, startingPoints, myDetailsCache, myProject, rootHolder, myUsersIndex, - loadGrowthController.getId()); + loadGrowthController.getId(), haveStructureFilter); list.add(loaderAndRefresher); } - }, true); + }, true, root); shortLoaders.add(new ByRootLoader(myProject, rootHolder, myMediator, myDetailsCache, ticket, myUsersIndex, filters, startingPoints)); ++ i; diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresher.java b/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresher.java index 608360b93d5b..35be53b3c2be 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresher.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresher.java @@ -12,13 +12,10 @@ */ package git4idea.history.wholeTree; -import java.util.List; - /** * @author irengrig */ public interface LoaderAndRefresher { - void loadByHashesAside(final List hashes); LoadAlgorithm.Result load(final LoadAlgorithm.LoadType loadType, long continuation); StepType flushIntoUI(); void interrupt(); diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresherImpl.java b/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresherImpl.java index 5328e7b5525b..dbb78f9e0a44 100644 --- a/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresherImpl.java +++ b/plugins/git4idea/src/git4idea/history/wholeTree/LoaderAndRefresherImpl.java @@ -23,8 +23,10 @@ import com.intellij.util.BufferedListConsumer; import com.intellij.util.Consumer; import com.intellij.util.containers.Convertor; import git4idea.GitBranch; -import git4idea.changes.GitChangeUtils; -import git4idea.history.browser.*; +import git4idea.history.browser.ChangesFilter; +import git4idea.history.browser.GitCommit; +import git4idea.history.browser.LowLevelAccessImpl; +import git4idea.history.browser.SymbolicRefs; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -53,6 +55,7 @@ public class LoaderAndRefresherImpl implements LoaderAndRefresher hashes) { - final List result = new ArrayList(); - final List> parents = myLoadParents ? new ArrayList>() : null; - for (String hash : hashes) { - try { - final SHAHash shaHash = GitChangeUtils.commitExists(myProject, myRootHolder.getRoot(), hash); - if (shaHash == null) continue; - final List commits = myLowLevelAccess.getCommitDetails(Collections.singletonList(shaHash.getValue()), mySymbolicRefs); - myDetailsCache.acceptAnswer(commits, myRootHolder.getRoot()); - appendCommits(result, parents, commits); - } - catch (VcsException e1) { - continue; - } - } - if (! result.isEmpty()) { - final StepType stepType = myMediator.appendResult(myTicket, result, parents); - // here we react only on "stop", not on "pause" - if (StepType.STOP.equals(stepType)) { - myStepType = StepType.STOP; - } - } - } - private void appendCommits(List result, List> parents, List commits) { for (GitCommit commit : commits) { final Commit commitObj = diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/StructureFilterAction.java b/plugins/git4idea/src/git4idea/history/wholeTree/StructureFilterAction.java new file mode 100644 index 000000000000..93cb37c90c22 --- /dev/null +++ b/plugins/git4idea/src/git4idea/history/wholeTree/StructureFilterAction.java @@ -0,0 +1,96 @@ +/* + * Copyright 2000-2011 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 git4idea.history.wholeTree; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.project.DumbAwareAction; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import git4idea.GitVcs; + +import java.util.Collection; +import java.util.Map; + +/** + * @author irengrig + * Date: 2/3/11 + * Time: 4:29 PM + */ +public class StructureFilterAction extends BasePopupAction { + public static final String ALL = "All"; + public static final String STRUCTURE = "Structure:"; + public static final String FILTER = "(filter)"; + private final DumbAwareAction myAll; + private final DumbAwareAction mySelect; + private final StructureFilterI myStructureFilterI; + + public StructureFilterAction(Project project, final StructureFilterI structureFilterI) { + super(project, STRUCTURE, "Structure"); + myStructureFilterI = structureFilterI; + myAll = new DumbAwareAction(ALL) { + @Override + public void actionPerformed(AnActionEvent e) { + myLabel.setText(ALL); + myPanel.setToolTipText(STRUCTURE + " " + ALL); + structureFilterI.allSelected(); + } + }; + mySelect = new DumbAwareAction("Select...") { + @Override + public void actionPerformed(AnActionEvent e) { + final VcsStructureChooser vcsStructureChooser = + new VcsStructureChooser(GitVcs.getInstance(myProject), "Select folders to filter by", structureFilterI.getSelected()); + vcsStructureChooser.show(); + if (vcsStructureChooser.getExitCode() == DialogWrapper.CANCEL_EXIT_CODE) return; + final Collection files = vcsStructureChooser.getSelectedFiles(); + final Map modulesSet = vcsStructureChooser.getModulesSet(); + String text; + if (files.size() == 1) { + final VirtualFile file = files.iterator().next(); + final String module = modulesSet.get(file); + text = module == null ? file.getName() : module; + } + else { + text = FILTER; + } + text = text.length() > 20 ? FILTER : text; + myLabel.setText(text); + + final String toolTip; + final StringBuilder sb = new StringBuilder(); + for (VirtualFile file : files) { + sb.append("
"); + final String module = modulesSet.get(file); + final String name = module == null ? file.getName() : module; + sb.append(name).append(" (").append(file.getPath()).append(")"); + } + toolTip = sb.toString(); + myPanel.setToolTipText("" + STRUCTURE + "
" + toolTip + ""); + structureFilterI.select(files); + } + }; + myLabel.setText(ALL); + } + + @Override + protected void createActions(Consumer actionConsumer) { + actionConsumer.consume(myAll); + actionConsumer.consume(mySelect); + } +} diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/StructureFilterI.java b/plugins/git4idea/src/git4idea/history/wholeTree/StructureFilterI.java new file mode 100644 index 000000000000..1a2dd4a2d8e0 --- /dev/null +++ b/plugins/git4idea/src/git4idea/history/wholeTree/StructureFilterI.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2011 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 git4idea.history.wholeTree; + +import com.intellij.openapi.vfs.VirtualFile; + +import java.util.Collection; + +/** + * @author irengrig + * Date: 7/8/11 + * Time: 1:49 PM + */ +public interface StructureFilterI { + void allSelected(); + void select(final Collection files); + Collection getSelected(); +} diff --git a/plugins/git4idea/src/git4idea/history/wholeTree/VcsStructureChooser.java b/plugins/git4idea/src/git4idea/history/wholeTree/VcsStructureChooser.java new file mode 100644 index 000000000000..f3cc9fccc6e5 --- /dev/null +++ b/plugins/git4idea/src/git4idea/history/wholeTree/VcsStructureChooser.java @@ -0,0 +1,457 @@ +/* + * Copyright 2000-2011 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 git4idea.history.wholeTree; + +import com.intellij.ide.util.treeView.AbstractTreeUi; +import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.diff.impl.patch.formove.FilePathComparator; +import com.intellij.openapi.fileChooser.FileChooserDescriptor; +import com.intellij.openapi.fileChooser.ex.FileNodeDescriptor; +import com.intellij.openapi.fileChooser.ex.FileSystemTreeImpl; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.ui.Splitter; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.vcs.AbstractVcs; +import com.intellij.openapi.vcs.FilePath; +import com.intellij.openapi.vcs.ProjectLevelVcsManager; +import com.intellij.openapi.vcs.changes.ui.VirtualFileListCellRenderer; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.*; +import com.intellij.ui.components.JBList; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.PlatformIcons; +import com.intellij.util.PlusMinus; +import com.intellij.util.TreeNodeState; +import com.intellij.util.containers.Convertor; +import com.intellij.util.containers.hash.HashSet; +import com.intellij.util.treeWithCheckedNodes.SelectionManager; +import com.intellij.util.ui.UIUtil; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeCellRenderer; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.*; + +/** + * @author irengrig + * Date: 2/3/11 + * Time: 12:04 PM + */ +public class VcsStructureChooser extends DialogWrapper { + private final static int MAX_FOLDERS = 10; + public static final Border BORDER = IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.LEFT); + public static final String DEFAULT_TEXT = "Selected:"; + public static final String CAN_NOT_ADD_TEXT = "Selected: (You have added " + MAX_FOLDERS + " elements. No more is allowed.)"; + private final AbstractVcs myVcs; + private Set myRoots; + private Map myModulesSet; + private SelectionManager mySelectionManager; + private DefaultMutableTreeNode myRoot; + private JBList mySelectedList; + private JLabel mySelectedLabel; + private Tree myTree; + + public VcsStructureChooser(final AbstractVcs vcs, final String title, final Collection initialSelection) { + super(vcs.getProject(), true); + setTitle(title); + myVcs = vcs; + mySelectionManager = new SelectionManager(MAX_FOLDERS, 500, MyNodeConvertor.getInstance()); + init(); + mySelectionManager.setSelection(initialSelection); + checkEmptyness(); + } + + // todo background? + private void calculateRoots() { + final ProjectLevelVcsManager vcsManager = ProjectLevelVcsManager.getInstance(myVcs.getProject()); + final VirtualFile[] rootsUnderVcs = vcsManager.getRootsUnderVcs(myVcs); + + final ModuleManager moduleManager = ModuleManager.getInstance(myVcs.getProject()); + // assertion for read access inside + final Module[] modules = ApplicationManager.getApplication().runReadAction(new Computable() { + public Module[] compute() { + return moduleManager.getModules(); + } + }); + + myRoots = new HashSet(); + myRoots.addAll(Arrays.asList(rootsUnderVcs)); + myModulesSet = new HashMap(); + for (Module module : modules) { + final VirtualFile[] files = ModuleRootManager.getInstance(module).getContentRoots(); + for (VirtualFile file : files) { + if (myVcs.equals(vcsManager.getVcsFor(file))) { + myModulesSet.put(file, module.getName()); + myRoots.add(file); + } + } + } + } + + public Map getModulesSet() { + return myModulesSet; + } + + public Collection getSelectedFiles() { + return ((CollectionListModel) mySelectedList.getModel()).getItems(); + } + + private void checkEmptyness() { + setOKActionEnabled(mySelectedList.getModel().getSize() > 0); + } + + @Override + protected String getDimensionServiceKey() { + return "git4idea.history.wholeTree.VcsStructureChooser"; + } + + @Override + public JComponent getPreferredFocusedComponent() { + return myTree; + } + + @Override + protected JComponent createCenterPanel() { + final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, true, true, true, false, true); + calculateRoots(); + final ArrayList list = new ArrayList(myRoots); + final Comparator comparator = new Comparator() { + @Override + public int compare(VirtualFile o1, VirtualFile o2) { + final String module1 = myModulesSet.get(o1); + final String path1 = module1 != null ? module1 : o1.getPath(); + final String module2 = myModulesSet.get(o2); + final String path2 = module2 != null ? module2 : o2.getPath(); + return path1.compareToIgnoreCase(path2); + } + }; + for (VirtualFile root : list) { + descriptor.addRoot(root); + } + myTree = new Tree(); + myTree.setMinimumSize(new Dimension(200, 200)); + myTree.setBorder(BORDER); + myTree.setShowsRootHandles(true); + myTree.setRootVisible(true); + final MyCheckboxTreeCellRenderer cellRenderer = new MyCheckboxTreeCellRenderer(mySelectionManager, myModulesSet, myVcs.getProject(), + myTree, myRoots); + final FileSystemTreeImpl fileSystemTree = new FileSystemTreeImpl(myVcs.getProject(), descriptor, myTree, cellRenderer, null, new Convertor() { + @Override + public String convert(TreePath o) { + final DefaultMutableTreeNode lastPathComponent = ((DefaultMutableTreeNode) o.getLastPathComponent()); + final Object uo = lastPathComponent.getUserObject(); + if (uo instanceof FileNodeDescriptor) { + final VirtualFile file = ((FileNodeDescriptor)uo).getElement().getFile(); + final String module = myModulesSet.get(file); + if (module != null) return module; + return file == null ? "" : file.getName(); + } + return o.toString(); + } + }); + final AbstractTreeUi ui = fileSystemTree.getTreeBuilder().getUi(); + ui.setNodeDescriptorComparator(new Comparator() { + @Override + public int compare(NodeDescriptor o1, NodeDescriptor o2) { + if (o1 instanceof FileNodeDescriptor && o2 instanceof FileNodeDescriptor) { + final VirtualFile f1 = ((FileNodeDescriptor)o1).getElement().getFile(); + final VirtualFile f2 = ((FileNodeDescriptor)o2).getElement().getFile(); + return comparator.compare(f1, f2); + } + return o1.getIndex() - o2.getIndex(); + } + }); + myRoot = (DefaultMutableTreeNode)myTree.getModel().getRoot(); + + myTree.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + int row = myTree.getRowForLocation(e.getX(), e.getY()); + if (row < 0) return; + final Object o = myTree.getPathForRow(row).getLastPathComponent(); + if (myRoot == o || getFile(o) == null) return; + + Rectangle rowBounds = myTree.getRowBounds(row); + cellRenderer.setBounds(rowBounds); + Rectangle checkBounds = cellRenderer.myCheckbox.getBounds(); + checkBounds.setLocation(rowBounds.getLocation()); + + if (checkBounds.height == 0) checkBounds.height = rowBounds.height; + + if (checkBounds.contains(e.getPoint())) { + mySelectionManager.toggleSelection((DefaultMutableTreeNode)o); + myTree.revalidate(); + myTree.repaint(); + e.consume(); + } + } + }); + + myTree.addKeyListener(new KeyAdapter() { + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_SPACE) { + TreePath treePath = myTree.getLeadSelectionPath(); + if (treePath == null) return; + final Object o = treePath.getLastPathComponent(); + if (myRoot == o || getFile(o) == null) return; + mySelectionManager.toggleSelection((DefaultMutableTreeNode)o); + myTree.revalidate(); + myTree.repaint(); + e.consume(); + } + } + }); + + final Splitter splitter = new Splitter(true, 0.7f); + splitter.setFirstComponent(new JBScrollPane(fileSystemTree.getTree())); + final JPanel wrapper = new JPanel(new BorderLayout()); + mySelectedLabel = new JLabel(DEFAULT_TEXT); + mySelectedLabel.setBorder(BorderFactory.createEmptyBorder(2, 0, 2, 0)); + wrapper.add(mySelectedLabel, BorderLayout.NORTH); + mySelectedList = new JBList(new CollectionListModel(new ArrayList())); + mySelectedList.setCellRenderer(new WithModulesListCellRenderer(myVcs.getProject(), myModulesSet)); + wrapper.add(ScrollPaneFactory.createScrollPane(mySelectedList), BorderLayout.CENTER); + splitter.setSecondComponent(wrapper); + + mySelectionManager.setSelectionChangeListener(new PlusMinus() { + @Override + public void plus(VirtualFile virtualFile) { + final CollectionListModel model = (CollectionListModel)mySelectedList.getModel(); + model.add(virtualFile); + model.sort(FilePathComparator.getInstance()); + recalculateErrorText(); + mySelectedList.revalidate(); + mySelectedList.repaint(); + } + + private void recalculateErrorText() { + checkEmptyness(); + if (mySelectionManager.canAddSelection()) { + mySelectedLabel.setText(DEFAULT_TEXT); + } else { + mySelectedLabel.setText(CAN_NOT_ADD_TEXT); + } + mySelectedLabel.revalidate(); + } + + @Override + public void minus(VirtualFile virtualFile) { + final CollectionListModel defaultListModel = (CollectionListModel)mySelectedList.getModel(); + for (int i = 0; i < defaultListModel.getSize(); i++) { + final VirtualFile elementAt = (VirtualFile)defaultListModel.getElementAt(i); + if (virtualFile.equals(elementAt)) { + defaultListModel.remove(i); + break; + } + } + defaultListModel.sort(FilePathComparator.getInstance()); + recalculateErrorText(); + mySelectedList.revalidate(); + mySelectedList.repaint(); + } + }); + mySelectedList.addKeyListener(new KeyAdapter() { + @Override + public void keyReleased(KeyEvent e) { + if (e.getModifiers() == 0 && e.getKeyCode() == KeyEvent.VK_DELETE) { + final int[] idx = mySelectedList.getSelectedIndices(); + if (idx != null && idx.length > 0) { + final int answer = Messages + .showYesNoDialog(myVcs.getProject(), "Remove selected paths from filter?", "Remove from filter", Messages.getQuestionIcon()); + if (Messages.OK == answer) { + Arrays.sort(idx); + for (int i = idx.length - 1; i >= 0; --i) { + int i1 = idx[i]; + mySelectionManager.removeSelection((VirtualFile)((CollectionListModel) mySelectedList.getModel()).getElementAt(i1)); + myTree.revalidate(); + myTree.repaint(); + } + } + } + } + } + }); + + return splitter; + } + + @Nullable + private static VirtualFile getFile(final Object node) { + if (! (((DefaultMutableTreeNode)node).getUserObject() instanceof FileNodeDescriptor)) return null; + final FileNodeDescriptor descriptor = (FileNodeDescriptor)((DefaultMutableTreeNode)node).getUserObject(); + if (descriptor.getElement().getFile() == null) return null; + return descriptor.getElement().getFile(); + } + + private static class MyCheckboxTreeCellRenderer extends JPanel implements TreeCellRenderer { + private final WithModulesListCellRenderer myTextRenderer; + public final JCheckBox myCheckbox; + private final SelectionManager mySelectionManager; + private final Map myModulesSet; + private final Collection myRoots; + private final ColoredTreeCellRenderer myColoredRenderer; + private final JLabel myEmpty; + private final JList myFictive; + + private MyCheckboxTreeCellRenderer(final SelectionManager selectionManager, Map modulesSet, final Project project, + final JTree tree, final Collection roots) { + super(new BorderLayout()); + mySelectionManager = selectionManager; + myModulesSet = modulesSet; + myRoots = roots; + myColoredRenderer = new ColoredTreeCellRenderer() { + @Override + public void customizeCellRenderer(JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + append(value.toString()); + } + }; + myFictive = new JBList(); + myFictive.setBackground(tree.getBackground()); + myFictive.setSelectionBackground(UIUtil.getListSelectionBackground()); + myFictive.setSelectionForeground(UIUtil.getListSelectionForeground()); + + myTextRenderer = new WithModulesListCellRenderer(project, myModulesSet) { + @Override + protected void putParentPath(Object value, FilePath path, FilePath self) { + if (myRoots.contains(self.getVirtualFile())) { + super.putParentPath(value, path, self); + } + } + }; + + myCheckbox = new JCheckBox(); + myEmpty = new JLabel(""); + + add(myCheckbox, BorderLayout.WEST); + add(myTextRenderer, BorderLayout.CENTER); + myCheckbox.setVisible(true); + } + + @Override + public Component getTreeCellRendererComponent(JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + myTextRenderer.setOpened(expanded); + invalidate(); + final VirtualFile file = getFile(value); + final DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; + if (file == null) { + if (value instanceof DefaultMutableTreeNode) { + final Object uo = node.getUserObject(); + if (uo instanceof String) { + myColoredRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); + return myColoredRenderer; + } + } + return myEmpty; + } + myCheckbox.setVisible(true); + final TreeNodeState state = mySelectionManager.getState(node); + myCheckbox.setEnabled(TreeNodeState.CLEAR.equals(state) || TreeNodeState.SELECTED.equals(state)); + myCheckbox.setSelected(!TreeNodeState.CLEAR.equals(state)); + myTextRenderer.getListCellRendererComponent(myFictive, file, 0, selected, hasFocus); + revalidate(); + return this; + } + } + + private static class MyNodeConvertor implements Convertor { + private final static MyNodeConvertor ourInstance = new MyNodeConvertor(); + + public static MyNodeConvertor getInstance() { + return ourInstance; + } + + @Override + public VirtualFile convert(DefaultMutableTreeNode o) { + return ((FileNodeDescriptor)o.getUserObject()).getElement().getFile(); + } + } + + private static class WithModulesListCellRenderer extends VirtualFileListCellRenderer { + private boolean opened; + private final Map myModules; + + private WithModulesListCellRenderer(Project project, final Map modules) { + super(project, true); + myModules = modules; + } + + public void setOpened(boolean opened) { + this.opened = opened; + } + + @Override + protected String getName(FilePath path) { + final String module = myModules.get(path.getVirtualFile()); + if (module != null) { + return module; + } + return super.getName(path); + } + + @Override + protected void renderIcon(FilePath path) { + final String module = myModules.get(path.getVirtualFile()); + if (module != null) { + if (opened) { + setIcon(PlatformIcons.CONTENT_ROOT_ICON_OPEN); + } else { + setIcon(PlatformIcons.CONTENT_ROOT_ICON_CLOSED); + } + } else { + if (path.isDirectory()) { + if (opened) { + setIcon(PlatformIcons.DIRECTORY_OPEN_ICON); + } else { + setIcon(PlatformIcons.DIRECTORY_CLOSED_ICON); + } + } else { + setIcon(path.getFileType().getIcon()); + } + } + } + + @Override + protected void putParentPathImpl(Object value, String parentPath, FilePath self) { + append(self.getPath(), SimpleTextAttributes.GRAYED_ATTRIBUTES); + } + } +} diff --git a/plugins/git4idea/tests/git4idea/tests/GitHistoryUtilsTest.java b/plugins/git4idea/tests/git4idea/tests/GitHistoryUtilsTest.java index f47dd289b68b..e5db5bc01289 100644 --- a/plugins/git4idea/tests/git4idea/tests/GitHistoryUtilsTest.java +++ b/plugins/git4idea/tests/git4idea/tests/GitHistoryUtilsTest.java @@ -270,7 +270,7 @@ public class GitHistoryUtilsTest extends GitSingleUserTest { } }; - GitHistoryUtils.hashesWithParents(myProject, bfilePath, consumer, null); + GitHistoryUtils.hashesWithParents(myProject, bfilePath, consumer, null, null); assertEquals(hashesWithParents.size(), expectedSize); for (Iterator hit = hashesWithParents.iterator(), myIt = myRevisionsAfterRename.iterator(); hit.hasNext(); ) {