UX-1 new SearchEverywhere popup

* filters
This commit is contained in:
Mikhail Sokolov
2018-06-04 12:19:33 +03:00
parent dcc337a97d
commit 5e5dc50342
11 changed files with 328 additions and 36 deletions
@@ -19,7 +19,7 @@ public interface SearchEverywhereContributor {
String ALL_CONTRIBUTORS_GROUP_ID = SearchEverywhereContributor.class.getSimpleName() + ".All";
ExtensionPointName<SearchEverywhereContributorFactory> EP_NAME = ExtensionPointName.create("com.intellij.searchEverywhereContributor");
ExtensionPointName<SearchEverywhereContributorFactory<?>> EP_NAME = ExtensionPointName.create("com.intellij.searchEverywhereContributor");
@NotNull
String getSearchProviderId();
@@ -45,7 +45,7 @@ public interface SearchEverywhereContributor {
Object getDataForItem(Object element, String dataId);
static List<SearchEverywhereContributorFactory> getProviders() {
static List<SearchEverywhereContributorFactory<?>> getProviders() {
return Arrays.asList(EP_NAME.getExtensions());
}
}
@@ -1,7 +1,14 @@
package com.intellij.ide.actions.searcheverywhere;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface SearchEverywhereContributorFactory {
public interface SearchEverywhereContributorFactory<T> {
@NotNull
SearchEverywhereContributor createContributor(AnActionEvent initEvent);
@Nullable
SearchEverywhereContributorFilter<T> createFilter();
}
@@ -0,0 +1,20 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.actions.searcheverywhere;
import javax.swing.*;
import java.util.List;
public interface SearchEverywhereContributorFilter<T> {
List<T> getAllElements();
List<T> getSelectedElements();
boolean isSelected(T element);
void setSelected(T element, boolean selected);
String getElementText(T element);
Icon getElementIcon(T element);
}
@@ -130,11 +130,11 @@ public class GotoFileAction extends GotoActionBase implements DumbAware {
* <li>File type with greater name is greater (case is ignored).</li>
* </ol>
*/
static class FileTypeComparator implements Comparator<FileType> {
public static class FileTypeComparator implements Comparator<FileType> {
/**
* an instance of comparator
*/
static final Comparator<FileType> INSTANCE = new FileTypeComparator();
public static final Comparator<FileType> INSTANCE = new FileTypeComparator();
/**
* {@inheritDoc}
@@ -13,6 +13,7 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
@@ -119,8 +120,9 @@ public class ActionSearchEverywhereContributor implements SearchEverywhereContri
return !inplaceChange;
}
public static class Factory implements SearchEverywhereContributorFactory {
public static class Factory implements SearchEverywhereContributorFactory<Void> {
@NotNull
@Override
public SearchEverywhereContributor createContributor(AnActionEvent initEvent) {
return new ActionSearchEverywhereContributor(
@@ -128,5 +130,11 @@ public class ActionSearchEverywhereContributor implements SearchEverywhereContri
initEvent.getData(PlatformDataKeys.CONTEXT_COMPONENT),
initEvent.getData(CommonDataKeys.EDITOR));
}
@Nullable
@Override
public SearchEverywhereContributorFilter<Void> createFilter() {
return null;
}
}
}
@@ -5,12 +5,20 @@ import com.intellij.ide.IdeBundle;
import com.intellij.ide.util.NavigationItemListCellRenderer;
import com.intellij.ide.util.gotoByName.ChooseByNameModel;
import com.intellij.ide.util.gotoByName.GotoClassModel2;
import com.intellij.lang.DependentLanguage;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageUtil;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.fileTypes.LanguageFileType;
import com.intellij.openapi.project.Project;
import com.intellij.ui.IdeUICustomization;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* @author Konstantin Bulenkov
@@ -47,10 +55,28 @@ public class ClassSearchEverywhereContributor extends AbstractGotoSEContributor
return new NavigationItemListCellRenderer();
}
public static class Factory implements SearchEverywhereContributorFactory {
public static class Factory implements SearchEverywhereContributorFactory<Language> {
public static final Function<Language, String> LANGUAGE_NAME_EXTRACTOR = Language::getDisplayName;
public static final Function<Language, Icon> LANGUAGE_ICON_EXTRACTOR = language -> {
final LanguageFileType fileType = language.getAssociatedFileType();
return fileType != null ? fileType.getIcon() : null;
};
@NotNull
@Override
public SearchEverywhereContributor createContributor(AnActionEvent initEvent) {
return new ClassSearchEverywhereContributor(initEvent.getProject());
}
@Nullable
@Override
public SearchEverywhereContributorFilter<Language> createFilter() {
List<Language> items = Language.getRegisteredLanguages()
.stream()
.filter(lang -> lang != Language.ANY && !(lang instanceof DependentLanguage))
.sorted(LanguageUtil.LANGUAGE_COMPARATOR)
.collect(Collectors.toList());
return new SearchEverywhereContributorFilterImpl<>(items, LANGUAGE_NAME_EXTRACTOR, LANGUAGE_ICON_EXTRACTOR);
}
}
}
@@ -3,17 +3,24 @@ package com.intellij.ide.actions.searcheverywhere;
import com.intellij.codeInsight.navigation.NavigationUtil;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.actions.GotoFileAction;
import com.intellij.ide.util.gotoByName.ChooseByNameModel;
import com.intellij.ide.util.gotoByName.GotoFileModel;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.ui.IdeUICustomization;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.InputEvent;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author Konstantin Bulenkov
@@ -79,10 +86,20 @@ public class FileSearchEverywhereContributor extends AbstractGotoSEContributor {
return true;
}
public static class Factory implements SearchEverywhereContributorFactory {
public static class Factory implements SearchEverywhereContributorFactory<FileType> {
@NotNull
@Override
public SearchEverywhereContributor createContributor(AnActionEvent initEvent) {
return new FileSearchEverywhereContributor(initEvent.getProject());
}
@Nullable
@Override
public SearchEverywhereContributorFilter<FileType> createFilter() {
List<FileType> items = Stream.of(FileTypeManager.getInstance().getRegisteredFileTypes())
.sorted(GotoFileAction.FileTypeComparator.INSTANCE)
.collect(Collectors.toList());
return new SearchEverywhereContributorFilterImpl<>(items, FileType::getName, FileType::getIcon);
}
}
}
@@ -0,0 +1,62 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.actions.searcheverywhere;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
public class SearchEverywhereContributorFilterImpl<T> implements SearchEverywhereContributorFilter<T> {
private final Map<T, Boolean> myElementsMap = new LinkedHashMap<>();
private final Function<? super T, String> myTextExtractor;
private final Function<? super T, ? extends Icon> myIconExtractor;
public SearchEverywhereContributorFilterImpl(@NotNull List<T> elements,
@NotNull Function<? super T, String> textExtractor,
@NotNull Function<? super T, ? extends Icon> iconExtractor) {
myTextExtractor = textExtractor;
myIconExtractor = iconExtractor;
elements.forEach(e -> myElementsMap.put(e, true));
}
@Override
public List<T> getAllElements() {
return new ArrayList<>(myElementsMap.keySet());
}
@Override
public List<T> getSelectedElements() {
return myElementsMap.entrySet().stream()
.filter(Map.Entry::getValue)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
@Override
public boolean isSelected(T element) {
return myElementsMap.get(element);
}
@Override
public void setSelected(T element, boolean selected) {
if (myElementsMap.containsKey(element)) {
myElementsMap.put(element, selected);
}
}
@Override
public String getElementText(T element) {
return myTextExtractor.apply(element);
}
@Override
public Icon getElementIcon(T element) {
return myIconExtractor.apply(element);
}
}
@@ -20,9 +20,7 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.*;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -32,8 +30,8 @@ import static com.intellij.ide.actions.SearchEverywhereAction.SEARCH_EVERYWHERE_
public class SearchEverywhereManagerImpl implements SearchEverywhereManager {
private final Project myProject;
private final List<SearchEverywhereContributorFactory> myContributorFactories = SearchEverywhereContributor.getProviders();
private final List<SearchEverywhereContributorFactory<?>> myContributorFactories = SearchEverywhereContributor.getProviders();
private final Map<String, SearchEverywhereContributorFilter<?>> myContributorFilters = new HashMap<>();
private JBPopup myBalloon;
private SearchEverywhereUI mySearchEverywhereUI;
@@ -58,12 +56,18 @@ public class SearchEverywhereManagerImpl implements SearchEverywhereManager {
new RecentFilesSEContributor(project)
);
List<SearchEverywhereContributor> contributors = myContributorFactories.stream()
.map(factory -> factory.createContributor(initEvent))
.sorted(Comparator.comparingInt(SearchEverywhereContributor::getSortWeight))
.collect(Collectors.toList());
List<SearchEverywhereContributor> contributors = new ArrayList<>();
myContributorFactories.forEach(factory -> {
SearchEverywhereContributor contributor = factory.createContributor(initEvent);
myContributorFilters.computeIfAbsent(contributor.getSearchProviderId(), s -> factory.createFilter());
contributors.add(contributor);
});
Collections.sort(contributors, Comparator.comparingInt(SearchEverywhereContributor::getSortWeight));
myContributorFilters.computeIfAbsent(SearchEverywhereContributor.ALL_CONTRIBUTORS_GROUP_ID,
s -> new SearchEverywhereContributorFilterImpl<>(contributors, contributor -> contributor.getGroupName(), contributor -> null)
);
mySearchEverywhereUI = createView(myProject, serviceContributors, contributors);
mySearchEverywhereUI = createView(myProject, serviceContributors, contributors, myContributorFilters);
mySearchEverywhereUI.switchToContributor(selectedContributorID);
if (searchText != null && !searchText.isEmpty()) {
mySearchEverywhereUI.getSearchField().setText(searchText);
@@ -149,8 +153,9 @@ public class SearchEverywhereManagerImpl implements SearchEverywhereManager {
private SearchEverywhereUI createView(Project project,
List<SearchEverywhereContributor> serviceContributors,
List<SearchEverywhereContributor> allContributors) {
SearchEverywhereUI view = new SearchEverywhereUI(project, serviceContributors, allContributors);
List<SearchEverywhereContributor> allContributors,
Map<String, SearchEverywhereContributorFilter<?>> contributorFilters) {
SearchEverywhereUI view = new SearchEverywhereUI(project, serviceContributors, allContributors, contributorFilters);
view.addPropertyChangeListener("preferredSize", evt -> {
if (isShown()) {
myBalloon.pack(true, true);
@@ -2,13 +2,14 @@
package com.intellij.ide.actions.searcheverywhere;
import com.google.common.collect.Lists;
import com.intellij.execution.runners.ExecutionUtil;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.icons.AllIcons;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.util.ElementsChooser;
import com.intellij.ide.util.gotoByName.QuickSearchComponent;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.impl.ActionButton;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.keymap.KeymapUtil;
@@ -17,11 +18,16 @@ import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.JBPopupListener;
import com.intellij.openapi.ui.popup.LightweightWindowEvent;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.MinusculeMatcher;
@@ -63,6 +69,7 @@ public class SearchEverywhereUI extends BorderLayoutPanel implements Disposable,
public static final int MULTIPLE_CONTRIBUTORS_ELEMENTS_LIMIT = 15;
private final List<SearchEverywhereContributor> allContributors;
private final Map<String, SearchEverywhereContributorFilter<?>> myContributorFilters;
private final Project myProject;
private SETab mySelectedTab;
@@ -84,8 +91,10 @@ public class SearchEverywhereUI extends BorderLayoutPanel implements Disposable,
private Runnable searchFinishedHandler = () -> {};
public SearchEverywhereUI(Project project, List<SearchEverywhereContributor> serviceContributors,
List<SearchEverywhereContributor> contributors) {
public SearchEverywhereUI(Project project,
List<SearchEverywhereContributor> serviceContributors,
List<SearchEverywhereContributor> contributors,
Map<String, SearchEverywhereContributorFilter<?>> filters) {
withMinimumWidth(670);
withPreferredWidth(670);
withBackground(JBUI.CurrentTheme.SearchEverywhere.dialogBackground());
@@ -94,6 +103,7 @@ public class SearchEverywhereUI extends BorderLayoutPanel implements Disposable,
allContributors = new ArrayList<>();
allContributors.addAll(serviceContributors);
allContributors.addAll(contributors);
myContributorFilters = filters;
myNonProjectCB = new JBCheckBox();
myNonProjectCB.setOpaque(false);
@@ -323,19 +333,14 @@ public class SearchEverywhereUI extends BorderLayoutPanel implements Disposable,
res.add(myNonProjectCB);
res.add(Box.createHorizontalStrut(JBUI.scale(19)));
AnAction pinAction = new ShowInFindToolWindowAction();
ActionButton pinButton = new ActionButton(pinAction, pinAction.getTemplatePresentation(), ActionPlaces.UNKNOWN, ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE);
res.add(pinButton);
res.add(Box.createHorizontalStrut(JBUI.scale(10)));
AnAction emptyAction = new AnAction(AllIcons.General.Filter) {
@Override
public void actionPerformed(AnActionEvent e) {}
};
ActionButton filterButton = new ActionButton(emptyAction, emptyAction.getTemplatePresentation(), ActionPlaces.UNKNOWN, ActionToolbar.DEFAULT_MINIMUM_BUTTON_SIZE);
res.add(filterButton);
res.add(Box.createHorizontalStrut(JBUI.scale(10)));
DefaultActionGroup actionGroup = new DefaultActionGroup();
actionGroup.addAction(new ShowInFindToolWindowAction());
actionGroup.addAction(new ShowFilterAction());
ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar("search.everywhere.toolbar", actionGroup, true);
JComponent toolbarComponent = toolbar.getComponent();
toolbarComponent.setOpaque(false);
res.add(toolbarComponent);
return res;
}
@@ -979,6 +984,127 @@ public class SearchEverywhereUI extends BorderLayoutPanel implements Disposable,
}
}
private class ShowFilterAction extends ToggleAction implements DumbAware {
private JBPopup myFilterPopup;
public ShowFilterAction() {
super("Filter", "Filter files by type", AllIcons.General.Filter);
}
@Override
public boolean isSelected(final AnActionEvent e) {
return myFilterPopup != null && !myFilterPopup.isDisposed();
}
@Override
public void setSelected(final AnActionEvent e, final boolean state) {
if (state) {
showPopup(e.getInputEvent().getComponent());
}
else {
if (myFilterPopup != null && !myFilterPopup.isDisposed()) {
myFilterPopup.cancel();
}
}
}
@Override
public void update(@NotNull AnActionEvent e) {
Icon icon = getTemplatePresentation().getIcon();
e.getPresentation().setIcon(isActive() ? ExecutionUtil.getLiveIndicator(icon) : icon);
e.getPresentation().setEnabled(myContributorFilters.get(getSelectedContributorID()) != null);
e.getPresentation().putClientProperty(SELECTED_PROPERTY, isSelected(e));
}
private boolean isActive() {
String contributorID = getSelectedContributorID();
SearchEverywhereContributorFilter<?> filter = myContributorFilters.get(contributorID);
if (filter == null) {
return false;
}
return filter.getAllElements().size() != filter.getSelectedElements().size();
}
private void showPopup(Component anchor) {
if (myFilterPopup != null) {
return;
}
JBPopupListener popupCloseListener = new JBPopupListener() {
@Override
public void onClosed(LightweightWindowEvent event) {
myFilterPopup = null;
}
};
myFilterPopup = JBPopupFactory.getInstance()
.createComponentPopupBuilder(createFilterPanel(), null)
.setModalContext(false)
.setFocusable(false)
.setResizable(true)
.setCancelOnClickOutside(false)
.setMinSize(new Dimension(200, 200))
.setDimensionServiceKey(myProject, "GotoFile_FileTypePopup", false)
.addListener(popupCloseListener)
.createPopup();
Disposer.register(SearchEverywhereUI.this, myFilterPopup);
myFilterPopup.showUnderneathOf(anchor);
}
private JComponent createFilterPanel() {
SearchEverywhereContributorFilter<?> filter = myContributorFilters.get(getSelectedContributorID());
ElementsChooser<?> chooser = filterToChooser(filter);
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(chooser);
JPanel buttons = new JPanel();
JButton all = new JButton("All");
all.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
chooser.setAllElementsMarked(true);
}
});
buttons.add(all);
JButton none = new JButton("None");
none.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
chooser.setAllElementsMarked(false);
}
});
buttons.add(none);
JButton invert = new JButton("Invert");
invert.addActionListener(new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
chooser.invertSelection();
}
});
buttons.add(invert);
panel.add(buttons);
return panel;
}
private <T> ElementsChooser<T> filterToChooser(SearchEverywhereContributorFilter<T> filter) {
ElementsChooser<T> res = new ElementsChooser<T>(filter.getAllElements(), false) {
@Override
protected String getItemText(@NotNull T value) {
return filter.getElementText(value);
}
@Nullable
@Override
protected Icon getItemIcon(@NotNull T value) {
return filter.getElementIcon(value);
}
};
res.markElements(filter.getSelectedElements());
ElementsChooser.ElementsMarkListener<T> listener = (element, isMarked) -> filter.setSelected(element, isMarked);
res.addElementsMarkListener(listener);
return res;
}
}
private static JLabel groupInfoLabel(String text) {
JLabel label = new JLabel(text);
label.setForeground(UIUtil.getLabelDisabledForeground());
@@ -5,12 +5,18 @@ import com.intellij.ide.IdeBundle;
import com.intellij.ide.util.NavigationItemListCellRenderer;
import com.intellij.ide.util.gotoByName.ChooseByNameModel;
import com.intellij.ide.util.gotoByName.GotoSymbolModel2;
import com.intellij.lang.DependentLanguage;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageUtil;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
import com.intellij.ui.IdeUICustomization;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Konstantin Bulenkov
@@ -47,11 +53,26 @@ public class SymbolSearchEverywhereContributor extends AbstractGotoSEContributor
return new NavigationItemListCellRenderer();
}
public static class Factory implements SearchEverywhereContributorFactory {
public static class Factory implements SearchEverywhereContributorFactory<Language> {
@NotNull
@Override
public SearchEverywhereContributor createContributor(AnActionEvent initEvent) {
return new SymbolSearchEverywhereContributor(initEvent.getProject());
}
@Nullable
@Override
public SearchEverywhereContributorFilter<Language> createFilter() {
List<Language> items = Language.getRegisteredLanguages()
.stream()
.filter(lang -> lang != Language.ANY && !(lang instanceof DependentLanguage))
.sorted(LanguageUtil.LANGUAGE_COMPARATOR)
.collect(Collectors.toList());
return new SearchEverywhereContributorFilterImpl<>(items,
ClassSearchEverywhereContributor.Factory.LANGUAGE_NAME_EXTRACTOR,
ClassSearchEverywhereContributor.Factory.LANGUAGE_ICON_EXTRACTOR
);
}
}
}