removing duplicated SearchEverywhereUIMixedResults

GitOrigin-RevId: 413ac34011ca7e2bb87b8b37517def29599125a0
This commit is contained in:
Mikhail Sokolov
2020-10-14 19:09:29 +03:00
committed by intellij-monorepo-bot
parent 021d9aa60f
commit dfff0327aa
26 changed files with 869 additions and 2221 deletions

View File

@@ -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
}
}

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;
}
};
}
}

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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);
}
};
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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()) {

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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)));
}
}

View File

@@ -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;

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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);