mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
removing duplicated SearchEverywhereUIMixedResults
GitOrigin-RevId: 413ac34011ca7e2bb87b8b37517def29599125a0
This commit is contained in:
committed by
intellij-monorepo-bot
parent
021d9aa60f
commit
dfff0327aa
@@ -1,7 +1,7 @@
|
||||
// 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.testGuiFramework.tests.community.focus
|
||||
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereUIBase
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereUI
|
||||
import com.intellij.testGuiFramework.framework.Timeouts.defaultTimeout
|
||||
import com.intellij.testGuiFramework.framework.GuiTestUtil.textfield
|
||||
import com.intellij.testGuiFramework.impl.GuiRobotHolder
|
||||
@@ -58,7 +58,7 @@ object FocusIssuesUtil {
|
||||
}
|
||||
|
||||
private fun findSearchEverywhereUI(): Container {
|
||||
return GuiRobotHolder.robot.finder().find { it is SearchEverywhereUIBase } as SearchEverywhereUIBase
|
||||
return GuiRobotHolder.robot.finder().find { it is SearchEverywhereUI } as SearchEverywhereUI
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
package com.intellij.java.navigation
|
||||
|
||||
import com.intellij.ide.actions.searcheverywhere.*
|
||||
import com.intellij.ide.actions.searcheverywhere.mixed.SearchEverywhereUIMixedResults
|
||||
import com.intellij.ide.util.gotoByName.GotoActionTest
|
||||
import com.intellij.openapi.actionSystem.AbbreviationManager
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
@@ -25,7 +24,7 @@ import static com.intellij.testFramework.PlatformTestUtil.waitForFuture
|
||||
class SearchEverywhereTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
static final int SEARCH_TIMEOUT = 50_000
|
||||
|
||||
SearchEverywhereUIBase mySearchUI
|
||||
SearchEverywhereUI mySearchUI
|
||||
|
||||
private SEParam mixingResultsFlag
|
||||
private SEParam twoTabsFlag
|
||||
@@ -241,18 +240,16 @@ class SearchEverywhereTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
assert waitForFuture(future, SEARCH_TIMEOUT) == [file4, file3, file5, file2, file1, file6]
|
||||
}
|
||||
|
||||
private SearchEverywhereUIBase createTestUI(List<SearchEverywhereContributor<Object>> contributors) {
|
||||
private SearchEverywhereUI createTestUI(List<SearchEverywhereContributor<Object>> contributors) {
|
||||
def map = new HashMap<SearchEverywhereContributor<?>, SearchEverywhereTabDescriptor>()
|
||||
contributors.forEach({map.put(it, null)})
|
||||
return createTestUI(map)
|
||||
}
|
||||
|
||||
private SearchEverywhereUIBase createTestUI(Map<SearchEverywhereContributor<?>, SearchEverywhereTabDescriptor> contributorsMap) {
|
||||
private SearchEverywhereUI createTestUI(Map<SearchEverywhereContributor<?>, SearchEverywhereTabDescriptor> contributorsMap) {
|
||||
if (mySearchUI != null) Disposer.dispose(mySearchUI)
|
||||
|
||||
def mixingEnabled = Experiments.getInstance().isFeatureEnabled("search.everywhere.mixed.results")
|
||||
mySearchUI = mixingEnabled ? new SearchEverywhereUIMixedResults(project, contributorsMap)
|
||||
: new SearchEverywhereUI(project, contributorsMap)
|
||||
mySearchUI = new SearchEverywhereUI(project, contributorsMap)
|
||||
def tab = contributorsMap.size() > 1
|
||||
? SearchEverywhereManagerImpl.ALL_CONTRIBUTORS_GROUP_ID
|
||||
: contributorsMap.keySet().find().getSearchProviderId()
|
||||
|
||||
@@ -220,7 +220,7 @@ public abstract class AbstractGotoSEContributor implements WeightedSearchEverywh
|
||||
}
|
||||
});
|
||||
}
|
||||
result.add(new SearchEverywhereUIBase.FiltersAction(filter, onChanged));
|
||||
result.add(new SearchEverywhereUI.FiltersAction(filter, onChanged));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright 2000-2020 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 com.intellij.ide.actions.SearchEverywhereClassifier;
|
||||
import com.intellij.ide.util.gotoByName.GotoActionModel;
|
||||
import com.intellij.ui.AppUIUtil;
|
||||
import com.intellij.ui.CellRendererPanel;
|
||||
import com.intellij.ui.SeparatorComponent;
|
||||
import com.intellij.ui.SimpleColoredComponent;
|
||||
import com.intellij.ui.components.JBList;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import java.awt.*;
|
||||
|
||||
class GroupedListFactory extends SEResultsListFactory {
|
||||
@Override
|
||||
public SearchListModel createModel() {
|
||||
return new GroupedSearchListModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JBList<Object> createList(SearchListModel model) {
|
||||
return new JBList<>(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
ListCellRenderer<Object> createListRenderer(SearchListModel model, SearchEverywhereHeader header) {
|
||||
GroupedSearchListModel groupedModel = (GroupedSearchListModel)model;
|
||||
return new ListCellRenderer<>() {
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
if (value == SearchListModel.MORE_ELEMENT) {
|
||||
Component component = myMoreRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
component.setPreferredSize(UIUtil.updateListRowHeight(component.getPreferredSize()));
|
||||
return component;
|
||||
}
|
||||
|
||||
SearchEverywhereContributor<Object> contributor = groupedModel.getContributorForIndex(index);
|
||||
Component component = SearchEverywhereClassifier.EP_Manager.getListCellRendererComponent(
|
||||
list, value, index, isSelected, cellHasFocus);
|
||||
if (component == null) {
|
||||
//noinspection ConstantConditions
|
||||
component = contributor.getElementsRenderer().getListCellRendererComponent(
|
||||
list, value, index, isSelected, true);
|
||||
}
|
||||
|
||||
if (component instanceof JComponent) {
|
||||
Border border = ((JComponent)component).getBorder();
|
||||
if (border != GotoActionModel.GotoActionListCellRenderer.TOGGLE_BUTTON_BORDER) {
|
||||
((JComponent)component).setBorder(JBUI.Borders.empty(1, 2));
|
||||
}
|
||||
}
|
||||
AppUIUtil.targetToDevice(component, list);
|
||||
component.setPreferredSize(UIUtil.updateListRowHeight(component.getPreferredSize()));
|
||||
if (!header.getSelectedTab().isSingleContributor() && groupedModel.isGroupFirstItem(index)) {
|
||||
//noinspection ConstantConditions
|
||||
component = myGroupTitleRenderer.withDisplayedData(contributor.getFullGroupName(), component);
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final GroupTitleRenderer myGroupTitleRenderer = new GroupTitleRenderer();
|
||||
|
||||
private static class GroupTitleRenderer extends CellRendererPanel {
|
||||
|
||||
final SimpleColoredComponent titleLabel = new SimpleColoredComponent();
|
||||
|
||||
GroupTitleRenderer() {
|
||||
setLayout(new BorderLayout());
|
||||
SeparatorComponent separatorComponent = new SeparatorComponent(
|
||||
titleLabel.getPreferredSize().height / 2, JBUI.CurrentTheme.BigPopup.listSeparatorColor(), null);
|
||||
|
||||
JPanel topPanel = JBUI.Panels.simplePanel(5, 0)
|
||||
.addToCenter(separatorComponent)
|
||||
.addToLeft(titleLabel)
|
||||
.withBorder(JBUI.Borders.empty(1, 7))
|
||||
.withBackground(UIUtil.getListBackground());
|
||||
add(topPanel, BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
public GroupTitleRenderer withDisplayedData(@Nls String title, Component itemContent) {
|
||||
titleLabel.clear();
|
||||
titleLabel.append(title, SMALL_LABEL_ATTRS);
|
||||
Component prevContent = ((BorderLayout)getLayout()).getLayoutComponent(BorderLayout.CENTER);
|
||||
if (prevContent != null) {
|
||||
remove(prevContent);
|
||||
}
|
||||
add(itemContent, BorderLayout.CENTER);
|
||||
accessibleContext = itemContent.getAccessibleContext();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,9 +25,9 @@ import static com.intellij.ide.actions.searcheverywhere.SEResultsEqualityProvide
|
||||
/**
|
||||
* @author msokolov
|
||||
*/
|
||||
class MultiThreadSearcher implements SESearcher {
|
||||
class GroupedResultsSearcher implements SESearcher {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(MultiThreadSearcher.class);
|
||||
private static final Logger LOG = Logger.getInstance(GroupedResultsSearcher.class);
|
||||
|
||||
@NotNull private final Listener myListener;
|
||||
@NotNull private final Executor myNotificationExecutor;
|
||||
@@ -40,9 +40,9 @@ class MultiThreadSearcher implements SESearcher {
|
||||
* @param notificationExecutor searcher guarantees that all listener methods will be called only through this executor
|
||||
* @param equalityProviders collection of equality providers that checks if found elements are already in the search results
|
||||
*/
|
||||
MultiThreadSearcher(@NotNull Listener listener,
|
||||
@NotNull Executor notificationExecutor,
|
||||
@NotNull Collection<? extends SEResultsEqualityProvider> equalityProviders) {
|
||||
GroupedResultsSearcher(@NotNull Listener listener,
|
||||
@NotNull Executor notificationExecutor,
|
||||
@NotNull Collection<? extends SEResultsEqualityProvider> equalityProviders) {
|
||||
myListener = listener;
|
||||
myNotificationExecutor = notificationExecutor;
|
||||
myEqualityProvider = SEResultsEqualityProvider.composite(equalityProviders);
|
||||
@@ -98,9 +98,16 @@ class MultiThreadSearcher implements SESearcher {
|
||||
|
||||
@Override
|
||||
public ProgressIndicator findMoreItems(@NotNull Map<? extends SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>> alreadyFound,
|
||||
@NotNull String pattern,
|
||||
@NotNull SearchEverywhereContributor<?> contributor,
|
||||
int newLimit) {
|
||||
@NotNull Map<? extends SearchEverywhereContributor<?>, Integer> contributorsAndLimits,
|
||||
@NotNull String pattern) {
|
||||
if (contributorsAndLimits.size() > 1)
|
||||
throw new IllegalArgumentException("Multiple contributors are not allowed for grouped list");
|
||||
|
||||
Map.Entry<? extends SearchEverywhereContributor<?>, Integer> entry = contributorsAndLimits.entrySet().stream().findFirst()
|
||||
.orElseThrow(() -> new IllegalArgumentException("Empty contributors map is not allowed"));
|
||||
SearchEverywhereContributor<?> contributor = entry.getKey();
|
||||
int newLimit = entry.getValue();
|
||||
|
||||
ProgressIndicator indicator = new ProgressIndicatorBase();
|
||||
ResultsAccumulator accumulator = new ShowMoreResultsAccumulator(alreadyFound, myEqualityProvider, contributor, newLimit,
|
||||
myListener, myNotificationExecutor, indicator);
|
||||
@@ -223,7 +230,7 @@ class MultiThreadSearcher implements SESearcher {
|
||||
|
||||
private static abstract class ResultsAccumulator {
|
||||
protected final Map<SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>> sections;
|
||||
protected final MultiThreadSearcher.Listener myListener;
|
||||
protected final SESearcher.Listener myListener;
|
||||
protected final Executor myNotificationExecutor;
|
||||
protected final SEResultsEqualityProvider myEqualityProvider;
|
||||
protected final ProgressIndicator myProgressIndicator;
|
||||
@@ -0,0 +1,219 @@
|
||||
// Copyright 2000-2020 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 com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.diff.Diff;
|
||||
import com.intellij.util.diff.FilesTooBigForDiffException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
class GroupedSearchListModel extends SearchListModel {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(GroupedSearchListModel.class);
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements(SearchEverywhereContributor contributor) {
|
||||
return listElements.stream()
|
||||
.anyMatch(info -> info.getElement() == MORE_ELEMENT && info.getContributor() == contributor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addElements(List<? extends SearchEverywhereFoundElementInfo> items) {
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<SearchEverywhereContributor<?>, List<SearchEverywhereFoundElementInfo>> itemsMap = new HashMap<>();
|
||||
items.forEach(info -> {
|
||||
List<SearchEverywhereFoundElementInfo> list = itemsMap.computeIfAbsent(info.getContributor(), contributor -> new ArrayList<>());
|
||||
list.add(info);
|
||||
});
|
||||
itemsMap.forEach((contributor, list) -> list.sort(Comparator.comparingInt(SearchEverywhereFoundElementInfo::getPriority).reversed()));
|
||||
|
||||
if (resultsExpired) {
|
||||
retainContributors(itemsMap.keySet());
|
||||
clearMoreItems();
|
||||
|
||||
itemsMap.forEach((contributor, list) -> {
|
||||
Object[] oldItems = ArrayUtil.toObjectArray(getFoundItems(contributor));
|
||||
Object[] newItems = list.stream()
|
||||
.map(SearchEverywhereFoundElementInfo::getElement)
|
||||
.toArray();
|
||||
try {
|
||||
Diff.Change change = Diff.buildChanges(oldItems, newItems);
|
||||
applyChange(change, contributor, list);
|
||||
}
|
||||
catch (FilesTooBigForDiffException e) {
|
||||
LOG.error("Cannot calculate diff for updated search results");
|
||||
}
|
||||
});
|
||||
resultsExpired = false;
|
||||
}
|
||||
else {
|
||||
itemsMap.forEach((contributor, list) -> {
|
||||
int startIndex = contributors().indexOf(contributor);
|
||||
int insertionIndex = getInsertionPoint(contributor);
|
||||
int endIndex = insertionIndex + list.size() - 1;
|
||||
listElements.addAll(insertionIndex, list);
|
||||
fireIntervalAdded(this, insertionIndex, endIndex);
|
||||
|
||||
// there were items for this contributor before update
|
||||
if (startIndex >= 0) {
|
||||
listElements.subList(startIndex, endIndex + 1)
|
||||
.sort(Comparator.comparingInt(SearchEverywhereFoundElementInfo::getPriority).reversed());
|
||||
fireContentsChanged(this, startIndex, endIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void retainContributors(Collection<SearchEverywhereContributor<?>> retainContributors) {
|
||||
Iterator<SearchEverywhereFoundElementInfo> iterator = listElements.iterator();
|
||||
int startInterval = 0;
|
||||
int endInterval = -1;
|
||||
while (iterator.hasNext()) {
|
||||
SearchEverywhereFoundElementInfo item = iterator.next();
|
||||
if (retainContributors.contains(item.getContributor())) {
|
||||
if (startInterval <= endInterval) {
|
||||
fireIntervalRemoved(this, startInterval, endInterval);
|
||||
startInterval = endInterval + 2;
|
||||
}
|
||||
else {
|
||||
startInterval++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
iterator.remove();
|
||||
}
|
||||
endInterval++;
|
||||
}
|
||||
|
||||
if (startInterval <= endInterval) {
|
||||
fireIntervalRemoved(this, startInterval, endInterval);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearMoreItems() {
|
||||
ListIterator<SearchEverywhereFoundElementInfo> iterator = listElements.listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
int index = iterator.nextIndex();
|
||||
if (iterator.next().getElement() == MORE_ELEMENT) {
|
||||
iterator.remove();
|
||||
fireContentsChanged(this, index, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyChange(Diff.Change change,
|
||||
SearchEverywhereContributor<?> contributor,
|
||||
List<SearchEverywhereFoundElementInfo> newItems) {
|
||||
int firstItemIndex = contributors().indexOf(contributor);
|
||||
if (firstItemIndex < 0) {
|
||||
firstItemIndex = getInsertionPoint(contributor);
|
||||
}
|
||||
|
||||
for (Diff.Change ch : toRevertedList(change)) {
|
||||
if (ch.deleted > 0) {
|
||||
for (int i = ch.deleted - 1; i >= 0; i--) {
|
||||
int index = firstItemIndex + ch.line0 + i;
|
||||
listElements.remove(index);
|
||||
}
|
||||
fireIntervalRemoved(this, firstItemIndex + ch.line0, firstItemIndex + ch.line0 + ch.deleted - 1);
|
||||
}
|
||||
|
||||
if (ch.inserted > 0) {
|
||||
List<SearchEverywhereFoundElementInfo> addedItems = newItems.subList(ch.line1, ch.line1 + ch.inserted);
|
||||
listElements.addAll(firstItemIndex + ch.line0, addedItems);
|
||||
fireIntervalAdded(this, firstItemIndex + ch.line0, firstItemIndex + ch.line0 + ch.inserted - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Diff.Change> toRevertedList(Diff.Change change) {
|
||||
List<Diff.Change> res = new ArrayList<>();
|
||||
while (change != null) {
|
||||
res.add(0, change);
|
||||
change = change.link;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeElement(@NotNull Object item, SearchEverywhereContributor<?> contributor) {
|
||||
int index = contributors().indexOf(contributor);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (index < getSize() && listElements.get(index).getContributor() == contributor) {
|
||||
if (item.equals(getElementAt(index))) {
|
||||
listElements.remove(index);
|
||||
fireIntervalRemoved(this, index, index);
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHasMore(SearchEverywhereContributor<?> contributor, boolean newVal) {
|
||||
int index = contributors().lastIndexOf(contributor);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean alreadyHas = isMoreElement(index);
|
||||
if (alreadyHas && !newVal) {
|
||||
listElements.remove(index);
|
||||
fireIntervalRemoved(this, index, index);
|
||||
}
|
||||
|
||||
if (!alreadyHas && newVal) {
|
||||
index += 1;
|
||||
listElements.add(index, new SearchEverywhereFoundElementInfo(MORE_ELEMENT, 0, contributor));
|
||||
fireIntervalAdded(this, index, index);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isGroupFirstItem(int index) {
|
||||
return index == 0 || listElements.get(index).getContributor() != listElements.get(index - 1).getContributor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexToScroll(int currentIndex, boolean scrollDown) {
|
||||
int index = currentIndex;
|
||||
do {
|
||||
index += scrollDown ? 1 : -1;
|
||||
}
|
||||
while (index >= 0 && index < getSize() && !isGroupFirstItem(index) && !isMoreElement(index));
|
||||
|
||||
return Integer.max(Integer.min(index, getSize() - 1), 0);
|
||||
}
|
||||
|
||||
public int getItemsForContributor(SearchEverywhereContributor<?> contributor) {
|
||||
List<SearchEverywhereContributor> contributorsList = contributors();
|
||||
int first = contributorsList.indexOf(contributor);
|
||||
int last = contributorsList.lastIndexOf(contributor);
|
||||
if (isMoreElement(last)) {
|
||||
last -= 1;
|
||||
}
|
||||
return last - first + 1;
|
||||
}
|
||||
|
||||
private int getInsertionPoint(SearchEverywhereContributor contributor) {
|
||||
if (listElements.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
List<SearchEverywhereContributor> list = contributors();
|
||||
int index = list.lastIndexOf(contributor);
|
||||
if (index >= 0) {
|
||||
return isMoreElement(index) ? index : index + 1;
|
||||
}
|
||||
|
||||
index = Collections.binarySearch(list, contributor, Comparator.comparingInt(SearchEverywhereContributor::getSortWeight));
|
||||
return -index - 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2000-2020 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 com.intellij.ide.actions.SearchEverywhereClassifier;
|
||||
import com.intellij.ide.util.gotoByName.GotoActionModel;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.ui.AppUIUtil;
|
||||
import com.intellij.ui.components.JBList;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
class MixedListFactory extends SEResultsListFactory {
|
||||
|
||||
private final List<String> prioritizedContributors = new ArrayList<>();
|
||||
|
||||
MixedListFactory() {
|
||||
prioritizedContributors.add("CommandsContributor");
|
||||
prioritizedContributors.add(TopHitSEContributor.class.getSimpleName());
|
||||
if (Registry.is("search.everywhere.recent.at.top")) {
|
||||
prioritizedContributors.add(RecentFilesSEContributor.class.getSimpleName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchListModel createModel() {
|
||||
MixedSearchListModel mixedModel = new MixedSearchListModel();
|
||||
|
||||
Map<String, Integer> priorities = new HashMap<>();
|
||||
for (int i = 0; i < prioritizedContributors.size(); i++) {
|
||||
priorities.put(prioritizedContributors.get(i), prioritizedContributors.size() - i);
|
||||
}
|
||||
|
||||
Comparator<SearchEverywhereFoundElementInfo> prioritizedContributorsComparator = (element1, element2) -> {
|
||||
int firstElementPriority = priorities.getOrDefault(element1.getContributor().getSearchProviderId(), 0);
|
||||
int secondElementPriority = priorities.getOrDefault(element2.getContributor().getSearchProviderId(), 0);
|
||||
return Integer.compare(firstElementPriority, secondElementPriority);
|
||||
};
|
||||
|
||||
Comparator<SearchEverywhereFoundElementInfo> comparator = prioritizedContributorsComparator
|
||||
.thenComparing(SearchEverywhereFoundElementInfo.COMPARATOR)
|
||||
.reversed();
|
||||
mixedModel.setElementsComparator(comparator);
|
||||
|
||||
return mixedModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JBList<Object> createList(SearchListModel model) {
|
||||
return new JBList<>(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
ListCellRenderer<Object> createListRenderer(SearchListModel model, SearchEverywhereHeader header) {
|
||||
return new ListCellRenderer<>() {
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
if (value == SearchListModel.MORE_ELEMENT) {
|
||||
Component component = myMoreRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
component.setPreferredSize(UIUtil.updateListRowHeight(component.getPreferredSize()));
|
||||
return component;
|
||||
}
|
||||
|
||||
SearchEverywhereContributor<Object> contributor = model.getContributorForIndex(index);
|
||||
Component component = SearchEverywhereClassifier.EP_Manager.getListCellRendererComponent(
|
||||
list, value, index, isSelected, cellHasFocus);
|
||||
if (component == null) {
|
||||
//noinspection ConstantConditions
|
||||
component = contributor.getElementsRenderer().getListCellRendererComponent(
|
||||
list, value, index, isSelected, true);
|
||||
}
|
||||
|
||||
if (component instanceof JComponent) {
|
||||
Border border = ((JComponent)component).getBorder();
|
||||
if (border != GotoActionModel.GotoActionListCellRenderer.TOGGLE_BUTTON_BORDER) {
|
||||
((JComponent)component).setBorder(JBUI.Borders.empty(1, 2));
|
||||
}
|
||||
}
|
||||
AppUIUtil.targetToDevice(component, list);
|
||||
component.setPreferredSize(UIUtil.updateListRowHeight(component.getPreferredSize()));
|
||||
|
||||
return component;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
// Copyright 2000-2020 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.mixed;
|
||||
package com.intellij.ide.actions.searcheverywhere;
|
||||
|
||||
import com.intellij.concurrency.SensitiveProgressWrapper;
|
||||
import com.intellij.ide.actions.searcheverywhere.SEResultsEqualityProvider;
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor;
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereFoundElementInfo;
|
||||
import com.intellij.ide.actions.searcheverywhere.WeightedSearchEverywhereContributor;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
@@ -29,9 +25,9 @@ import static com.intellij.ide.actions.searcheverywhere.SEResultsEqualityProvide
|
||||
/**
|
||||
* @author msokolov
|
||||
*/
|
||||
class MultiThreadSearcher implements SESearcher {
|
||||
class MixedResultsSearcher implements SESearcher {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(MultiThreadSearcher.class);
|
||||
private static final Logger LOG = Logger.getInstance(MixedResultsSearcher.class);
|
||||
|
||||
@NotNull private final Listener myListener;
|
||||
@NotNull private final Executor myNotificationExecutor;
|
||||
@@ -44,9 +40,9 @@ class MultiThreadSearcher implements SESearcher {
|
||||
* @param notificationExecutor searcher guarantees that all listener methods will be called only through this executor
|
||||
* @param equalityProviders collection of equality providers that checks if found elements are already in the search results
|
||||
*/
|
||||
MultiThreadSearcher(@NotNull Listener listener,
|
||||
@NotNull Executor notificationExecutor,
|
||||
@NotNull Collection<? extends SEResultsEqualityProvider> equalityProviders) {
|
||||
MixedResultsSearcher(@NotNull Listener listener,
|
||||
@NotNull Executor notificationExecutor,
|
||||
@NotNull Collection<? extends SEResultsEqualityProvider> equalityProviders) {
|
||||
myListener = listener;
|
||||
myNotificationExecutor = notificationExecutor;
|
||||
myEqualityProvider = SEResultsEqualityProvider.composite(equalityProviders);
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright 2000-2020 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 com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.diff.Diff;
|
||||
import com.intellij.util.diff.FilesTooBigForDiffException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
class MixedSearchListModel extends SearchListModel {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(MixedSearchListModel.class);
|
||||
|
||||
private final Map<SearchEverywhereContributor<?>, Boolean> hasMoreContributors = new HashMap<>();
|
||||
|
||||
private Comparator<SearchEverywhereFoundElementInfo> myElementsComparator = SearchEverywhereFoundElementInfo.COMPARATOR.reversed();
|
||||
|
||||
// new elements cannot be added before this index when "more..." elements are loaded
|
||||
private int myMaxFrozenIndex;
|
||||
|
||||
public void setElementsComparator(Comparator<SearchEverywhereFoundElementInfo> elementsComparator) {
|
||||
myElementsComparator = elementsComparator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements(SearchEverywhereContributor<?> contributor) {
|
||||
return Boolean.TRUE.equals(hasMoreContributors.get(contributor));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexToScroll(int currentIndex, boolean scrollDown) {
|
||||
return scrollDown ? getSize() - 1 : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addElements(List<? extends SearchEverywhereFoundElementInfo> items) {
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
items = items.stream()
|
||||
.sorted(myElementsComparator)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (resultsExpired) {
|
||||
clearMoreItems();
|
||||
|
||||
Object[] oldItems = ArrayUtil.toObjectArray(getItems());
|
||||
Object[] newItems = items.stream()
|
||||
.map(SearchEverywhereFoundElementInfo::getElement)
|
||||
.toArray();
|
||||
try {
|
||||
Diff.Change change = Diff.buildChanges(oldItems, newItems);
|
||||
applyChange(change, items);
|
||||
}
|
||||
catch (FilesTooBigForDiffException e) {
|
||||
LOG.error("Cannot calculate diff for updated search results");
|
||||
}
|
||||
|
||||
resultsExpired = false;
|
||||
}
|
||||
else {
|
||||
int startIndex = listElements.size();
|
||||
listElements.addAll(items);
|
||||
int endIndex = listElements.size() - 1;
|
||||
fireIntervalAdded(this, startIndex, endIndex);
|
||||
|
||||
// there were items for this contributor before update
|
||||
if (startIndex > 0) {
|
||||
List<SearchEverywhereFoundElementInfo> lst = myMaxFrozenIndex >= 0
|
||||
? listElements.subList(myMaxFrozenIndex + 1, listElements.size())
|
||||
: listElements;
|
||||
lst.sort(myElementsComparator);
|
||||
fireContentsChanged(this, 0, endIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearMoreItems() {
|
||||
if (listElements.isEmpty()) return;
|
||||
|
||||
int lastItemIndex = listElements.size() - 1;
|
||||
SearchEverywhereFoundElementInfo lastItem = listElements.get(lastItemIndex);
|
||||
if (lastItem.getElement() == MORE_ELEMENT) {
|
||||
listElements.remove(lastItemIndex);
|
||||
fireIntervalRemoved(this, lastItemIndex, lastItemIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyChange(Diff.Change change, List<? extends SearchEverywhereFoundElementInfo> newItems) {
|
||||
for (Diff.Change ch : toRevertedList(change)) {
|
||||
if (ch.deleted > 0) {
|
||||
listElements.subList(ch.line0, ch.line0 + ch.deleted).clear();
|
||||
fireIntervalRemoved(this, ch.line0, ch.line0 + ch.deleted - 1);
|
||||
}
|
||||
|
||||
if (ch.inserted > 0) {
|
||||
List<? extends SearchEverywhereFoundElementInfo> addedItems = newItems.subList(ch.line1, ch.line1 + ch.inserted);
|
||||
listElements.addAll(ch.line0, addedItems);
|
||||
fireIntervalAdded(this, ch.line0, ch.line0 + ch.inserted - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Diff.Change> toRevertedList(Diff.Change change) {
|
||||
List<Diff.Change> res = new ArrayList<>();
|
||||
while (change != null) {
|
||||
res.add(0, change);
|
||||
change = change.link;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeElement(@NotNull Object item, SearchEverywhereContributor<?> contributor) {
|
||||
if (listElements.isEmpty()) return;
|
||||
|
||||
for (int i = 0; i < listElements.size(); i++) {
|
||||
SearchEverywhereFoundElementInfo info = listElements.get(i);
|
||||
if (info.getContributor() == contributor && info.getElement().equals(item)) {
|
||||
listElements.remove(i);
|
||||
fireIntervalRemoved(this, i, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHasMore(SearchEverywhereContributor<?> contributor, boolean contributorHasMore) {
|
||||
hasMoreContributors.put(contributor, contributorHasMore);
|
||||
|
||||
int lasItemIndex = listElements.size() - 1;
|
||||
if (lasItemIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean hasMore = hasMoreContributors.values().stream().anyMatch(val -> val);
|
||||
boolean alreadyHas = isMoreElement(lasItemIndex);
|
||||
if (alreadyHas && !hasMore) {
|
||||
listElements.remove(lasItemIndex);
|
||||
fireIntervalRemoved(this, lasItemIndex, lasItemIndex);
|
||||
}
|
||||
|
||||
if (!alreadyHas && hasMore) {
|
||||
myMaxFrozenIndex = lasItemIndex;
|
||||
listElements.add(new SearchEverywhereFoundElementInfo(MORE_ELEMENT, 0, null));
|
||||
lasItemIndex += 1;
|
||||
fireIntervalAdded(this, lasItemIndex, lasItemIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
hasMoreContributors.clear();
|
||||
myMaxFrozenIndex = -1;
|
||||
super.clear();
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,13 @@ import java.util.stream.IntStream;
|
||||
class SEListSelectionTracker implements ListSelectionListener {
|
||||
|
||||
private final JBList<?> myList;
|
||||
private final SearchEverywhereUI.SearchListModel myListModel;
|
||||
private final SearchListModel myListModel;
|
||||
|
||||
private int myLockCounter;
|
||||
private final List<Object> mySelectedItems = new ArrayList<>();
|
||||
private boolean myMoreSelected;
|
||||
|
||||
SEListSelectionTracker(@NotNull JBList<?> list, @NotNull SearchEverywhereUI.SearchListModel model) {
|
||||
SEListSelectionTracker(@NotNull JBList<?> list, @NotNull SearchListModel model) {
|
||||
myList = list;
|
||||
myListModel = model;
|
||||
}
|
||||
@@ -32,6 +32,7 @@ class SEListSelectionTracker implements ListSelectionListener {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
if (isLocked()) return;
|
||||
if (myList.getSelectedIndices().length == 0) return;
|
||||
|
||||
saveSelection();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2000-2020 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 com.intellij.ide.IdeBundle;
|
||||
import com.intellij.ui.ColoredListCellRenderer;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import com.intellij.ui.components.JBList;
|
||||
import com.intellij.util.ui.JBInsets;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
abstract class SEResultsListFactory {
|
||||
|
||||
abstract SearchListModel createModel();
|
||||
|
||||
abstract JBList<Object> createList(SearchListModel model);
|
||||
|
||||
abstract ListCellRenderer<Object> createListRenderer(SearchListModel model, SearchEverywhereHeader header);
|
||||
|
||||
protected static final SimpleTextAttributes SMALL_LABEL_ATTRS = new SimpleTextAttributes(
|
||||
SimpleTextAttributes.STYLE_SMALLER, JBUI.CurrentTheme.BigPopup.listTitleLabelForeground());
|
||||
|
||||
protected static final ListCellRenderer<Object> myMoreRenderer = new ColoredListCellRenderer<>() {
|
||||
|
||||
@Override
|
||||
protected int getMinHeight() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customizeCellRenderer(@NotNull JList<?> list, Object value, int index, boolean selected, boolean hasFocus) {
|
||||
if (value != SearchListModel.MORE_ELEMENT) {
|
||||
throw new AssertionError(value);
|
||||
}
|
||||
setFont(UIUtil.getLabelFont().deriveFont(UIUtil.getFontSize(UIUtil.FontSize.SMALL)));
|
||||
append(IdeBundle.message("search.everywhere.points.more"), SMALL_LABEL_ATTRS);
|
||||
setIpad(JBInsets.create(1, 7));
|
||||
setMyBorder(null);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -13,9 +13,8 @@ interface SESearcher {
|
||||
@NotNull String pattern);
|
||||
|
||||
ProgressIndicator findMoreItems(@NotNull Map<? extends SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>> alreadyFound,
|
||||
@NotNull String pattern,
|
||||
@NotNull SearchEverywhereContributor<?> contributor,
|
||||
int newLimit);
|
||||
@NotNull Map<? extends SearchEverywhereContributor<?>, Integer> contributorsAndLimits,
|
||||
@NotNull String pattern);
|
||||
|
||||
/**
|
||||
* Search process listener interface
|
||||
|
||||
@@ -55,7 +55,7 @@ public class SearchEverywhereHeader {
|
||||
public SearchEverywhereHeader(@Nullable Project project,
|
||||
Map<SearchEverywhereContributor<?>, SearchEverywhereTabDescriptor> contributors,
|
||||
@NotNull Runnable scopeChangedCallback, Function<String, String> shortcutSupplier,
|
||||
AnAction showInFindToolWindowAction, SearchEverywhereUIBase ui) {
|
||||
AnAction showInFindToolWindowAction, SearchEverywhereUI ui) {
|
||||
myScopeChangedCallback = scopeChangedCallback;
|
||||
myProject = project;
|
||||
myShortcutSupplier = shortcutSupplier;
|
||||
@@ -149,7 +149,7 @@ public class SearchEverywhereHeader {
|
||||
if (myProject != null && !projectContributors.isEmpty()) {
|
||||
List<AnAction> projectActions = Arrays.asList(
|
||||
new MyScopeChooserAction(myProject, projectContributors, myScopeChangedCallback),
|
||||
new SearchEverywhereUIBase.FiltersAction(createContributorsFilter(myProject, projectContributors), myScopeChangedCallback)
|
||||
new SearchEverywhereUI.FiltersAction(createContributorsFilter(myProject, projectContributors), myScopeChangedCallback)
|
||||
);
|
||||
res.add(createTab(SearchEverywhereTabDescriptor.PROJECT.getId(), IdeBundle.message("searcheverywhere.project.search.tab.name"), projectContributors,
|
||||
projectActions));
|
||||
@@ -176,7 +176,7 @@ public class SearchEverywhereHeader {
|
||||
myScopeChangedCallback.run();
|
||||
}
|
||||
},
|
||||
new SearchEverywhereUIBase.FiltersAction(createContributorsFilter(myProject, ideContributors), myScopeChangedCallback)
|
||||
new SearchEverywhereUI.FiltersAction(createContributorsFilter(myProject, ideContributors), myScopeChangedCallback)
|
||||
);
|
||||
res.add(createTab(SearchEverywhereTabDescriptor.IDE.getId(), IdeBundle.message("searcheverywhere.ide.search.tab.name"),
|
||||
ideContributors, ideActions));
|
||||
@@ -211,7 +211,7 @@ public class SearchEverywhereHeader {
|
||||
});
|
||||
onChanged.run();
|
||||
}
|
||||
}, new SearchEverywhereUIBase.FiltersAction(createContributorsFilter(myProject, contributors), onChanged));
|
||||
}, new SearchEverywhereUI.FiltersAction(createContributorsFilter(myProject, contributors), onChanged));
|
||||
SETab allTab = createTab(SearchEverywhereManagerImpl.ALL_CONTRIBUTORS_GROUP_ID,
|
||||
IdeBundle.message("searcheverywhere.allelements.tab.name"),
|
||||
contributors, actions);
|
||||
|
||||
@@ -3,13 +3,11 @@ package com.intellij.ide.actions.searcheverywhere;
|
||||
|
||||
import com.intellij.codeWithMe.ClientId;
|
||||
import com.intellij.ide.actions.BigPopupUI;
|
||||
import com.intellij.ide.actions.searcheverywhere.mixed.SearchEverywhereUIMixedResults;
|
||||
import com.intellij.ide.actions.searcheverywhere.statistics.SearchEverywhereUsageTriggerCollector;
|
||||
import com.intellij.ide.lightEdit.LightEdit;
|
||||
import com.intellij.ide.lightEdit.LightEditCompatible;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.Experiments;
|
||||
import com.intellij.openapi.keymap.KeymapUtil;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
@@ -52,7 +50,7 @@ public final class SearchEverywhereManagerImpl implements SearchEverywhereManage
|
||||
private final Project myProject;
|
||||
|
||||
private JBPopup myBalloon;
|
||||
private SearchEverywhereUIBase mySearchEverywhereUI;
|
||||
private SearchEverywhereUI mySearchEverywhereUI;
|
||||
private Dimension myBalloonFullSize;
|
||||
|
||||
private final SearchHistoryList myHistoryList = new SearchHistoryList();
|
||||
@@ -256,14 +254,12 @@ public final class SearchEverywhereManagerImpl implements SearchEverywhereManage
|
||||
myEverywhere = everywhere;
|
||||
}
|
||||
|
||||
private SearchEverywhereUIBase createView(Project project,
|
||||
private SearchEverywhereUI createView(Project project,
|
||||
Map<SearchEverywhereContributor<?>, SearchEverywhereTabDescriptor> contributors) {
|
||||
if (LightEdit.owns(project)) {
|
||||
contributors = ContainerUtil.filter(contributors, (contributor) -> contributor instanceof LightEditCompatible);
|
||||
}
|
||||
SearchEverywhereUIBase view = Experiments.getInstance().isFeatureEnabled("search.everywhere.mixed.results")
|
||||
? new SearchEverywhereUIMixedResults(project, contributors, myTabsShortcutsMap::get)
|
||||
: new SearchEverywhereUI(project, contributors, myTabsShortcutsMap::get);
|
||||
SearchEverywhereUI view = new SearchEverywhereUI(project, contributors, myTabsShortcutsMap::get);
|
||||
|
||||
view.setSearchFinishedHandler(() -> {
|
||||
if (isShown()) {
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
// Copyright 2000-2020 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 com.google.common.collect.Lists;
|
||||
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.IdeBundle;
|
||||
import com.intellij.ide.SearchTopHitProvider;
|
||||
import com.intellij.ide.actions.SearchEverywhereClassifier;
|
||||
import com.intellij.ide.actions.BigPopupUI;
|
||||
import com.intellij.ide.actions.bigPopup.ShowFilterAction;
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereHeader.SETab;
|
||||
import com.intellij.ide.actions.searcheverywhere.statistics.SearchEverywhereUsageTriggerCollector;
|
||||
import com.intellij.ide.actions.searcheverywhere.statistics.SearchFieldStatisticsCollector;
|
||||
import com.intellij.ide.util.gotoByName.GotoActionModel;
|
||||
import com.intellij.ide.util.ElementsChooser;
|
||||
import com.intellij.ide.util.gotoByName.QuickSearchComponent;
|
||||
import com.intellij.internal.statistic.eventLog.FeatureUsageData;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.actionSystem.impl.ActionMenu;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ApplicationNamesInfo;
|
||||
import com.intellij.openapi.application.Experiments;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.keymap.KeymapUtil;
|
||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
@@ -32,6 +32,7 @@ import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.popup.JBPopup;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.wm.ToolWindowId;
|
||||
import com.intellij.openapi.wm.ToolWindowManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
@@ -48,16 +49,12 @@ import com.intellij.usageView.UsageInfo;
|
||||
import com.intellij.usages.*;
|
||||
import com.intellij.usages.impl.UsageViewManagerImpl;
|
||||
import com.intellij.util.Alarm;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.Consumer;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.diff.Diff;
|
||||
import com.intellij.util.diff.FilesTooBigForDiffException;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import com.intellij.util.text.MatcherHolder;
|
||||
import com.intellij.util.ui.EmptyIcon;
|
||||
import com.intellij.util.ui.JBInsets;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
@@ -66,7 +63,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
@@ -78,6 +74,7 @@ import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.intellij.ide.actions.searcheverywhere.statistics.SearchEverywhereUsageTriggerCollector.getReportableContributorID;
|
||||
|
||||
@@ -85,26 +82,21 @@ import static com.intellij.ide.actions.searcheverywhere.statistics.SearchEverywh
|
||||
* @author Konstantin Bulenkov
|
||||
* @author Mikhail.Sokolov
|
||||
*/
|
||||
public final class SearchEverywhereUI extends SearchEverywhereUIBase implements DataProvider, QuickSearchComponent {
|
||||
private static final Logger LOG = Logger.getInstance(SearchEverywhereUI.class);
|
||||
public final class SearchEverywhereUI extends BigPopupUI implements DataProvider, QuickSearchComponent {
|
||||
|
||||
public static final String SEARCH_EVERYWHERE_SEARCH_FILED_KEY = "search-everywhere-textfield"; //only for testing purposes
|
||||
|
||||
public static final int SINGLE_CONTRIBUTOR_ELEMENTS_LIMIT = 30;
|
||||
public static final int MULTIPLE_CONTRIBUTORS_ELEMENTS_LIMIT = 15;
|
||||
public static final int THROTTLING_TIMEOUT = 100;
|
||||
|
||||
private static final SimpleTextAttributes SMALL_LABEL_ATTRS = new SimpleTextAttributes(
|
||||
SimpleTextAttributes.STYLE_SMALLER, JBUI.CurrentTheme.BigPopup.listTitleLabelForeground());
|
||||
|
||||
private final SEResultsListFactory myListFactory;
|
||||
private SearchListModel myListModel;
|
||||
private final SearchEverywhereHeader myHeader;
|
||||
private JBPopup myHint;
|
||||
|
||||
private String myNotFoundString;
|
||||
|
||||
private final SESearcher mySearcher;
|
||||
private final ThrottlingListenerWrapper myBufferedListener;
|
||||
private ProgressIndicator mySearchProgressIndicator;
|
||||
|
||||
private final SEListSelectionTracker mySelectionTracker;
|
||||
|
||||
public SearchEverywhereUI(@Nullable Project project,
|
||||
@@ -116,10 +108,16 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
Map<SearchEverywhereContributor<?>, SearchEverywhereTabDescriptor> contributors,
|
||||
@NotNull Function<String, String> shortcutSupplier) {
|
||||
super(project);
|
||||
myListFactory = Experiments.getInstance().isFeatureEnabled("search.everywhere.mixed.results")
|
||||
? new MixedListFactory()
|
||||
: new GroupedListFactory();
|
||||
|
||||
List<SEResultsEqualityProvider> equalityProviders = SEResultsEqualityProvider.getProviders();
|
||||
myBufferedListener = new ThrottlingListenerWrapper(THROTTLING_TIMEOUT, mySearchListener, Runnable::run);
|
||||
mySearcher = new MultiThreadSearcher(myBufferedListener, run ->
|
||||
ApplicationManager.getApplication().invokeLater(run), equalityProviders);
|
||||
mySearcher = Experiments.getInstance().isFeatureEnabled("search.everywhere.mixed.results")
|
||||
? new MixedResultsSearcher(myBufferedListener, run -> ApplicationManager.getApplication().invokeLater(run), equalityProviders)
|
||||
: new GroupedResultsSearcher(myBufferedListener, run -> ApplicationManager.getApplication().invokeLater(run), equalityProviders);
|
||||
|
||||
Runnable scopeChangedCallback = () -> {
|
||||
updateSearchFieldAdvertisement();
|
||||
scheduleRebuildList();
|
||||
@@ -136,7 +134,10 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
int[] selectedIndices = myResultsList.getSelectedIndices();
|
||||
if (selectedIndices.length > 1) {
|
||||
boolean multiSelection = Arrays.stream(selectedIndices)
|
||||
.allMatch(i -> myListModel.getContributorForIndex(i).isMultiSelectionSupported());
|
||||
.allMatch(i -> {
|
||||
SearchEverywhereContributor<Object> contributor = myListModel.getContributorForIndex(i);
|
||||
return contributor != null && contributor.isMultiSelectionSupported();
|
||||
});
|
||||
if (!multiSelection) {
|
||||
int index = myResultsList.getLeadSelectionIndex();
|
||||
myResultsList.setSelectedIndex(index);
|
||||
@@ -157,24 +158,21 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
return (list, value, index, isSelected, cellHasFocus) -> new JPanel();
|
||||
}
|
||||
|
||||
return new CompositeCellRenderer();
|
||||
return myListFactory.createListRenderer(myListModel, myHeader);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JBList<Object> createList() {
|
||||
myListModel = new SearchListModel();
|
||||
myListModel = myListFactory.createModel();
|
||||
addListDataListener(myListModel);
|
||||
|
||||
return new JBList<>(myListModel);
|
||||
return myListFactory.createList(myListModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toggleEverywhereFilter() {
|
||||
myHeader.toggleEverywhere();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchToTab(@NotNull String tabID) {
|
||||
SETab selectedTab = myHeader.getTabs().stream()
|
||||
.filter(tab -> tab.getID().equals(tabID))
|
||||
@@ -228,12 +226,10 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSelectedTabID() {
|
||||
return myHeader.getSelectedTab().getID();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getSelectionIdentity() {
|
||||
Object value = myResultsList.getSelectedValue();
|
||||
@@ -264,6 +260,7 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
List<PsiElement> elements = indicesStream.mapToObj(i -> {
|
||||
SearchEverywhereContributor<Object> contributor = myListModel.getContributorForIndex(i);
|
||||
Object item = myListModel.getElementAt(i);
|
||||
//noinspection ConstantConditions
|
||||
Object psi = contributor.getDataForItem(item, CommonDataKeys.PSI_ELEMENT.getName());
|
||||
return (PsiElement)psi;
|
||||
})
|
||||
@@ -276,6 +273,7 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
return indicesStream.mapToObj(i -> {
|
||||
SearchEverywhereContributor<Object> contributor = myListModel.getContributorForIndex(i);
|
||||
Object item = myListModel.getElementAt(i);
|
||||
//noinspection ConstantConditions
|
||||
return contributor.getDataForItem(item, dataId);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
@@ -318,6 +316,7 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
@NlsContexts.PopupAdvertisement
|
||||
protected String[] getInitialHints() {
|
||||
return new String[]{
|
||||
IdeBundle.message("searcheverywhere.open.in.split.shortcuts.hint",
|
||||
@@ -503,14 +502,14 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
triggerTabSwitched(e);
|
||||
});
|
||||
registerAction(SearchEverywhereActions.NAVIGATE_TO_NEXT_GROUP, e -> {
|
||||
fetchGroups(true);
|
||||
scrollList(true);
|
||||
FeatureUsageData data = SearchEverywhereUsageTriggerCollector
|
||||
.createData(null)
|
||||
.addInputEvent(e);
|
||||
featureTriggered(SearchEverywhereUsageTriggerCollector.GROUP_NAVIGATE, data);
|
||||
});
|
||||
registerAction(SearchEverywhereActions.NAVIGATE_TO_PREV_GROUP, e -> {
|
||||
fetchGroups(false);
|
||||
scrollList(false);
|
||||
FeatureUsageData data = SearchEverywhereUsageTriggerCollector
|
||||
.createData(null)
|
||||
.addInputEvent(e);
|
||||
@@ -578,6 +577,7 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
private void showDescriptionForIndex(int index) {
|
||||
if (index >= 0 && !myListModel.isMoreElement(index)) {
|
||||
SearchEverywhereContributor<Object> contributor = myListModel.getContributorForIndex(index);
|
||||
//noinspection ConstantConditions
|
||||
Object data = contributor.getDataForItem(
|
||||
myListModel.getElementAt(index), SearchEverywhereDataKeys.ITEM_STRING_DESCRIPTION.getName());
|
||||
if (data instanceof String) {
|
||||
@@ -645,18 +645,13 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
featureTriggered(SearchEverywhereUsageTriggerCollector.TAB_SWITCHED, data);
|
||||
}
|
||||
|
||||
private void fetchGroups(boolean down) {
|
||||
int index = myResultsList.getSelectedIndex();
|
||||
do {
|
||||
index += down ? 1 : -1;
|
||||
}
|
||||
while (index >= 0 &&
|
||||
index < myListModel.getSize() &&
|
||||
!myListModel.isGroupFirstItem(index) &&
|
||||
!myListModel.isMoreElement(index));
|
||||
if (index >= 0 && index < myListModel.getSize()) {
|
||||
myResultsList.setSelectedIndex(index);
|
||||
ScrollingUtil.ensureIndexIsVisible(myResultsList, index, 0);
|
||||
private void scrollList(boolean down) {
|
||||
int currentIndex = myResultsList.getSelectedIndex();
|
||||
int newIndex = myListModel.getIndexToScroll(currentIndex, down);
|
||||
|
||||
if (newIndex != currentIndex) {
|
||||
myResultsList.setSelectedIndex(newIndex);
|
||||
ScrollingUtil.ensureIndexIsVisible(myResultsList, newIndex, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,6 +728,7 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
Object value = myListModel.getElementAt(i);
|
||||
|
||||
String selectedTabContributorID = myHeader.getSelectedTab().getReportableID();
|
||||
//noinspection ConstantConditions
|
||||
String reportableContributorID = getReportableContributorID(contributor);
|
||||
FeatureUsageData data = SearchEverywhereUsageTriggerCollector.createData(reportableContributorID, selectedTabContributorID, i);
|
||||
if (value instanceof PsiElement) {
|
||||
@@ -753,10 +749,30 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
|
||||
private void showMoreElements(SearchEverywhereContributor contributor) {
|
||||
featureTriggered(SearchEverywhereUsageTriggerCollector.MORE_ITEM_SELECTED, null);
|
||||
|
||||
if (contributor != null) {
|
||||
myListModel.setHasMore(contributor, false);
|
||||
}
|
||||
else {
|
||||
myListModel.clearMoreItems();
|
||||
}
|
||||
|
||||
Map<SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>> found = myListModel.getFoundElementsMap();
|
||||
int limit = myListModel.getItemsForContributor(contributor)
|
||||
+ (myHeader.getSelectedTab().isSingleContributor() ? SINGLE_CONTRIBUTOR_ELEMENTS_LIMIT : MULTIPLE_CONTRIBUTORS_ELEMENTS_LIMIT);
|
||||
mySearchProgressIndicator = mySearcher.findMoreItems(found, getSearchPattern(), contributor, limit);
|
||||
int additionalItemsCount = myHeader.getSelectedTab().isSingleContributor() ? SINGLE_CONTRIBUTOR_ELEMENTS_LIMIT
|
||||
: MULTIPLE_CONTRIBUTORS_ELEMENTS_LIMIT;
|
||||
|
||||
Stream<Map.Entry<SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>>> stream = found.entrySet().stream();
|
||||
if (contributor != null) {
|
||||
stream = stream.filter(entry -> entry.getKey() == contributor);
|
||||
}
|
||||
else {
|
||||
stream = stream.filter(entry -> myListModel.hasMoreElements(entry.getKey()));
|
||||
}
|
||||
|
||||
Map<? extends SearchEverywhereContributor<?>, Integer> contributorsAndLimits =
|
||||
stream.collect(Collectors.toMap(entry -> entry.getKey(), entry -> entry.getValue().size() + additionalItemsCount));
|
||||
|
||||
mySearchProgressIndicator = mySearcher.findMoreItems(found, contributorsAndLimits, getSearchPattern());
|
||||
}
|
||||
|
||||
private void stopSearching() {
|
||||
@@ -774,7 +790,6 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
searchFinishedHandler.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
@TestOnly
|
||||
public Future<List<Object>> findElementsForPattern(String pattern) {
|
||||
clearResults();
|
||||
@@ -787,47 +802,12 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
@TestOnly
|
||||
public void clearResults() {
|
||||
myListModel.clear();
|
||||
mySearchField.setText("");
|
||||
}
|
||||
|
||||
private class CompositeCellRenderer implements ListCellRenderer<Object> {
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
||||
if (value == SearchListModel.MORE_ELEMENT) {
|
||||
Component component = myMoreRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
||||
component.setPreferredSize(UIUtil.updateListRowHeight(component.getPreferredSize()));
|
||||
return component;
|
||||
}
|
||||
|
||||
SearchEverywhereContributor<Object> contributor = myListModel.getContributorForIndex(index);
|
||||
Component component = SearchEverywhereClassifier.EP_Manager.getListCellRendererComponent(
|
||||
list, value, index, isSelected, cellHasFocus);
|
||||
if (component == null) {
|
||||
component = contributor.getElementsRenderer().getListCellRendererComponent(
|
||||
list, value, index, isSelected, true);
|
||||
}
|
||||
|
||||
if (component instanceof JComponent) {
|
||||
Border border = ((JComponent)component).getBorder();
|
||||
if (border != GotoActionModel.GotoActionListCellRenderer.TOGGLE_BUTTON_BORDER) {
|
||||
((JComponent)component).setBorder(JBUI.Borders.empty(1, 2));
|
||||
}
|
||||
}
|
||||
AppUIUtil.targetToDevice(component, list);
|
||||
component.setPreferredSize(UIUtil.updateListRowHeight(component.getPreferredSize()));
|
||||
if (!myHeader.getSelectedTab().isSingleContributor() && myListModel.isGroupFirstItem(index)) {
|
||||
component = myGroupTitleRenderer.withDisplayedData(contributor.getFullGroupName(), component);
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
private final ListCellRenderer<Object> myCommandRenderer = new ColoredListCellRenderer<>() {
|
||||
|
||||
@Override
|
||||
@@ -843,320 +823,53 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
}
|
||||
};
|
||||
|
||||
private final ListCellRenderer<Object> myMoreRenderer = new ColoredListCellRenderer<>() {
|
||||
protected static class FiltersAction extends ShowFilterAction {
|
||||
final PersistentSearchEverywhereContributorFilter<?> filter;
|
||||
final Runnable rebuildRunnable;
|
||||
|
||||
@Override
|
||||
protected int getMinHeight() {
|
||||
return -1;
|
||||
public FiltersAction(@NotNull PersistentSearchEverywhereContributorFilter<?> filter,
|
||||
@NotNull Runnable rebuildRunnable) {
|
||||
this.filter = filter;
|
||||
this.rebuildRunnable = rebuildRunnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void customizeCellRenderer(@NotNull JList<?> list, Object value, int index, boolean selected, boolean hasFocus) {
|
||||
if (value != SearchListModel.MORE_ELEMENT) {
|
||||
throw new AssertionError(value);
|
||||
}
|
||||
setFont(UIUtil.getLabelFont().deriveFont(UIUtil.getFontSize(UIUtil.FontSize.SMALL)));
|
||||
append(IdeBundle.message("search.everywhere.points.more"), SMALL_LABEL_ATTRS);
|
||||
setIpad(JBInsets.create(1, 7));
|
||||
setMyBorder(null);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private final GroupTitleRenderer myGroupTitleRenderer = new GroupTitleRenderer();
|
||||
|
||||
private static class GroupTitleRenderer extends CellRendererPanel {
|
||||
|
||||
final SimpleColoredComponent titleLabel = new SimpleColoredComponent();
|
||||
|
||||
GroupTitleRenderer() {
|
||||
setLayout(new BorderLayout());
|
||||
SeparatorComponent separatorComponent = new SeparatorComponent(
|
||||
titleLabel.getPreferredSize().height / 2, JBUI.CurrentTheme.BigPopup.listSeparatorColor(), null);
|
||||
|
||||
JPanel topPanel = JBUI.Panels.simplePanel(5, 0)
|
||||
.addToCenter(separatorComponent)
|
||||
.addToLeft(titleLabel)
|
||||
.withBorder(JBUI.Borders.empty(1, 7))
|
||||
.withBackground(UIUtil.getListBackground());
|
||||
add(topPanel, BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
public GroupTitleRenderer withDisplayedData(@Nls String title, Component itemContent) {
|
||||
titleLabel.clear();
|
||||
titleLabel.append(title, SMALL_LABEL_ATTRS);
|
||||
Component prevContent = ((BorderLayout)getLayout()).getLayoutComponent(BorderLayout.CENTER);
|
||||
if (prevContent != null) {
|
||||
remove(prevContent);
|
||||
}
|
||||
add(itemContent, BorderLayout.CENTER);
|
||||
accessibleContext = itemContent.getAccessibleContext();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SearchListModel extends AbstractListModel<Object> {
|
||||
|
||||
static final Object MORE_ELEMENT = new Object();
|
||||
|
||||
private final List<SearchEverywhereFoundElementInfo> listElements = new ArrayList<>();
|
||||
|
||||
private boolean resultsExpired = false;
|
||||
|
||||
public boolean isResultsExpired() {
|
||||
return resultsExpired;
|
||||
}
|
||||
|
||||
public void expireResults() {
|
||||
resultsExpired = true;
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return listElements.size();
|
||||
protected boolean isActive() {
|
||||
return filter.getAllElements().size() != filter.getSelectedElements().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getElementAt(int index) {
|
||||
return listElements.get(index).getElement();
|
||||
protected ElementsChooser<?> createChooser() {
|
||||
return createChooser(filter, rebuildRunnable);
|
||||
}
|
||||
|
||||
public List<Object> getItems() {
|
||||
return new ArrayList<>(values());
|
||||
}
|
||||
|
||||
public Collection<Object> getFoundItems(SearchEverywhereContributor contributor) {
|
||||
return listElements.stream()
|
||||
.filter(info -> info.getContributor() == contributor && info.getElement() != MORE_ELEMENT)
|
||||
.map(info -> info.getElement())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public boolean hasMoreElements(SearchEverywhereContributor contributor) {
|
||||
return listElements.stream()
|
||||
.anyMatch(info -> info.getElement() == MORE_ELEMENT && info.getContributor() == contributor);
|
||||
}
|
||||
|
||||
public void addElements(List<? extends SearchEverywhereFoundElementInfo> items) {
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<SearchEverywhereContributor<?>, List<SearchEverywhereFoundElementInfo>> itemsMap = new HashMap<>();
|
||||
items.forEach(info -> {
|
||||
List<SearchEverywhereFoundElementInfo> list = itemsMap.computeIfAbsent(info.getContributor(), contributor -> new ArrayList<>());
|
||||
list.add(info);
|
||||
});
|
||||
itemsMap.forEach((contributor, list) -> list.sort(Comparator.comparingInt(SearchEverywhereFoundElementInfo::getPriority).reversed()));
|
||||
|
||||
if (resultsExpired) {
|
||||
retainContributors(itemsMap.keySet());
|
||||
clearMoreItems();
|
||||
|
||||
itemsMap.forEach((contributor, list) -> {
|
||||
Object[] oldItems = ArrayUtil.toObjectArray(getFoundItems(contributor));
|
||||
Object[] newItems = list.stream()
|
||||
.map(SearchEverywhereFoundElementInfo::getElement)
|
||||
.toArray();
|
||||
try {
|
||||
Diff.Change change = Diff.buildChanges(oldItems, newItems);
|
||||
applyChange(change, contributor, list);
|
||||
}
|
||||
catch (FilesTooBigForDiffException e) {
|
||||
LOG.error("Cannot calculate diff for updated search results");
|
||||
}
|
||||
});
|
||||
resultsExpired = false;
|
||||
}
|
||||
else {
|
||||
itemsMap.forEach((contributor, list) -> {
|
||||
int startIndex = contributors().indexOf(contributor);
|
||||
int insertionIndex = getInsertionPoint(contributor);
|
||||
int endIndex = insertionIndex + list.size() - 1;
|
||||
listElements.addAll(insertionIndex, list);
|
||||
fireIntervalAdded(this, insertionIndex, endIndex);
|
||||
|
||||
// there were items for this contributor before update
|
||||
if (startIndex >= 0) {
|
||||
listElements.subList(startIndex, endIndex + 1)
|
||||
.sort(Comparator.comparingInt(SearchEverywhereFoundElementInfo::getPriority).reversed());
|
||||
fireContentsChanged(this, startIndex, endIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void retainContributors(Collection<SearchEverywhereContributor<?>> retainContributors) {
|
||||
Iterator<SearchEverywhereFoundElementInfo> iterator = listElements.iterator();
|
||||
int startInterval = 0;
|
||||
int endInterval = -1;
|
||||
while (iterator.hasNext()) {
|
||||
SearchEverywhereFoundElementInfo item = iterator.next();
|
||||
if (retainContributors.contains(item.getContributor())) {
|
||||
if (startInterval <= endInterval) {
|
||||
fireIntervalRemoved(this, startInterval, endInterval);
|
||||
startInterval = endInterval + 2;
|
||||
}
|
||||
else {
|
||||
startInterval++;
|
||||
}
|
||||
}
|
||||
else {
|
||||
iterator.remove();
|
||||
}
|
||||
endInterval++;
|
||||
}
|
||||
|
||||
if (startInterval <= endInterval) {
|
||||
fireIntervalRemoved(this, startInterval, endInterval);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearMoreItems() {
|
||||
ListIterator<SearchEverywhereFoundElementInfo> iterator = listElements.listIterator();
|
||||
while (iterator.hasNext()) {
|
||||
int index = iterator.nextIndex();
|
||||
if (iterator.next().getElement() == MORE_ELEMENT) {
|
||||
iterator.remove();
|
||||
fireContentsChanged(this, index, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyChange(Diff.Change change,
|
||||
SearchEverywhereContributor<?> contributor,
|
||||
List<SearchEverywhereFoundElementInfo> newItems) {
|
||||
int firstItemIndex = contributors().indexOf(contributor);
|
||||
if (firstItemIndex < 0) {
|
||||
firstItemIndex = getInsertionPoint(contributor);
|
||||
}
|
||||
|
||||
for (Diff.Change ch : toRevertedList(change)) {
|
||||
if (ch.deleted > 0) {
|
||||
for (int i = ch.deleted - 1; i >= 0; i--) {
|
||||
int index = firstItemIndex + ch.line0 + i;
|
||||
listElements.remove(index);
|
||||
}
|
||||
fireIntervalRemoved(this, firstItemIndex + ch.line0, firstItemIndex + ch.line0 + ch.deleted - 1);
|
||||
private static <T> ElementsChooser<T> createChooser(@NotNull PersistentSearchEverywhereContributorFilter<T> filter,
|
||||
@NotNull Runnable rebuildRunnable) {
|
||||
ElementsChooser<T> res = new ElementsChooser<>(filter.getAllElements(), false) {
|
||||
@Override
|
||||
protected String getItemText(@NotNull T value) {
|
||||
return filter.getElementText(value);
|
||||
}
|
||||
|
||||
if (ch.inserted > 0) {
|
||||
List<SearchEverywhereFoundElementInfo> addedItems = newItems.subList(ch.line1, ch.line1 + ch.inserted);
|
||||
listElements.addAll(firstItemIndex + ch.line0, addedItems);
|
||||
fireIntervalAdded(this, firstItemIndex + ch.line0, firstItemIndex + ch.line0 + ch.inserted - 1);
|
||||
@Nullable
|
||||
@Override
|
||||
protected Icon getItemIcon(@NotNull T value) {
|
||||
return filter.getElementIcon(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Diff.Change> toRevertedList(Diff.Change change) {
|
||||
List<Diff.Change> res = new ArrayList<>();
|
||||
while (change != null) {
|
||||
res.add(0, change);
|
||||
change = change.link;
|
||||
}
|
||||
};
|
||||
res.markElements(filter.getSelectedElements());
|
||||
ElementsChooser.ElementsMarkListener<T> listener = (element, isMarked) -> {
|
||||
filter.setSelected(element, isMarked);
|
||||
rebuildRunnable.run();
|
||||
};
|
||||
res.addElementsMarkListener(listener);
|
||||
return res;
|
||||
}
|
||||
|
||||
public void removeElement(@NotNull Object item, SearchEverywhereContributor contributor) {
|
||||
int index = contributors().indexOf(contributor);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (index < listElements.size() && listElements.get(index).getContributor() == contributor) {
|
||||
if (item.equals(listElements.get(index).getElement())) {
|
||||
listElements.remove(index);
|
||||
fireIntervalRemoved(this, index, index);
|
||||
return;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
public void setHasMore(SearchEverywhereContributor<?> contributor, boolean newVal) {
|
||||
int index = contributors().lastIndexOf(contributor);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean alreadyHas = isMoreElement(index);
|
||||
if (alreadyHas && !newVal) {
|
||||
listElements.remove(index);
|
||||
fireIntervalRemoved(this, index, index);
|
||||
}
|
||||
|
||||
if (!alreadyHas && newVal) {
|
||||
index += 1;
|
||||
listElements.add(index, new SearchEverywhereFoundElementInfo(MORE_ELEMENT, 0, contributor));
|
||||
fireIntervalAdded(this, index, index);
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
int index = listElements.size() - 1;
|
||||
listElements.clear();
|
||||
if (index >= 0) {
|
||||
fireIntervalRemoved(this, 0, index);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(Object val) {
|
||||
return values().contains(val);
|
||||
}
|
||||
|
||||
public boolean isMoreElement(int index) {
|
||||
return listElements.get(index).getElement() == MORE_ELEMENT;
|
||||
}
|
||||
|
||||
public <Item> SearchEverywhereContributor<Item> getContributorForIndex(int index) {
|
||||
//noinspection unchecked
|
||||
return (SearchEverywhereContributor<Item>)listElements.get(index).getContributor();
|
||||
}
|
||||
|
||||
public boolean isGroupFirstItem(int index) {
|
||||
return index == 0 || listElements.get(index).getContributor() != listElements.get(index - 1).getContributor();
|
||||
}
|
||||
|
||||
public int getItemsForContributor(SearchEverywhereContributor<?> contributor) {
|
||||
List<SearchEverywhereContributor> contributorsList = contributors();
|
||||
int first = contributorsList.indexOf(contributor);
|
||||
int last = contributorsList.lastIndexOf(contributor);
|
||||
if (isMoreElement(last)) {
|
||||
last -= 1;
|
||||
}
|
||||
return last - first + 1;
|
||||
}
|
||||
|
||||
public Map<SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>> getFoundElementsMap() {
|
||||
return listElements.stream()
|
||||
.filter(info -> info.element != MORE_ELEMENT)
|
||||
.collect(Collectors.groupingBy(o -> o.getContributor(), Collectors.toCollection(ArrayList::new)));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<SearchEverywhereContributor> contributors() {
|
||||
return Lists.transform(listElements, info -> info.getContributor());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<Object> values() {
|
||||
return Lists.transform(listElements, info -> info.getElement());
|
||||
}
|
||||
|
||||
private int getInsertionPoint(SearchEverywhereContributor contributor) {
|
||||
if (listElements.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
List<SearchEverywhereContributor> list = contributors();
|
||||
int index = list.lastIndexOf(contributor);
|
||||
if (index >= 0) {
|
||||
return isMoreElement(index) ? index : index + 1;
|
||||
}
|
||||
|
||||
index = Collections.binarySearch(list, contributor, Comparator.comparingInt(SearchEverywhereContributor::getSortWeight));
|
||||
return -index - 1;
|
||||
}
|
||||
}
|
||||
|
||||
private class ShowInFindToolWindowAction extends DumbAwareAction {
|
||||
@@ -1376,7 +1089,7 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
|
||||
@Override
|
||||
public void elementsAdded(@NotNull List<? extends SearchEverywhereFoundElementInfo> list) {
|
||||
boolean wasEmpty = myListModel.listElements.isEmpty();
|
||||
boolean wasEmpty = myListModel.getSize() == 0;
|
||||
|
||||
mySelectionTracker.lock();
|
||||
myListModel.addElements(list);
|
||||
@@ -1384,13 +1097,13 @@ public final class SearchEverywhereUI extends SearchEverywhereUIBase implements
|
||||
|
||||
mySelectionTracker.restoreSelection();
|
||||
|
||||
if (wasEmpty && !myListModel.listElements.isEmpty()) {
|
||||
if (wasEmpty && myListModel.getSize() > 0) {
|
||||
Object prevSelection = ((SearchEverywhereManagerImpl)SearchEverywhereManager.getInstance(myProject))
|
||||
.getPrevSelection(getSelectedTabID());
|
||||
if (prevSelection instanceof Integer) {
|
||||
for (SearchEverywhereFoundElementInfo info : myListModel.listElements) {
|
||||
if (Objects.hashCode(info.element) == ((Integer)prevSelection).intValue()) {
|
||||
myResultsList.setSelectedValue(info.element, true);
|
||||
for (Object item : myListModel.getItems()) {
|
||||
if (Objects.hashCode(item) == ((Integer)prevSelection).intValue()) {
|
||||
myResultsList.setSelectedValue(item, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2000-2020 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 com.intellij.ide.actions.BigPopupUI;
|
||||
import com.intellij.ide.actions.bigPopup.ShowFilterAction;
|
||||
import com.intellij.ide.util.ElementsChooser;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
public abstract class SearchEverywhereUIBase extends BigPopupUI {
|
||||
public static final String SEARCH_EVERYWHERE_SEARCH_FILED_KEY = "search-everywhere-textfield"; //only for testing purposes
|
||||
|
||||
public SearchEverywhereUIBase(Project project) {super(project);}
|
||||
|
||||
public abstract void toggleEverywhereFilter();
|
||||
|
||||
public abstract void switchToTab(@NotNull String tabID);
|
||||
|
||||
public abstract String getSelectedTabID();
|
||||
|
||||
@Nullable
|
||||
public abstract Object getSelectionIdentity();
|
||||
|
||||
@TestOnly
|
||||
public abstract Future<List<Object>> findElementsForPattern(String pattern);
|
||||
|
||||
@TestOnly
|
||||
public abstract void clearResults();
|
||||
|
||||
protected static class FiltersAction extends ShowFilterAction {
|
||||
final PersistentSearchEverywhereContributorFilter<?> filter;
|
||||
final Runnable rebuildRunnable;
|
||||
|
||||
public FiltersAction(@NotNull PersistentSearchEverywhereContributorFilter<?> filter,
|
||||
@NotNull Runnable rebuildRunnable) {
|
||||
this.filter = filter;
|
||||
this.rebuildRunnable = rebuildRunnable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isActive() {
|
||||
return filter.getAllElements().size() != filter.getSelectedElements().size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ElementsChooser<?> createChooser() {
|
||||
return createChooser(filter, rebuildRunnable);
|
||||
}
|
||||
|
||||
private static <T> ElementsChooser<T> createChooser(@NotNull PersistentSearchEverywhereContributorFilter<T> filter,
|
||||
@NotNull Runnable rebuildRunnable) {
|
||||
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);
|
||||
rebuildRunnable.run();
|
||||
};
|
||||
res.addElementsMarkListener(listener);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2000-2020 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 com.google.common.collect.Lists;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
abstract class SearchListModel extends AbstractListModel<Object> {
|
||||
|
||||
static final Object MORE_ELEMENT = new Object();
|
||||
|
||||
protected final List<SearchEverywhereFoundElementInfo> listElements = new ArrayList<>();
|
||||
protected boolean resultsExpired = false;
|
||||
|
||||
public boolean isResultsExpired() {
|
||||
return resultsExpired;
|
||||
}
|
||||
|
||||
public void expireResults() {
|
||||
resultsExpired = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return listElements.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getElementAt(int index) {
|
||||
return listElements.get(index).getElement();
|
||||
}
|
||||
|
||||
public List<Object> getItems() {
|
||||
return new ArrayList<>(values());
|
||||
}
|
||||
|
||||
public Collection<Object> getFoundItems(SearchEverywhereContributor contributor) {
|
||||
return listElements.stream()
|
||||
.filter(info -> info.getContributor() == contributor && info.getElement() != MORE_ELEMENT)
|
||||
.map(info -> info.getElement())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected List<SearchEverywhereContributor> contributors() {
|
||||
return Lists.transform(listElements, info -> info.getContributor());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected List<Object> values() {
|
||||
return Lists.transform(listElements, info -> info.getElement());
|
||||
}
|
||||
|
||||
public abstract boolean hasMoreElements(SearchEverywhereContributor<?> contributor);
|
||||
|
||||
public abstract void setHasMore(SearchEverywhereContributor<?> contributor, boolean contributorHasMore);
|
||||
|
||||
public abstract void addElements(List<? extends SearchEverywhereFoundElementInfo> items);
|
||||
|
||||
public abstract void removeElement(@NotNull Object item, SearchEverywhereContributor<?> contributor);
|
||||
|
||||
public abstract void clearMoreItems();
|
||||
|
||||
public abstract int getIndexToScroll(int currentIndex, boolean scrollDown);
|
||||
|
||||
public void clear() {
|
||||
int index = getSize() - 1;
|
||||
listElements.clear();
|
||||
if (index >= 0) {
|
||||
fireIntervalRemoved(this, 0, index);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean contains(Object val) {
|
||||
return values().contains(val);
|
||||
}
|
||||
|
||||
public boolean isMoreElement(int index) {
|
||||
return getElementAt(index) == MORE_ELEMENT;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public <Item> SearchEverywhereContributor<Item> getContributorForIndex(int index) {
|
||||
//noinspection unchecked
|
||||
return (SearchEverywhereContributor<Item>)listElements.get(index).getContributor();
|
||||
}
|
||||
|
||||
public Map<SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>> getFoundElementsMap() {
|
||||
return listElements.stream()
|
||||
.filter(info -> info.element != MORE_ELEMENT)
|
||||
.collect(Collectors.groupingBy(o -> o.getContributor(), Collectors.toCollection(ArrayList::new)));
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,12 @@ import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Implementation of {@link MultiThreadSearcher.Listener} which decrease events rate and raise batch updates
|
||||
* Implementation of {@link SESearcher.Listener} which decrease events rate and raise batch updates
|
||||
* each {@code throttlingDelay} milliseconds.
|
||||
* <br>
|
||||
* Not thread-safe and should be notified only in EDT
|
||||
*/
|
||||
class ThrottlingListenerWrapper implements MultiThreadSearcher.Listener {
|
||||
class ThrottlingListenerWrapper implements SESearcher.Listener {
|
||||
|
||||
public final int myThrottlingDelay;
|
||||
|
||||
@@ -28,7 +28,7 @@ class ThrottlingListenerWrapper implements MultiThreadSearcher.Listener {
|
||||
private final Alarm flushAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
|
||||
private boolean flushScheduled;
|
||||
|
||||
ThrottlingListenerWrapper(int throttlingDelay, MultiThreadSearcher.Listener delegateListener, Executor delegateExecutor) {
|
||||
ThrottlingListenerWrapper(int throttlingDelay, SESearcher.Listener delegateListener, Executor delegateExecutor) {
|
||||
myThrottlingDelay = throttlingDelay;
|
||||
myDelegateListener = delegateListener;
|
||||
myDelegateExecutor = delegateExecutor;
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
// Copyright 2000-2020 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.mixed;
|
||||
|
||||
import com.intellij.ui.ScrollingUtil;
|
||||
import com.intellij.ui.components.JBList;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
class SEListSelectionTracker implements ListSelectionListener {
|
||||
|
||||
private final JBList<?> myList;
|
||||
private final SearchEverywhereUIMixedResults.SearchListModel myListModel;
|
||||
|
||||
private int myLockCounter;
|
||||
private final List<Object> mySelectedItems = new ArrayList<>();
|
||||
private boolean myMoreSelected;
|
||||
|
||||
SEListSelectionTracker(@NotNull JBList<?> list, @NotNull SearchEverywhereUIMixedResults.SearchListModel model) {
|
||||
myList = list;
|
||||
myListModel = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
if (isLocked()) return;
|
||||
if (myList.getSelectedIndices().length == 0) return;
|
||||
|
||||
saveSelection();
|
||||
}
|
||||
|
||||
void saveSelection() {
|
||||
mySelectedItems.clear();
|
||||
|
||||
int[] indices = myList.getSelectedIndices();
|
||||
List<?> selectedItemsList;
|
||||
if (indices.length == 1 && myListModel.isMoreElement(indices[0])) {
|
||||
myMoreSelected = true;
|
||||
selectedItemsList = Collections.singletonList(myListModel.getElementAt(indices[0] - 1));
|
||||
}
|
||||
else {
|
||||
myMoreSelected = false;
|
||||
selectedItemsList = Arrays.stream(indices)
|
||||
.filter(i -> !myListModel.isMoreElement(i))
|
||||
.mapToObj(i -> myListModel.getElementAt(i))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
mySelectedItems.addAll(selectedItemsList);
|
||||
}
|
||||
|
||||
void restoreSelection() {
|
||||
if (isLocked()) return;
|
||||
|
||||
lock();
|
||||
try {
|
||||
int[] indicesToSelect = calcIndicesToSelect();
|
||||
if (myMoreSelected && indicesToSelect.length == 1) {
|
||||
indicesToSelect[0] += 1;
|
||||
}
|
||||
|
||||
if (indicesToSelect.length == 0) {
|
||||
indicesToSelect = new int[]{0};
|
||||
}
|
||||
|
||||
myList.setSelectedIndices(indicesToSelect);
|
||||
ScrollingUtil.ensureRangeIsVisible(myList, indicesToSelect[0], indicesToSelect[indicesToSelect.length - 1]);
|
||||
}
|
||||
finally {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void resetSelectionIfNeeded() {
|
||||
int[] indices = calcIndicesToSelect();
|
||||
if (indices.length == 0) {
|
||||
mySelectedItems.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void lock() {
|
||||
myLockCounter++;
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
if (myLockCounter > 0) myLockCounter--;
|
||||
}
|
||||
|
||||
private boolean isLocked() {
|
||||
return myLockCounter > 0;
|
||||
}
|
||||
|
||||
private int[] calcIndicesToSelect() {
|
||||
List<Object> items = myListModel.getItems();
|
||||
if (items.isEmpty()) return ArrayUtil.EMPTY_INT_ARRAY;
|
||||
|
||||
return IntStream.range(0, items.size())
|
||||
.filter(i -> mySelectedItems.contains(items.get(i)))
|
||||
.toArray();
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// Copyright 2000-2020 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.mixed;
|
||||
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor;
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereFoundElementInfo;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
interface SESearcher {
|
||||
ProgressIndicator search(@NotNull Map<? extends SearchEverywhereContributor<?>, Integer> contributorsAndLimits,
|
||||
@NotNull String pattern);
|
||||
|
||||
ProgressIndicator findMoreItems(@NotNull Map<? extends SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>> alreadyFound,
|
||||
@NotNull Map<? extends SearchEverywhereContributor<?>, Integer> contributorsAndLimits,
|
||||
@NotNull String pattern);
|
||||
|
||||
/**
|
||||
* Search process listener interface
|
||||
*/
|
||||
interface Listener {
|
||||
void elementsAdded(@NotNull List<? extends SearchEverywhereFoundElementInfo> list);
|
||||
void elementsRemoved(@NotNull List<? extends SearchEverywhereFoundElementInfo> list);
|
||||
void searchFinished(@NotNull Map<SearchEverywhereContributor<?>, Boolean> hasMoreContributors);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,142 +0,0 @@
|
||||
// Copyright 2000-2020 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.mixed;
|
||||
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor;
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereFoundElementInfo;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.util.Alarm;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Implementation of {@link MultiThreadSearcher.Listener} which decrease events rate and raise batch updates
|
||||
* each {@code throttlingDelay} milliseconds.
|
||||
* <br>
|
||||
* Not thread-safe and should be notified only in EDT
|
||||
*/
|
||||
class ThrottlingListenerWrapper implements SESearcher.Listener {
|
||||
|
||||
public final int myThrottlingDelay;
|
||||
|
||||
private final SESearcher.Listener myDelegateListener;
|
||||
private final Executor myDelegateExecutor;
|
||||
|
||||
private final Buffer myBuffer = new Buffer();
|
||||
private final BiConsumer<List<SearchEverywhereFoundElementInfo>, List<SearchEverywhereFoundElementInfo>> myFlushConsumer;
|
||||
|
||||
private final Alarm flushAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
|
||||
private boolean flushScheduled;
|
||||
|
||||
ThrottlingListenerWrapper(int throttlingDelay, SESearcher.Listener delegateListener, Executor delegateExecutor) {
|
||||
myThrottlingDelay = throttlingDelay;
|
||||
myDelegateListener = delegateListener;
|
||||
myDelegateExecutor = delegateExecutor;
|
||||
|
||||
myFlushConsumer = (added, removed) -> {
|
||||
if (!added.isEmpty()) {
|
||||
myDelegateExecutor.execute(() -> myDelegateListener.elementsAdded(added));
|
||||
}
|
||||
if (!removed.isEmpty()) {
|
||||
myDelegateExecutor.execute(() -> myDelegateListener.elementsRemoved(removed));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void clearBuffer() {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
myBuffer.clear();
|
||||
cancelScheduledFlush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementsAdded(@NotNull List<? extends SearchEverywhereFoundElementInfo> list) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
myBuffer.addEvent(new Event(Event.ADD, list));
|
||||
scheduleFlushBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void elementsRemoved(@NotNull List<? extends SearchEverywhereFoundElementInfo> list) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
myBuffer.addEvent(new Event(Event.REMOVE, list));
|
||||
scheduleFlushBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void searchFinished(@NotNull Map<SearchEverywhereContributor<?>, Boolean> hasMoreContributors) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
myBuffer.flush(myFlushConsumer);
|
||||
myDelegateExecutor.execute(() -> myDelegateListener.searchFinished(hasMoreContributors));
|
||||
cancelScheduledFlush();
|
||||
}
|
||||
|
||||
private void scheduleFlushBuffer() {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
|
||||
Runnable flushTask = () -> {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
if (!flushScheduled) return;
|
||||
flushScheduled = false;
|
||||
myBuffer.flush(myFlushConsumer);
|
||||
};
|
||||
|
||||
if (!flushScheduled) {
|
||||
flushAlarm.addRequest(flushTask, myThrottlingDelay);
|
||||
flushScheduled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelScheduledFlush() {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
flushAlarm.cancelAllRequests();
|
||||
flushScheduled = false;
|
||||
}
|
||||
|
||||
private static class Event {
|
||||
static final int REMOVE = 0;
|
||||
static final int ADD = 1;
|
||||
|
||||
final int type;
|
||||
final List<? extends SearchEverywhereFoundElementInfo> items;
|
||||
|
||||
Event(int type, List<? extends SearchEverywhereFoundElementInfo> items) {
|
||||
this.type = type;
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Buffer {
|
||||
private final Queue<Event> myQueue = new ArrayDeque<>();
|
||||
|
||||
public void addEvent(Event event) {
|
||||
myQueue.add(event);
|
||||
}
|
||||
|
||||
public void flush(BiConsumer<? super List<SearchEverywhereFoundElementInfo>, ? super List<SearchEverywhereFoundElementInfo>> consumer) {
|
||||
List<SearchEverywhereFoundElementInfo> added = new ArrayList<>();
|
||||
List<SearchEverywhereFoundElementInfo> removed = new ArrayList<>();
|
||||
myQueue.forEach(event -> {
|
||||
if (event.type == Event.ADD) {
|
||||
added.addAll(event.items);
|
||||
} else {
|
||||
event.items.forEach(removing -> {
|
||||
if (!added.removeIf(existing -> existing.getContributor() == removing.getContributor() && existing.getElement() == removing.getElement())) {
|
||||
removed.add(removing);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
myQueue.clear();
|
||||
consumer.accept(added, removed);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
myQueue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -164,7 +164,7 @@ class MixingMultiThreadSearchTest extends BasePlatformTestCase {
|
||||
private void testScenario(Scenario scenario) {
|
||||
SearchResultsCollector collector = new SearchResultsCollector()
|
||||
Alarm alarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, getTestRootDisposable())
|
||||
MultiThreadSearcher searcher = new MultiThreadSearcher(collector, { command -> alarm.addRequest(command, 0) }, ourEqualityProviders)
|
||||
SESearcher searcher = new MixedResultsSearcher(collector, { command -> alarm.addRequest(command, 0) }, ourEqualityProviders)
|
||||
ProgressIndicator indicator = searcher.search(scenario.contributorsAndLimits, "tst")
|
||||
|
||||
try {
|
||||
|
||||
@@ -45,7 +45,7 @@ public class MultiThreadSearchDeadlockTest extends BasePlatformTestCase {
|
||||
|
||||
Collector collector = new Collector();
|
||||
Alarm alarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, getTestRootDisposable());
|
||||
MultiThreadSearcher searcher = new MultiThreadSearcher(collector, command -> alarm.addRequest(command, 0), ourEqualityProviders);
|
||||
SESearcher searcher = new GroupedResultsSearcher(collector, command -> alarm.addRequest(command, 0), ourEqualityProviders);
|
||||
ProgressIndicator progressIndicator = searcher.search(contributorsMap, "tst");
|
||||
|
||||
try {
|
||||
@@ -73,7 +73,7 @@ public class MultiThreadSearchDeadlockTest extends BasePlatformTestCase {
|
||||
|
||||
Collector collector = new Collector();
|
||||
Alarm alarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, getTestRootDisposable());
|
||||
MultiThreadSearcher searcher = new MultiThreadSearcher(collector, command -> alarm.addRequest(command, 0), ourEqualityProviders);
|
||||
SESearcher searcher = new GroupedResultsSearcher(collector, command -> alarm.addRequest(command, 0), ourEqualityProviders);
|
||||
ProgressIndicator progressIndicator = searcher.search(contributorsMap, "tst");
|
||||
|
||||
try {
|
||||
@@ -105,7 +105,7 @@ public class MultiThreadSearchDeadlockTest extends BasePlatformTestCase {
|
||||
|
||||
Collector collector = new Collector();
|
||||
Alarm alarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, getTestRootDisposable());
|
||||
MultiThreadSearcher searcher = new MultiThreadSearcher(collector, command -> alarm.addRequest(command, 0), ourEqualityProviders);
|
||||
SESearcher searcher = new GroupedResultsSearcher(collector, command -> alarm.addRequest(command, 0), ourEqualityProviders);
|
||||
ProgressIndicator progressIndicator = searcher.search(contributorsMap, "tst");
|
||||
|
||||
try {
|
||||
|
||||
@@ -30,7 +30,7 @@ public class MultiThreadSearchTest extends BasePlatformTestCase {
|
||||
Collection<Scenario> scenarios = createMultithreadScenarios();
|
||||
SearchResultsCollector collector = new SearchResultsCollector();
|
||||
Alarm alarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, getTestRootDisposable());
|
||||
MultiThreadSearcher searcher = new MultiThreadSearcher(collector, command -> alarm.addRequest(command, 0), ourEqualityProviders);
|
||||
SESearcher searcher = new GroupedResultsSearcher(collector, command -> alarm.addRequest(command, 0), ourEqualityProviders);
|
||||
|
||||
scenarios.forEach(scenario -> {
|
||||
ProgressIndicator indicator = searcher.search(scenario.contributorsAndLimits, "tst");
|
||||
@@ -262,7 +262,7 @@ public class MultiThreadSearchTest extends BasePlatformTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
private static class SearchResultsCollector implements MultiThreadSearcher.Listener {
|
||||
private static class SearchResultsCollector implements SESearcher.Listener {
|
||||
|
||||
private final Map<String, List<String>> myMap = new ConcurrentHashMap<>();
|
||||
private final AtomicBoolean myFinished = new AtomicBoolean(false);
|
||||
|
||||
@@ -13,8 +13,6 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.intellij.ide.actions.searcheverywhere.SearchEverywhereUI.SearchListModel;
|
||||
|
||||
public class SearchModelTest extends BasePlatformTestCase {
|
||||
|
||||
private final static SearchEverywhereContributor<Object> STUB_CONTRIBUTOR_1 = createStubContributor(100);
|
||||
@@ -22,7 +20,7 @@ public class SearchModelTest extends BasePlatformTestCase {
|
||||
private final static SearchEverywhereContributor<Object> STUB_CONTRIBUTOR_3 = createStubContributor(300);
|
||||
|
||||
public void testElementsAdding() {
|
||||
SearchListModel model = new SearchListModel();
|
||||
SearchListModel model = new GroupedSearchListModel();
|
||||
|
||||
// adding to empty -----------------------------------------------------------------------
|
||||
model.addElements(Arrays.asList(
|
||||
@@ -67,7 +65,7 @@ public class SearchModelTest extends BasePlatformTestCase {
|
||||
|
||||
actualItems = model.getItems();
|
||||
expectedItems = Arrays.asList("item_1_10", "item_1_20", "item_1_25", "item_1_30", "item_1_35", SearchListModel.MORE_ELEMENT,
|
||||
"item_2_05", "item_2_10", "item_2_20","item_2_23", "item_2_25","item_2_30", "item_2_40",
|
||||
"item_2_05", "item_2_10", "item_2_20", "item_2_23", "item_2_25", "item_2_30", "item_2_40",
|
||||
"item_3_03", "item_3_05", "item_3_10", "item_3_20", "item_3_30", "item_3_40", "item_3_50", SearchListModel.MORE_ELEMENT);
|
||||
Assert.assertEquals(expectedItems, actualItems);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user