git log structure chooser

This commit is contained in:
irengrig
2011-07-08 19:48:10 +04:00
parent 67d0f6c20b
commit 6cb6e67ddb
22 changed files with 997 additions and 136 deletions
@@ -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<NodeDescriptor> comparator, FileChooserDescriptor descriptor,
final Runnable onInitialized) {
@@ -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);
}
}
@@ -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<TreePath, String> 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
*/
@@ -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<T> 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<Pair<String,ChangeNodeDecorator.Stress>> parts = null;
if (applyChangeDecorator) {
parts = myChangeDecorator.stressPartsOfFileName((Change)value, parentPath);
}
if (parts == null) {
parts = Collections.singletonList(new Pair<String, ChangeNodeDecorator.Stress>(parentPath, ChangeNodeDecorator.Stress.PLAIN));
}
append(" (");
for (Pair<String, ChangeNodeDecorator.Stress> 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<Pair<String,ChangeNodeDecorator.Stress>> parts = null;
if (applyChangeDecorator) {
parts = myChangeDecorator.stressPartsOfFileName((Change)value, parentPath);
}
if (parts == null) {
super.putParentPathImpl(value, parentPath, self);
return;
}
for (Pair<String, ChangeNodeDecorator.Stress> part : parts) {
append(part.getFirst(), part.getSecond().derive(SimpleTextAttributes.GRAYED_ATTRIBUTES));
}
}
};
myCheckbox.setBackground(null);
@@ -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);
}
}
@@ -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<T> {
public Set<T> getSelected() {
return Collections.unmodifiableSet(mySelected);
}
public void setSelection(Collection<T> files) {
mySelected.clear();
mySelected.addAll(files);
}
}
@@ -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<VirtualFile> myState;
private final Convertor<DefaultMutableTreeNode, VirtualFile> myNodeConvertor;
@Nullable
private PlusMinus<VirtualFile> mySelectionChangeListener;
public SelectionManager(int selectedSize, int queueSize, final Convertor<DefaultMutableTreeNode, VirtualFile> 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<VirtualFile> old = new HashSet<VirtualFile>(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<VirtualFile>() {
@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<VirtualFile>() {
@Override
public boolean process(VirtualFile vf) {
@@ -84,6 +96,38 @@ public class SelectionManager {
myState.remove(selected);
}
}
final Set<VirtualFile> selectedAfter = myState.getSelected();
if (mySelectionChangeListener != null && ! old.equals(selectedAfter)) {
final Set<VirtualFile> removed = CollectionsDelta.notInSecond(old, selectedAfter);
final Set<VirtualFile> 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<VirtualFile> 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<VirtualFile>() {
@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<DefaultMutableTreeNode, VirtualFile> myConvertor;
@@ -148,4 +205,12 @@ public class SelectionManager {
}
}
}
public PlusMinus<VirtualFile> getSelectionChangeListener() {
return mySelectionChangeListener;
}
public void setSelectionChangeListener(PlusMinus<VirtualFile> selectionChangeListener) {
mySelectionChangeListener = selectionChangeListener;
}
}
@@ -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<VirtualFile> 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;
@@ -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 + "'.");
}
@@ -410,11 +410,11 @@ public class GitHistoryUtils {
}
public static void historyWithLinks(final Project project,
FilePath path,
final SymbolicRefs refs,
final AsynchConsumer<GitCommit> gitCommitConsumer,
final Getter<Boolean> isCanceled,
final String... parameters) throws VcsException {
FilePath path,
final SymbolicRefs refs,
final AsynchConsumer<GitCommit> gitCommitConsumer,
final Getter<Boolean> isCanceled,
Collection<VirtualFile> 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<CommitHashPlusParents> consumer,
final Getter<Boolean> isCanceled,
final String... parameters) throws VcsException {
Collection<VirtualFile> 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() {
@@ -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<Filter> filters, List<String> parameters) {
public static void filtersToParameters(Collection<Filter> filters, List<String> parameters, Collection<VirtualFile> paths) {
for (Filter filter : filters) {
filter.getCommandParametersFilter().applyToCommandLine(parameters);
filter.getCommandParametersFilter().applyToPaths(paths);
}
}
public static String[] filtersToParameterArray(Collection<Filter> filters) {
if (filters == null || filters.isEmpty()) return ArrayUtil.EMPTY_STRING_ARRAY;
final ArrayList<String> strings = new ArrayList<String>();
for (Filter filter : filters) {
filter.getCommandParametersFilter().applyToCommandLine(strings);
}
return ArrayUtil.toStringArray(strings);
}
public abstract static class Merger {
private final Collection<MemoryFilter> myFilters;
private MemoryFilter myResult;
@@ -141,6 +131,7 @@ public class ChangesFilter {
public interface CommandParametersFilter {
void applyToCommandLine(final List<String> sink);
void applyToPaths(Collection<VirtualFile> paths);
}
public interface Filter {
@@ -163,6 +154,10 @@ public class ChangesFilter {
public void applyToCommandLine(List<String> sink) {
sink.add("--author=" + myRegexp);
}
@Override
public void applyToPaths(Collection<VirtualFile> paths) {
}
};
myMemoryFilter = new MemoryFilter() {
public boolean applyInMemory(GitCommit commit) {
@@ -211,6 +206,10 @@ public class ChangesFilter {
public void applyToCommandLine(List<String> sink) {
sink.add("--committer=" + myRegexp);
}
@Override
public void applyToPaths(Collection<VirtualFile> paths) {
}
};
myMemoryFilter = new MemoryFilter() {
public boolean applyInMemory(GitCommit commit) {
@@ -257,6 +256,10 @@ public class ChangesFilter {
public void applyToCommandLine(List<String> sink) {
sink.add("--before=" + formatDate(myDate));
}
@Override
public void applyToPaths(Collection<VirtualFile> paths) {
}
};
myMemoryFilter = new MemoryFilter() {
public boolean applyInMemory(GitCommit commit) {
@@ -303,6 +306,10 @@ public class ChangesFilter {
public void applyToCommandLine(List<String> sink) {
sink.add("--after=" + formatDate(myDate));
}
@Override
public void applyToPaths(Collection<VirtualFile> 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<VirtualFile> files) {
for (VirtualFile file : files) {
myMap.put(FilePathsHelper.convertWithLastSeparator(file), file);
}
}
/*public boolean addPath(final VirtualFile vf) {
final Collection<VirtualFile> filesWeAlreadyHave = myMap.values();
final Collection<VirtualFile> childrenToRemove = new ArrayList<VirtualFile>();
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<String> sink) {
}
@Override
public void applyToPaths(Collection<VirtualFile> 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<VirtualFile> paths) {
}
};
myMemoryFilter = new MemoryFilter() {
public boolean applyInMemory(GitCommit commit) {
@@ -67,7 +67,8 @@ public class LowLevelAccessImpl implements LowLevelAccess {
final AsynchConsumer<CommitHashPlusParents> consumer,
Getter<Boolean> isCanceled, int useMaxCnt) throws VcsException {
final List<String> parameters = new ArrayList<String>();
ChangesFilter.filtersToParameters(filters, parameters);
final Collection<VirtualFile> paths = new HashSet<VirtualFile>();
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<VirtualFile> paths = new HashSet<VirtualFile>();
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<String> getBranchesWithCommit(final SHAHash hash) throws VcsException {
@@ -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<ChangesFilter.Filter> filters) {
ProgressManager.checkCanceled();
try {
final List<String> parameters = new ArrayList<String>();
final List<VirtualFile> paths = new ArrayList<VirtualFile>();
ChangesFilter.filtersToParameters(filters, parameters, paths);
final List<Pair<String,GitCommit>> stash = GitHistoryUtils.loadStashStackAsCommits(myProject, myRootHolder.getRoot(),
mySymbolicRefs, ChangesFilter.filtersToParameterArray(filters));
mySymbolicRefs, parameters.toArray(new String[parameters.size()]));
if (stash == null) return;
for (Pair<String, GitCommit> 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<ChangesFilter.Filter> filters) {
for (String hash : hashes) {
try {
final SHAHash shaHash = GitChangeUtils.commitExists(myProject, myRootHolder.getRoot(), hash, ChangesFilter.filtersToParameterArray(filters));
final List<String> parameters = new ArrayList<String>();
final List<VirtualFile> paths = new ArrayList<VirtualFile>();
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);
@@ -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<ChangesFilter.Filter> myCommitterFilters;
@Nullable
private final Set<ChangesFilter.Filter> myStructureFilters;
private final Map<VirtualFile, ChangesFilter.Filter> myStructureFilters;
@Nullable
private final List<String> myPossibleReferencies;
@@ -46,14 +44,14 @@ public class GitLogFilters {
public GitLogFilters(@Nullable ChangesFilter.Comment commentFilter,
@Nullable Set<ChangesFilter.Filter> committerFilters,
@Nullable Set<ChangesFilter.Filter> structureFilters, @Nullable List<String> possibleReferencies) {
@Nullable Map<VirtualFile, ChangesFilter.Filter> structureFilters, @Nullable List<String> possibleReferencies) {
myCommentFilter = commentFilter;
myCommitterFilters = committerFilters;
myStructureFilters = structureFilters;
myPossibleReferencies = possibleReferencies;
}
public void callConsumer(final Consumer<List<ChangesFilter.Filter>> consumer, boolean takeComment) {
public void callConsumer(final Consumer<List<ChangesFilter.Filter>> consumer, boolean takeComment, final VirtualFile root) {
final List<Set<ChangesFilter.Filter>> filters = new ArrayList<Set<ChangesFilter.Filter>>();
if (takeComment && myCommentFilter != null) {
filters.add(Collections.<ChangesFilter.Filter, ChangesFilter.Filter>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<List<ChangesFilter.Filter>> cartesian = Sets.cartesianProduct(filters);
if (cartesian.isEmpty()) {
@@ -85,7 +86,7 @@ public class GitLogFilters {
}
@Nullable
public Set<ChangesFilter.Filter> getStructureFilters() {
public Map<VirtualFile,ChangesFilter.Filter> getStructureFilters() {
return myStructureFilters;
}
@@ -98,4 +99,12 @@ public class GitLogFilters {
public List<String> getPossibleReferencies() {
return myPossibleReferencies;
}
public boolean haveStructureFilter() {
return myStructureFilters != null;
}
public boolean haveStructuresForRoot(VirtualFile root) {
return haveStructureFilter() && myStructureFilters.containsKey(root);
}
}
@@ -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<CommitI> 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<VirtualFile, ChangesFilter.Filter> structureFilters = null;
if (! myStructureFilter.myAllSelected) {
structureFilters = new HashMap<VirtualFile, ChangesFilter.Filter>();
final Collection<VirtualFile> selected = new ArrayList<VirtualFile>(myStructureFilter.getSelected());
final ArrayList<VirtualFile> copy = new ArrayList<VirtualFile>(myRootsUnderVcs);
Collections.sort(copy, FilePathComparator.getInstance());
Collections.reverse(copy);
for (VirtualFile root : copy) {
final Collection<VirtualFile> selectedForRoot = new SmartList<VirtualFile>();
final Iterator<VirtualFile> 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<String> 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<VirtualFile> myFiles;
private final Runnable myReloadCallback;
private MyStructureFilter(Runnable reloadCallback) {
myReloadCallback = reloadCallback;
myFiles = new ArrayList<VirtualFile>();
myAllSelected = true;
}
@Override
public void allSelected() {
if (myAllSelected) return;
myAllSelected = true;
myReloadCallback.run();
}
@Override
public void select(Collection<VirtualFile> files) {
myAllSelected = false;
if (Comparing.haveEqualElements(files, myFiles)) return;
myFiles.clear();
myFiles.addAll(files);
myReloadCallback.run();
}
@Override
public Collection<VirtualFile> getSelected() {
return myFiles;
}
}
}
@@ -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<List<ChangesFilter.Filter>>() {
@Override
public void consume(final List<ChangesFilter.Filter> 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;
@@ -12,13 +12,10 @@
*/
package git4idea.history.wholeTree;
import java.util.List;
/**
* @author irengrig
*/
public interface LoaderAndRefresher<T> {
void loadByHashesAside(final List<String> hashes);
LoadAlgorithm.Result<T> load(final LoadAlgorithm.LoadType loadType, long continuation);
StepType flushIntoUI();
void interrupt();
@@ -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<CommitHashPlus
private LowLevelAccessImpl myLowLevelAccess;
private SymbolicRefs mySymbolicRefs;
private final LoadGrowthController.ID myId;
private final boolean myHaveStructureFilter;
// state
@NotNull
private volatile StepType myStepType;
@@ -69,10 +72,11 @@ public class LoaderAndRefresherImpl implements LoaderAndRefresher<CommitHashPlus
Project project,
MyRootHolder rootHolder,
final UsersIndex usersIndex,
final LoadGrowthController.ID id) {
final LoadGrowthController.ID id, boolean haveStructureFilter) {
myRootHolder = rootHolder;
myUsersIndex = usersIndex;
myId = id;
myHaveStructureFilter = haveStructureFilter;
myLoadParents = filters == null || filters.isEmpty();
myTicket = ticket;
myFilters = filters;
@@ -142,7 +146,7 @@ public class LoaderAndRefresherImpl implements LoaderAndRefresher<CommitHashPlus
myRepeatingLoadConsumer.reset();
int count = MediatorImpl.ourManyLoadedStep;
boolean shouldFull = true;
boolean shouldFull = ! myHaveStructureFilter;
if (LoadAlgorithm.LoadType.TEST.equals(loadType)) {
count = ourFirstLoadCount;
} else if (LoadAlgorithm.LoadType.SHORT.equals(loadType) || LoadAlgorithm.LoadType.SHORT_START.equals(loadType)) {
@@ -251,30 +255,6 @@ public class LoaderAndRefresherImpl implements LoaderAndRefresher<CommitHashPlus
return filters;
}
public void loadByHashesAside(final List<String> hashes) {
final List<CommitI> result = new ArrayList<CommitI>();
final List<List<AbstractHash>> parents = myLoadParents ? new ArrayList<List<AbstractHash>>() : null;
for (String hash : hashes) {
try {
final SHAHash shaHash = GitChangeUtils.commitExists(myProject, myRootHolder.getRoot(), hash);
if (shaHash == null) continue;
final List<GitCommit> 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<CommitI> result, List<List<AbstractHash>> parents, List<GitCommit> commits) {
for (GitCommit commit : commits) {
final Commit commitObj =
@@ -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<VirtualFile> files = vcsStructureChooser.getSelectedFiles();
final Map<VirtualFile,String> 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("<br><b>");
final String module = modulesSet.get(file);
final String name = module == null ? file.getName() : module;
sb.append(name).append("</b> (").append(file.getPath()).append(")");
}
toolTip = sb.toString();
myPanel.setToolTipText("<html><b>" + STRUCTURE + "</b><br>" + toolTip + "</html>");
structureFilterI.select(files);
}
};
myLabel.setText(ALL);
}
@Override
protected void createActions(Consumer<AnAction> actionConsumer) {
actionConsumer.consume(myAll);
actionConsumer.consume(mySelect);
}
}
@@ -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<VirtualFile> files);
Collection<VirtualFile> getSelected();
}
@@ -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 = "<html>Selected:</html>";
public static final String CAN_NOT_ADD_TEXT = "<html>Selected: <font color=red>(You have added " + MAX_FOLDERS + " elements. No more is allowed.)</font></html>";
private final AbstractVcs myVcs;
private Set<VirtualFile> myRoots;
private Map<VirtualFile, String> 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<VirtualFile> 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<Module[]>() {
public Module[] compute() {
return moduleManager.getModules();
}
});
myRoots = new HashSet<VirtualFile>();
myRoots.addAll(Arrays.asList(rootsUnderVcs));
myModulesSet = new HashMap<VirtualFile, String>();
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<VirtualFile, String> getModulesSet() {
return myModulesSet;
}
public Collection<VirtualFile> 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<VirtualFile> list = new ArrayList<VirtualFile>(myRoots);
final Comparator<VirtualFile> comparator = new Comparator<VirtualFile>() {
@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<TreePath, String>() {
@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<NodeDescriptor>() {
@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<VirtualFile>()));
mySelectedList.setCellRenderer(new WithModulesListCellRenderer(myVcs.getProject(), myModulesSet));
wrapper.add(ScrollPaneFactory.createScrollPane(mySelectedList), BorderLayout.CENTER);
splitter.setSecondComponent(wrapper);
mySelectionManager.setSelectionChangeListener(new PlusMinus<VirtualFile>() {
@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<VirtualFile, String> myModulesSet;
private final Collection<VirtualFile> myRoots;
private final ColoredTreeCellRenderer myColoredRenderer;
private final JLabel myEmpty;
private final JList myFictive;
private MyCheckboxTreeCellRenderer(final SelectionManager selectionManager, Map<VirtualFile, String> modulesSet, final Project project,
final JTree tree, final Collection<VirtualFile> 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<DefaultMutableTreeNode, VirtualFile> {
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<VirtualFile, String> myModules;
private WithModulesListCellRenderer(Project project, final Map<VirtualFile, String> 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);
}
}
}
@@ -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(); ) {