mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-230679 Search Everywhere: Show all results in one group on All tab
GitOrigin-RevId: 0ea547d1c7fca47046ecca20bd2c9bd94700e713
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1c7303fd74
commit
4f7c69d4df
+2
-2
@@ -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.SearchEverywhereUI
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereUIBase
|
||||
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 SearchEverywhereUI } as SearchEverywhereUI
|
||||
return GuiRobotHolder.robot.finder().find { it is SearchEverywhereUIBase } as SearchEverywhereUIBase
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,7 +21,9 @@ public class JavaClassAndFileEqualityProvider implements SEResultsEqualityProvid
|
||||
}
|
||||
|
||||
if (isClassAndFile(newItemInfo, alreadyFoundItemInfo) && isSameFile(newElementPsi, alreadyFoundPsi)) {
|
||||
return newItemInfo.priority > alreadyFoundItemInfo.priority ? SEEqualElementsActionType.REPLACE : SEEqualElementsActionType.SKIP;
|
||||
return SearchEverywhereFoundElementInfo.COMPARATOR.compare(newItemInfo, alreadyFoundItemInfo) > 0
|
||||
? SEEqualElementsActionType.REPLACE
|
||||
: SEEqualElementsActionType.SKIP;
|
||||
}
|
||||
|
||||
return SEEqualElementsActionType.DO_NOTHING;
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
// 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.java.navigation
|
||||
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereContributor
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereManagerImpl
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereUI
|
||||
import com.intellij.ide.actions.searcheverywhere.*
|
||||
import com.intellij.ide.actions.searcheverywhere.mixed.SearchEverywhereUIMixedResults
|
||||
import com.intellij.openapi.application.Experiments
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
|
||||
import com.intellij.util.Processor
|
||||
import org.jetbrains.annotations.NotNull
|
||||
|
||||
import javax.swing.*
|
||||
|
||||
import static com.intellij.testFramework.PlatformTestUtil.waitForFuture
|
||||
|
||||
class SearchEverywhereTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
SearchEverywhereUI mySearchUI
|
||||
static final int SEARCH_TIMEOUT = 5000
|
||||
|
||||
SearchEverywhereUIBase mySearchUI
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
@@ -29,17 +36,127 @@ class SearchEverywhereTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
def ui = createTestUI([ChooseByNameTest.createClassContributor(project)])
|
||||
|
||||
def future = ui.findElementsForPattern("StrBuffer")
|
||||
assert waitForFuture(future, 5000) == [strBuffer]
|
||||
assert waitForFuture(future, SEARCH_TIMEOUT) == [strBuffer]
|
||||
|
||||
future = ui.findElementsForPattern("StringBuffer")
|
||||
assert waitForFuture(future, 5000) == [stringBuffer]
|
||||
assert waitForFuture(future, SEARCH_TIMEOUT) == [stringBuffer]
|
||||
}
|
||||
|
||||
private SearchEverywhereUI createTestUI(List<SearchEverywhereContributor<Object>> contributors) {
|
||||
void "test mixing classes and files"() {
|
||||
withMixingEnabled({
|
||||
def testClass = myFixture.addClass("class TestClass{}")
|
||||
def anotherTestClass = myFixture.addClass("class AnotherTestClass{}")
|
||||
def testFile = myFixture.addFileToProject("testClass.txt", "")
|
||||
|
||||
def ui = createTestUI([
|
||||
ChooseByNameTest.createClassContributor(project),
|
||||
ChooseByNameTest.createFileContributor(project)
|
||||
])
|
||||
|
||||
def future = ui.findElementsForPattern("TestClass")
|
||||
assert waitForFuture(future, SEARCH_TIMEOUT) == [testClass, testFile, anotherTestClass]
|
||||
|
||||
future = ui.findElementsForPattern("testClass")
|
||||
assert waitForFuture(future, SEARCH_TIMEOUT) == [testFile, testClass, anotherTestClass]
|
||||
|
||||
def testClass2 = myFixture.addClass("class testClass2{}")
|
||||
|
||||
future = ui.findElementsForPattern("testClass")
|
||||
assert waitForFuture(future, SEARCH_TIMEOUT) == [testClass2, testFile, testClass, anotherTestClass]
|
||||
})
|
||||
}
|
||||
|
||||
void "test mixing results from stub contributors"() {
|
||||
withMixingEnabled({
|
||||
def contributor1 = new StubContributor("contributor1", 1)
|
||||
def contributor2 = new StubContributor("contributor2", 2)
|
||||
def contributor3 = new StubContributor("contributor3", 3)
|
||||
|
||||
contributor1.addElements(["item1": 10, "item2": 12, "item3": 8])
|
||||
contributor2.addElements(["item3": 8, "item4": 10, "item5": 11, "item8": 15])
|
||||
contributor3.addElements(["item3": 8, "item5": 11, "item6": 10, "item7": 15])
|
||||
|
||||
def ui = createTestUI([contributor2, contributor1, contributor3])
|
||||
def future = ui.findElementsForPattern("ignored")
|
||||
assert waitForFuture(future, SEARCH_TIMEOUT) == ["item8", "item7", "item2", "item5", "item1", "item4", "item6", "item3"]
|
||||
})
|
||||
}
|
||||
|
||||
private SearchEverywhereUIBase createTestUI(List<SearchEverywhereContributor<Object>> contributors) {
|
||||
if (mySearchUI != null) Disposer.dispose(mySearchUI)
|
||||
|
||||
mySearchUI = new SearchEverywhereUI(project, contributors)
|
||||
def mixingEnabled = Experiments.getInstance().isFeatureEnabled("search.everywhere.mixed.results")
|
||||
mySearchUI = mixingEnabled ? new SearchEverywhereUIMixedResults(project, contributors)
|
||||
: new SearchEverywhereUI(project, contributors)
|
||||
mySearchUI.switchToContributor(SearchEverywhereManagerImpl.ALL_CONTRIBUTORS_GROUP_ID)
|
||||
return mySearchUI
|
||||
}
|
||||
|
||||
private static void withMixingEnabled(Runnable task) {
|
||||
Experiments.getInstance().setFeatureEnabled("search.everywhere.mixed.results", true)
|
||||
try {
|
||||
task.run()
|
||||
}
|
||||
finally {
|
||||
Experiments.getInstance().setFeatureEnabled("search.everywhere.mixed.results", false)
|
||||
}
|
||||
}
|
||||
|
||||
private static class StubContributor implements WeightedSearchEverywhereContributor<Object> {
|
||||
|
||||
private final String myId
|
||||
private final int mySortWeight
|
||||
private final Map<Object, Integer> myElements = new HashMap<>()
|
||||
|
||||
StubContributor(String id, int sortWeight) {
|
||||
myId = id
|
||||
mySortWeight = sortWeight
|
||||
}
|
||||
|
||||
void addElements(Map<Object, Integer> elements) {
|
||||
myElements.putAll(elements)
|
||||
}
|
||||
|
||||
@Override
|
||||
void fetchWeightedElements(@NotNull String pattern,
|
||||
@NotNull ProgressIndicator progressIndicator,
|
||||
@NotNull Processor<? super FoundItemDescriptor<Object>> consumer) {
|
||||
myElements.forEach({element, weight -> consumer.process(new FoundItemDescriptor(element, weight))})
|
||||
}
|
||||
|
||||
@Override
|
||||
String getSearchProviderId() {
|
||||
return myId
|
||||
}
|
||||
|
||||
@Override
|
||||
String getGroupName() {
|
||||
return myId
|
||||
}
|
||||
|
||||
@Override
|
||||
int getSortWeight() {
|
||||
return mySortWeight
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean showInFindResults() {
|
||||
return false
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean processSelectedItem(@NotNull Object selected, int modifiers, @NotNull String searchText) {
|
||||
return false
|
||||
}
|
||||
|
||||
@Override
|
||||
ListCellRenderer<? super Object> getElementsRenderer() {
|
||||
return new DefaultListCellRenderer()
|
||||
}
|
||||
|
||||
@Override
|
||||
Object getDataForItem(@NotNull Object element, @NotNull String dataId) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.*;
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereManager;
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereManagerImpl;
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereUI;
|
||||
import com.intellij.ide.actions.searcheverywhere.SearchEverywhereUIBase;
|
||||
import com.intellij.ide.actions.searcheverywhere.statistics.SearchEverywhereUsageTriggerCollector;
|
||||
import com.intellij.ide.lightEdit.LightEdit;
|
||||
import com.intellij.ide.structureView.StructureView;
|
||||
@@ -983,7 +983,7 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA
|
||||
super(false, "SearchEveryWhereHistory");
|
||||
JTextField editor = getTextEditor();
|
||||
editor.setOpaque(false);
|
||||
editor.putClientProperty(SearchEverywhereUI.SEARCH_EVERYWHERE_SEARCH_FILED_KEY, true);
|
||||
editor.putClientProperty(SearchEverywhereUIBase.SEARCH_EVERYWHERE_SEARCH_FILED_KEY, true);
|
||||
editor.putClientProperty("JTextField.Search.noBorderRing", Boolean.TRUE);
|
||||
|
||||
if (StartupUiUtil.isUnderDarcula()) {
|
||||
|
||||
+1
-1
@@ -219,7 +219,7 @@ public abstract class AbstractGotoSEContributor implements WeightedSearchEverywh
|
||||
}
|
||||
});
|
||||
}
|
||||
result.add(new SearchEverywhereUI.FiltersAction(filter, onChanged));
|
||||
result.add(new SearchEverywhereUIBase.FiltersAction(filter, onChanged));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -21,7 +21,9 @@ public class PsiElementsEqualityProvider implements SEResultsEqualityProvider {
|
||||
}
|
||||
|
||||
if (Objects.equals(newElementPsi, alreadyFoundPsi)) {
|
||||
return newItemInfo.priority > alreadyFoundItemInfo.priority ? SEEqualElementsActionType.REPLACE : SEEqualElementsActionType.SKIP;
|
||||
return SearchEverywhereFoundElementInfo.COMPARATOR.compare(newItemInfo, alreadyFoundItemInfo) > 0
|
||||
? SEEqualElementsActionType.REPLACE
|
||||
: SEEqualElementsActionType.SKIP;
|
||||
}
|
||||
|
||||
return SEEqualElementsActionType.DO_NOTHING;
|
||||
|
||||
+9
@@ -1,6 +1,8 @@
|
||||
// Copyright 2000-2019 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 java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Class containing info about found elements
|
||||
*/
|
||||
@@ -26,4 +28,11 @@ public class SearchEverywhereFoundElementInfo {
|
||||
public SearchEverywhereContributor<?> getContributor() {
|
||||
return contributor;
|
||||
}
|
||||
|
||||
public static final Comparator<SearchEverywhereFoundElementInfo> COMPARATOR = (o1, o2) -> {
|
||||
int res = Integer.compare(o1.priority, o2.priority);
|
||||
if (res != 0) return res;
|
||||
|
||||
return -Integer.compare(o1.contributor.getSortWeight(), o2.contributor.getSortWeight());
|
||||
};
|
||||
}
|
||||
|
||||
+13
-8
@@ -2,12 +2,15 @@
|
||||
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.openapi.actionSystem.ActionManager;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.KeyboardShortcut;
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys;
|
||||
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;
|
||||
@@ -47,7 +50,7 @@ public class SearchEverywhereManagerImpl implements SearchEverywhereManager {
|
||||
private final Project myProject;
|
||||
|
||||
private JBPopup myBalloon;
|
||||
private SearchEverywhereUI mySearchEverywhereUI;
|
||||
private SearchEverywhereUIBase mySearchEverywhereUI;
|
||||
private Dimension myBalloonFullSize;
|
||||
|
||||
private final SearchHistoryList myHistoryList = new SearchHistoryList();
|
||||
@@ -144,7 +147,7 @@ public class SearchEverywhereManagerImpl implements SearchEverywhereManager {
|
||||
myBalloonFullSize = null;
|
||||
});
|
||||
|
||||
if (mySearchEverywhereUI.getViewType() == SearchEverywhereUI.ViewType.SHORT) {
|
||||
if (mySearchEverywhereUI.getViewType() == BigPopupUI.ViewType.SHORT) {
|
||||
myBalloonFullSize = WindowStateService.getInstance(myProject).getSize(LOCATION_SETTINGS_KEY);
|
||||
Dimension prefSize = mySearchEverywhereUI.getPreferredSize();
|
||||
myBalloon.setSize(prefSize);
|
||||
@@ -156,7 +159,7 @@ public class SearchEverywhereManagerImpl implements SearchEverywhereManager {
|
||||
Point savedLocation = WindowStateService.getInstance(myProject).getLocation(LOCATION_SETTINGS_KEY);
|
||||
|
||||
//for first show and short mode popup should be shifted to the top screen half
|
||||
if (savedLocation == null && mySearchEverywhereUI.getViewType() == SearchEverywhereUI.ViewType.SHORT) {
|
||||
if (savedLocation == null && mySearchEverywhereUI.getViewType() == BigPopupUI.ViewType.SHORT) {
|
||||
Window window = project != null
|
||||
? WindowManager.getInstance().suggestParentWindow(project)
|
||||
: KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
|
||||
@@ -226,9 +229,11 @@ public class SearchEverywhereManagerImpl implements SearchEverywhereManager {
|
||||
myEverywhere = everywhere;
|
||||
}
|
||||
|
||||
private SearchEverywhereUI createView(Project project,
|
||||
private SearchEverywhereUIBase createView(Project project,
|
||||
List<? extends SearchEverywhereContributor<?>> contributors) {
|
||||
SearchEverywhereUI view = new SearchEverywhereUI(project, contributors, myTabsShortcutsMap::get);
|
||||
SearchEverywhereUIBase view = Experiments.getInstance().isFeatureEnabled("search.everywhere.mixed.results")
|
||||
? new SearchEverywhereUIMixedResults(project, contributors, myTabsShortcutsMap::get)
|
||||
: new SearchEverywhereUI(project, contributors, myTabsShortcutsMap::get);
|
||||
|
||||
view.setSearchFinishedHandler(() -> {
|
||||
if (isShown()) {
|
||||
@@ -248,7 +253,7 @@ public class SearchEverywhereManagerImpl implements SearchEverywhereManager {
|
||||
JBInsets.addTo(minSize, myBalloon.getContent().getInsets());
|
||||
myBalloon.setMinimumSize(minSize);
|
||||
|
||||
if (viewType == SearchEverywhereUI.ViewType.SHORT) {
|
||||
if (viewType == BigPopupUI.ViewType.SHORT) {
|
||||
myBalloonFullSize = myBalloon.getSize();
|
||||
JBInsets.removeFrom(myBalloonFullSize, myBalloon.getContent().getInsets());
|
||||
myBalloon.pack(false, true);
|
||||
@@ -294,12 +299,12 @@ public class SearchEverywhereManagerImpl implements SearchEverywhereManager {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
Object getPrevSelection(String contributorID) {
|
||||
public Object getPrevSelection(String contributorID) {
|
||||
return myPrevSelections.remove(contributorID);
|
||||
}
|
||||
|
||||
private void saveSize() {
|
||||
if (mySearchEverywhereUI.getViewType() == SearchEverywhereUI.ViewType.SHORT) {
|
||||
if (mySearchEverywhereUI.getViewType() == BigPopupUI.ViewType.SHORT) {
|
||||
WindowStateService.getInstance(myProject).putSize(LOCATION_SETTINGS_KEY, myBalloonFullSize);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-52
@@ -86,11 +86,9 @@ import java.util.stream.IntStream;
|
||||
* @author Konstantin Bulenkov
|
||||
* @author Mikhail.Sokolov
|
||||
*/
|
||||
public final class SearchEverywhereUI extends BigPopupUI implements DataProvider, QuickSearchComponent {
|
||||
public final class SearchEverywhereUI extends SearchEverywhereUIBase implements DataProvider, QuickSearchComponent {
|
||||
private static final Logger LOG = Logger.getInstance(SearchEverywhereUI.class);
|
||||
|
||||
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;
|
||||
@@ -1483,55 +1481,6 @@ public final class SearchEverywhereUI extends BigPopupUI implements DataProvider
|
||||
}
|
||||
}
|
||||
|
||||
static class FiltersAction extends ShowFilterAction {
|
||||
final PersistentSearchEverywhereContributorFilter<?> filter;
|
||||
final Runnable rebuildRunnable;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private class CompleteCommandAction extends DumbAwareAction {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// 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 javax.swing.*;
|
||||
|
||||
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 switchToContributor(@NotNull String contributorID);
|
||||
|
||||
public abstract String getSelectedContributorID();
|
||||
|
||||
@Nullable
|
||||
public abstract Object getSelectionIdentity();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -19,7 +19,7 @@ class ThrottlingListenerWrapper implements MultiThreadSearcher.Listener {
|
||||
|
||||
public final int myThrottlingDelay;
|
||||
|
||||
private final MultiThreadSearcher.Listener myDelegateListener;
|
||||
private final SESearcher.Listener myDelegateListener;
|
||||
private final Executor myDelegateExecutor;
|
||||
|
||||
private final Buffer myBuffer = new Buffer();
|
||||
|
||||
+3
-1
@@ -11,7 +11,9 @@ public class TrivialElementsEqualityProvider implements SEResultsEqualityProvide
|
||||
@Override
|
||||
public SEEqualElementsActionType compareItems(@NotNull SearchEverywhereFoundElementInfo newItem, @NotNull SearchEverywhereFoundElementInfo alreadyFoundItem) {
|
||||
if (Objects.equals(newItem.getElement(), alreadyFoundItem.getElement())) {
|
||||
return newItem.getPriority() > alreadyFoundItem.getPriority() ? SEEqualElementsActionType.REPLACE : SEEqualElementsActionType.SKIP;
|
||||
return SearchEverywhereFoundElementInfo.COMPARATOR.compare(newItem, alreadyFoundItem) > 0
|
||||
? SEEqualElementsActionType.REPLACE
|
||||
: SEEqualElementsActionType.SKIP;
|
||||
}
|
||||
return SEEqualElementsActionType.DO_NOTHING;
|
||||
}
|
||||
|
||||
+405
@@ -0,0 +1,405 @@
|
||||
// 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.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;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
|
||||
import com.intellij.util.ConcurrencyUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.intellij.ide.actions.searcheverywhere.SEResultsEqualityProvider.SEEqualElementsActionType.REPLACE;
|
||||
import static com.intellij.ide.actions.searcheverywhere.SEResultsEqualityProvider.SEEqualElementsActionType.SKIP;
|
||||
|
||||
/**
|
||||
* @author msokolov
|
||||
*/
|
||||
class MultiThreadSearcher implements SESearcher {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(MultiThreadSearcher.class);
|
||||
|
||||
@NotNull private final Listener myListener;
|
||||
@NotNull private final Executor myNotificationExecutor;
|
||||
@NotNull private final SEResultsEqualityProvider myEqualityProvider;
|
||||
|
||||
/**
|
||||
* Creates MultiThreadSearcher with search results {@link Listener} and specifies executor which going to be used to call listener methods.
|
||||
* Use this constructor when you for example need to receive listener events only in AWT thread
|
||||
* @param listener {@link Listener} to get notifications about searching process
|
||||
* @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) {
|
||||
myListener = listener;
|
||||
myNotificationExecutor = notificationExecutor;
|
||||
myEqualityProvider = SEResultsEqualityProvider.composite(equalityProviders);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgressIndicator search(@NotNull Map<? extends SearchEverywhereContributor<?>, Integer> contributorsAndLimits,
|
||||
@NotNull String pattern) {
|
||||
LOG.debug("Search started for pattern [", pattern, "]");
|
||||
|
||||
Collection<? extends SearchEverywhereContributor<?>> contributors = contributorsAndLimits.keySet();
|
||||
if (pattern.isEmpty()) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
contributors = Collections.emptySet(); //empty search string is not allowed for tests
|
||||
}
|
||||
else {
|
||||
contributors = ContainerUtil.filter(contributors, contributor -> contributor.isEmptyPatternSupported());
|
||||
}
|
||||
}
|
||||
|
||||
Function<ProgressIndicator, ResultsAccumulator> accumulatorSupplier = indicator ->
|
||||
new ResultsAccumulator(contributorsAndLimits, myEqualityProvider, myListener, myNotificationExecutor, indicator);
|
||||
|
||||
return performSearch(contributors, pattern, accumulatorSupplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgressIndicator findMoreItems(@NotNull Map<? extends SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>> alreadyFound,
|
||||
@NotNull Map<? extends SearchEverywhereContributor<?>, Integer> contributorsAndLimits,
|
||||
@NotNull String pattern) {
|
||||
Function<ProgressIndicator, ResultsAccumulator> accumulatorSupplier = indicator ->
|
||||
new ResultsAccumulator(alreadyFound, contributorsAndLimits, myEqualityProvider, myListener, myNotificationExecutor, indicator);
|
||||
|
||||
return performSearch(contributorsAndLimits.keySet(), pattern, accumulatorSupplier);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static ProgressIndicator performSearch(@NotNull Collection<? extends SearchEverywhereContributor<?>> contributors,
|
||||
@NotNull String pattern,
|
||||
@NotNull Function<ProgressIndicator, ResultsAccumulator> accumulatorSupplier) {
|
||||
ProgressIndicator indicator;
|
||||
ResultsAccumulator accumulator;
|
||||
if (!contributors.isEmpty()) {
|
||||
CountDownLatch latch = new CountDownLatch(contributors.size());
|
||||
ProgressIndicatorWithCancelListener indicatorWithCancelListener = new ProgressIndicatorWithCancelListener();
|
||||
accumulator = accumulatorSupplier.apply(indicatorWithCancelListener);
|
||||
|
||||
for (SearchEverywhereContributor<?> contributor : contributors) {
|
||||
Runnable task = createSearchTask(pattern, accumulator,
|
||||
indicatorWithCancelListener, contributor, () -> latch.countDown());
|
||||
ApplicationManager.getApplication().executeOnPooledThread(task);
|
||||
}
|
||||
|
||||
Runnable finisherTask = createFinisherTask(latch, accumulator, indicatorWithCancelListener);
|
||||
Future<?> finisherFeature = ApplicationManager.getApplication().executeOnPooledThread(finisherTask);
|
||||
indicatorWithCancelListener.setCancelCallback(() -> {
|
||||
accumulator.stop();
|
||||
finisherFeature.cancel(true);
|
||||
});
|
||||
indicator = indicatorWithCancelListener;
|
||||
}
|
||||
else {
|
||||
indicator = new ProgressIndicatorBase();
|
||||
accumulator = accumulatorSupplier.apply(indicator);
|
||||
}
|
||||
|
||||
indicator.start();
|
||||
if (contributors.isEmpty()) {
|
||||
indicator.stop();
|
||||
accumulator.searchFinished();
|
||||
}
|
||||
return indicator;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Runnable createSearchTask(String pattern,
|
||||
ResultsAccumulator accumulator,
|
||||
ProgressIndicator indicator,
|
||||
SearchEverywhereContributor<?> contributor,
|
||||
Runnable finalCallback) {
|
||||
//noinspection unchecked
|
||||
ContributorSearchTask<?> task = new ContributorSearchTask<>(
|
||||
(SearchEverywhereContributor<Object>)contributor, pattern,
|
||||
accumulator, indicator, finalCallback);
|
||||
return ConcurrencyUtil.underThreadNameRunnable("SE-SearchTask-" + contributor.getSearchProviderId(), task);
|
||||
}
|
||||
|
||||
private static Runnable createFinisherTask(CountDownLatch latch, ResultsAccumulator accumulator, ProgressIndicator indicator) {
|
||||
return ConcurrencyUtil.underThreadNameRunnable("SE-FinisherTask", () -> {
|
||||
try {
|
||||
latch.await();
|
||||
if (!indicator.isCanceled()) {
|
||||
accumulator.searchFinished();
|
||||
}
|
||||
indicator.stop();
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
LOG.debug("Finisher interrupted before search process is finished");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class ContributorSearchTask<Item> implements Runnable {
|
||||
|
||||
private final ResultsAccumulator myAccumulator;
|
||||
private final Runnable finishCallback;
|
||||
|
||||
private final SearchEverywhereContributor<Item> myContributor;
|
||||
private final String myPattern;
|
||||
private final ProgressIndicator myIndicator;
|
||||
|
||||
private ContributorSearchTask(SearchEverywhereContributor<Item> contributor,
|
||||
String pattern,
|
||||
ResultsAccumulator accumulator,
|
||||
ProgressIndicator indicator,
|
||||
Runnable callback) {
|
||||
myContributor = contributor;
|
||||
myPattern = pattern;
|
||||
myAccumulator = accumulator;
|
||||
myIndicator = indicator;
|
||||
finishCallback = callback;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.debug("Search task started for contributor ", myContributor);
|
||||
try {
|
||||
boolean repeat;
|
||||
do {
|
||||
ProgressIndicator wrapperIndicator = new SensitiveProgressWrapper(myIndicator);
|
||||
try {
|
||||
Runnable runnable = (myContributor instanceof WeightedSearchEverywhereContributor)
|
||||
? () -> ((WeightedSearchEverywhereContributor<Item>)myContributor).fetchWeightedElements(myPattern, wrapperIndicator,
|
||||
descriptor -> processFoundItem(descriptor.getItem(), descriptor.getWeight(), wrapperIndicator))
|
||||
: () -> myContributor.fetchElements(myPattern, wrapperIndicator,
|
||||
element -> {
|
||||
int priority = myContributor.getElementPriority(Objects.requireNonNull(element), myPattern);
|
||||
return processFoundItem(element, priority, wrapperIndicator);
|
||||
});
|
||||
|
||||
ProgressManager.getInstance().runProcess(runnable, wrapperIndicator);
|
||||
}
|
||||
catch (ProcessCanceledException ignore) {}
|
||||
repeat = !myIndicator.isCanceled() && wrapperIndicator.isCanceled();
|
||||
}
|
||||
while (repeat);
|
||||
|
||||
if (myIndicator.isCanceled()) {
|
||||
return;
|
||||
}
|
||||
myAccumulator.contributorFinished(myContributor);
|
||||
}
|
||||
finally {
|
||||
finishCallback.run();
|
||||
}
|
||||
LOG.debug("Search task finished for contributor ", myContributor);
|
||||
}
|
||||
|
||||
private boolean processFoundItem(Item element, int priority, ProgressIndicator wrapperIndicator) {
|
||||
try {
|
||||
if (element == null) {
|
||||
LOG.debug("Skip null element");
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean added = myAccumulator.addElement(element, myContributor, priority, wrapperIndicator);
|
||||
if (!added) {
|
||||
myAccumulator.setContributorHasMore(myContributor, true);
|
||||
}
|
||||
return added;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
LOG.warn("Search task was interrupted");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ResultsAccumulator {
|
||||
|
||||
private final Map<? extends SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>> mySections;
|
||||
private final Listener myListener;
|
||||
private final Executor myNotificationExecutor;
|
||||
private final SEResultsEqualityProvider myEqualityProvider;
|
||||
private final ProgressIndicator myProgressIndicator;
|
||||
private final Map<? extends SearchEverywhereContributor<?>, Integer> sectionsLimits;
|
||||
private final Map<? extends SearchEverywhereContributor<?>, Condition> conditionsMap;
|
||||
private final Map<SearchEverywhereContributor<?>, Boolean> hasMoreMap = new ConcurrentHashMap<>();
|
||||
private final Set<SearchEverywhereContributor<?>> finishedContributorsSet = ContainerUtil.newConcurrentSet();
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private volatile boolean mySearchFinished = false;
|
||||
|
||||
ResultsAccumulator(Map<? extends SearchEverywhereContributor<?>, Integer> contributorsAndLimits,
|
||||
SEResultsEqualityProvider equalityProvider, Listener listener, Executor notificationExecutor,
|
||||
ProgressIndicator progressIndicator) {
|
||||
this(contributorsAndLimits.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey(), entry -> new ArrayList<>(entry.getValue()))),
|
||||
contributorsAndLimits, equalityProvider, listener, notificationExecutor, progressIndicator);
|
||||
}
|
||||
|
||||
ResultsAccumulator(Map<? extends SearchEverywhereContributor<?>, Collection<SearchEverywhereFoundElementInfo>> alreadyFound,
|
||||
Map<? extends SearchEverywhereContributor<?>, Integer> contributorsAndLimits,
|
||||
SEResultsEqualityProvider equalityProvider,
|
||||
Listener listener,
|
||||
Executor notificationExecutor,
|
||||
ProgressIndicator progressIndicator) {
|
||||
mySections = alreadyFound;
|
||||
myEqualityProvider = equalityProvider;
|
||||
myListener = listener;
|
||||
myNotificationExecutor = notificationExecutor;
|
||||
myProgressIndicator = progressIndicator;
|
||||
|
||||
sectionsLimits = new HashMap<>(contributorsAndLimits);
|
||||
conditionsMap = contributorsAndLimits.keySet().stream().collect(Collectors.toMap(Function.identity(), c -> lock.newCondition()));
|
||||
}
|
||||
|
||||
public void setContributorHasMore(SearchEverywhereContributor<?> contributor, boolean hasMore) {
|
||||
hasMoreMap.put(contributor, hasMore);
|
||||
}
|
||||
|
||||
public boolean addElement(Object element, SearchEverywhereContributor<?> contributor, int priority, ProgressIndicator indicator) throws InterruptedException {
|
||||
SearchEverywhereFoundElementInfo newElementInfo = new SearchEverywhereFoundElementInfo(element, priority, contributor);
|
||||
Condition condition = conditionsMap.get(contributor);
|
||||
Collection<SearchEverywhereFoundElementInfo> section = mySections.get(contributor);
|
||||
int limit = sectionsLimits.get(contributor);
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
while (section.size() >= limit && !mySearchFinished) {
|
||||
indicator.checkCanceled();
|
||||
condition.await(100, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
if (mySearchFinished) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Map<SEResultsEqualityProvider.SEEqualElementsActionType, Collection<SearchEverywhereFoundElementInfo>> otherElementsMap = getActionsWithOtherElements(newElementInfo);
|
||||
if (otherElementsMap.get(REPLACE).isEmpty() && !otherElementsMap.get(SKIP).isEmpty()) {
|
||||
LOG.debug(String.format("Element %s for contributor %s was skipped", element.toString(), contributor.getSearchProviderId()));
|
||||
return true;
|
||||
}
|
||||
|
||||
section.add(newElementInfo);
|
||||
runInNotificationExecutor(() -> myListener.elementsAdded(Collections.singletonList(newElementInfo)));
|
||||
|
||||
List<SearchEverywhereFoundElementInfo> toRemove = new ArrayList<>(otherElementsMap.get(REPLACE));
|
||||
toRemove.forEach(info -> {
|
||||
Collection<SearchEverywhereFoundElementInfo> list = mySections.get(info.getContributor());
|
||||
Condition listCondition = conditionsMap.get(info.getContributor());
|
||||
list.remove(info);
|
||||
LOG.debug(String.format("Element %s for contributor %s is removed", info.getElement().toString(), info.getContributor().getSearchProviderId()));
|
||||
listCondition.signal();
|
||||
});
|
||||
runInNotificationExecutor(() -> myListener.elementsRemoved(toRemove));
|
||||
|
||||
if (section.size() >= limit) {
|
||||
stopSearchIfNeeded();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void contributorFinished(SearchEverywhereContributor<?> contributor) {
|
||||
lock.lock();
|
||||
try {
|
||||
finishedContributorsSet.add(contributor);
|
||||
stopSearchIfNeeded();
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void searchFinished() {
|
||||
runInNotificationExecutor(() -> myListener.searchFinished(hasMoreMap));
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
lock.lock();
|
||||
try {
|
||||
mySearchFinished = true;
|
||||
conditionsMap.values().forEach(Condition::signalAll);
|
||||
}
|
||||
finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* could be used only when current thread owns {@link #lock}
|
||||
*/
|
||||
private void stopSearchIfNeeded() {
|
||||
if (mySections.keySet().stream().allMatch(contributor -> isContributorFinished(contributor))) {
|
||||
mySearchFinished = true;
|
||||
conditionsMap.values().forEach(Condition::signalAll);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isContributorFinished(SearchEverywhereContributor<?> contributor) {
|
||||
if (finishedContributorsSet.contains(contributor)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Integer limit = sectionsLimits.get(contributor);
|
||||
return limit == null || mySections.get(contributor).size() >= limit;
|
||||
}
|
||||
|
||||
private Map<SEResultsEqualityProvider.SEEqualElementsActionType, Collection<SearchEverywhereFoundElementInfo>> getActionsWithOtherElements(
|
||||
SearchEverywhereFoundElementInfo newElement) {
|
||||
Map<SEResultsEqualityProvider.SEEqualElementsActionType, Collection<SearchEverywhereFoundElementInfo>> res = new EnumMap<>(
|
||||
SEResultsEqualityProvider.SEEqualElementsActionType.class);
|
||||
res.put(REPLACE, new ArrayList<>());
|
||||
res.put(SKIP, new ArrayList<>());
|
||||
mySections.values()
|
||||
.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.forEach(info -> {
|
||||
SEResultsEqualityProvider.SEEqualElementsActionType action = myEqualityProvider.compareItems(newElement, info);
|
||||
if (action != SEResultsEqualityProvider.SEEqualElementsActionType.DO_NOTHING) {
|
||||
res.get(action).add(info);
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private void runInNotificationExecutor(Runnable runnable) {
|
||||
myNotificationExecutor.execute(() -> {
|
||||
if (!myProgressIndicator.isCanceled()) {
|
||||
runnable.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static class ProgressIndicatorWithCancelListener extends ProgressIndicatorBase {
|
||||
|
||||
private volatile Runnable cancelCallback = () -> {};
|
||||
|
||||
private void setCancelCallback(Runnable cancelCallback) {
|
||||
this.cancelCallback = cancelCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRunningChange() {
|
||||
if (isCanceled()) {
|
||||
cancelCallback.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
// 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;
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
+1534
File diff suppressed because it is too large
Load Diff
+142
@@ -0,0 +1,142 @@
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
// 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.application.Experiments
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.testFramework.fixtures.BasePlatformTestCase
|
||||
import com.intellij.util.Alarm
|
||||
import com.intellij.util.Processor
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import org.junit.Assert
|
||||
|
||||
import javax.swing.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Phaser
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
class MixingMultiThreadSearchTest extends BasePlatformTestCase {
|
||||
|
||||
private static final String MORE_ITEM = "...MORE"
|
||||
private static final Collection<SEResultsEqualityProvider> ourEqualityProviders = Collections.singleton(new TrivialElementsEqualityProvider())
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp()
|
||||
Experiments.getInstance().setFeatureEnabled("search.everywhere.mixed.results", true)
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown()
|
||||
Experiments.getInstance().setFeatureEnabled("search.everywhere.mixed.results", false)
|
||||
}
|
||||
|
||||
void "test simple without collisions"() {
|
||||
def contributors = [
|
||||
(createTestContributor("test1", 0, "item1_1", "item1_2", "item1_3", "item1_4", "item1_5", "item1_6", "item1_7", "item1_8", "item1_9", "item1_10", "item1_11", "item1_12", "item1_13", "item1_14", "item1_15")) : 12,
|
||||
(createTestContributor("test2", 0, "item2_1", "item2_2", "item2_3", "item2_4", "item2_5", "item2_6", "item2_7", "item2_8", "item2_9", "item2_10", "item2_11", "item2_12")) : 10,
|
||||
(createTestContributor("test3", 0, "item3_1", "item3_2", "item3_3", "item3_4", "item3_5", "item3_6", "item3_7", "item3_8")) : 10,
|
||||
(createTestContributor("test4", 0, "item4_1", "item4_2", "item4_3", "item4_4", "item4_5", "item4_6", "item4_7", "item4_8", "item4_9", "item4_10", "item4_11", "item4_12", "item4_13")) : 11,
|
||||
(createTestContributor("test5", 0)) : 10,
|
||||
(createTestContributor("test6", 0, "item6_1", "item6_2", "item6_3", "item6_4", "item6_5", "item6_6", "item6_7", "item6_8", "item6_9", "item6_10", "item6_11", "item6_12", "item6_13")) : 10,
|
||||
(createTestContributor("test7", 0, "item7_1", "item7_2", "item7_3", "item7_4", "item7_5", "item7_6", "item7_7", "item7_8", "item7_9", "item7_10")) : 10,
|
||||
(createTestContributor("test8", 0, "item8_1", "item8_2", "item8_3", "item8_4", "item8_5")) : 10,
|
||||
(createTestContributor("test9", 0, "item9_1", "item9_2", "item9_3", "item9_4", "item9_5")) : 3,
|
||||
(createTestContributor("test10", 0, "item10_1", "item10_2", "item10_3", "item10_4", "item10_5")) : 5
|
||||
]
|
||||
def results = [
|
||||
"test1" : ["item1_1", "item1_2", "item1_3", "item1_4", "item1_5", "item1_6", "item1_7", "item1_8", "item1_9", "item1_10", "item1_11", "item1_12", MORE_ITEM],
|
||||
"test2" : ["item2_1", "item2_2", "item2_3", "item2_4", "item2_5", "item2_6", "item2_7", "item2_8", "item2_9", "item2_10", MORE_ITEM],
|
||||
"test3" : ["item3_1", "item3_2", "item3_3", "item3_4", "item3_5", "item3_6", "item3_7", "item3_8"],
|
||||
"test4" : ["item4_1", "item4_2", "item4_3", "item4_4", "item4_5", "item4_6", "item4_7", "item4_8", "item4_9", "item4_10", "item4_11", MORE_ITEM],
|
||||
"test5" : [],
|
||||
"test6" : ["item6_1", "item6_2", "item6_3", "item6_4", "item6_5", "item6_6", "item6_7", "item6_8", "item6_9", "item6_10", MORE_ITEM],
|
||||
"test7" : ["item7_1", "item7_2", "item7_3", "item7_4", "item7_5", "item7_6", "item7_7", "item7_8", "item7_9", "item7_10"],
|
||||
"test8" : ["item8_1", "item8_2", "item8_3", "item8_4", "item8_5"],
|
||||
"test9" : ["item9_1", "item9_2", "item9_3", MORE_ITEM],
|
||||
"test10" : ["item10_1", "item10_2", "item10_3", "item10_4", "item10_5"]
|
||||
]
|
||||
|
||||
testScenario(new Scenario(contributors, results))
|
||||
}
|
||||
|
||||
void "test simple without MORE items"() {
|
||||
def contributors = [
|
||||
(createTestContributor("test1", 0, "item1_1", "item1_2", "item1_3", "item1_4", "item1_5", "item1_6", "item1_7", "item1_8", "item1_9", "item1_10")) : 10,
|
||||
(createTestContributor("test2", 0, "item2_1", "item2_2", "item2_3", "item2_4", "item2_5", "item2_6", "item2_7", "item2_8", "item2_9", "item2_10", "item2_11", "item2_12")) : 20,
|
||||
(createTestContributor("test3", 0, "item3_1", "item3_2", "item3_3", "item3_4", "item3_5", "item3_6", "item3_7", "item3_8")) : 10
|
||||
]
|
||||
def results = [
|
||||
"test1" : ["item1_1", "item1_2", "item1_3", "item1_4", "item1_5", "item1_6", "item1_7", "item1_8", "item1_9", "item1_10"],
|
||||
"test2" : ["item2_1", "item2_2", "item2_3", "item2_4", "item2_5", "item2_6", "item2_7", "item2_8", "item2_9", "item2_10", "item2_11", "item2_12"],
|
||||
"test3" : ["item3_1", "item3_2", "item3_3", "item3_4", "item3_5", "item3_6", "item3_7", "item3_8"]
|
||||
]
|
||||
|
||||
testScenario(new Scenario(contributors, results))
|
||||
}
|
||||
|
||||
void "test empty results"() {
|
||||
def contributors = [
|
||||
(createTestContributor("test1", 0)) : 10,
|
||||
(createTestContributor("test2", 0)) : 10,
|
||||
(createTestContributor("test3", 0)) : 10,
|
||||
(createTestContributor("test4", 0)) : 10,
|
||||
(createTestContributor("test5", 0)) : 10
|
||||
]
|
||||
def results = [
|
||||
"test1" : [],
|
||||
"test2" : [],
|
||||
"test3" : [],
|
||||
"test4" : [],
|
||||
"test5" : []
|
||||
]
|
||||
|
||||
testScenario(new Scenario(contributors, results))
|
||||
}
|
||||
|
||||
void "test one contributor"() {
|
||||
testScenario(new Scenario(
|
||||
[(createTestContributor("test1", 0, "item1_1", "item1_2", "item1_3", "item1_4", "item1_5", "item1_6", "item1_7", "item1_8", "item1_9", "item1_10", "item1_11", "item1_12", "item1_13", "item1_14", "item1_15")) : 10],
|
||||
["test1": ["item1_1", "item1_2", "item1_3", "item1_4", "item1_5", "item1_6", "item1_7", "item1_8", "item1_9", "item1_10", MORE_ITEM]])
|
||||
)
|
||||
}
|
||||
|
||||
void "test one contributor with no MORE item"() {
|
||||
testScenario(new Scenario(
|
||||
[(createTestContributor("test1", 0, "item1_1", "item1_2", "item1_3", "item1_4", "item1_5", "item1_6", "item1_7", "item1_8", "item1_9", "item1_10")) : 10],
|
||||
["test1": ["item1_1", "item1_2", "item1_3", "item1_4", "item1_5", "item1_6", "item1_7", "item1_8", "item1_9", "item1_10"]])
|
||||
)
|
||||
}
|
||||
|
||||
void "test one contributor with empty results"() {
|
||||
testScenario(new Scenario(
|
||||
[(createTestContributor("test1", 0)) : 10],
|
||||
["test1":[]])
|
||||
)
|
||||
}
|
||||
|
||||
void "test collisions scenario"() {
|
||||
def contributors = [
|
||||
(createTestContributor("test1", 0, "item1_1", "item1_2", "item1_3", "item1_4", "item1_5", "item1_6", "item1_7", "item1_8", "item1_9", "item1_10", "item1_11", "item1_12", "item1_13", "item1_14", "item1_15")) : 12,
|
||||
(createTestContributor("test2", 10, "item2_1", "item2_2", "duplicateItem1", "duplicateItem2", "item2_3", "item2_4", "item2_5", "item2_6", "item2_7", "item2_8", "item2_9", "item2_10", "item2_11", "item2_12")) : 10,
|
||||
(createTestContributor("test3", 8, "item3_1", "item3_2", "item3_3", "item3_4", "duplicateItem1", "item3_5", "item3_6", "item3_7", "item3_8", "duplicateItem2", "duplicateItem3")) : 10,
|
||||
(createTestContributor("test4", 15, "item4_1", "item4_2", "duplicateItem2", "duplicateItem3", "item4_3", "item4_4", "item4_5", "item4_6", "item4_7", "item4_8", "item4_9")) : 10,
|
||||
(createTestContributor("test6", 20, "item6_1", "item6_2", "item6_3", "item6_4", "duplicateItem3", "item6_5", "item6_6", "item6_7", "item6_8", "item6_9", "item6_10", "item6_11", "duplicateItem4", "item6_12", "item6_13")) : 10,
|
||||
(createTestContributor("test7", 5, "item7_1", "item7_2", "duplicateItem3", "item7_3", "item7_4", "item7_5", "item7_6", "item7_7", "item7_8", "item7_9", "item7_10")) : 10,
|
||||
(createTestContributor("test8", 10, "item8_1", "item8_2", "item8_3", "item8_4", "item8_5", "duplicateItem4")) : 10,
|
||||
(createTestContributor("test9", 15, "item9_1", "item9_2", "item9_3", "item9_4", "duplicateItem5", "duplicateItem6")) : 5,
|
||||
(createTestContributor("test10", 10, "item10_1", "item10_2", "item10_3", "item10_4", "duplicateItem5", "duplicateItem6")) : 5
|
||||
]
|
||||
def results = [
|
||||
"test1": ["item1_1", "item1_2", "item1_3", "item1_4", "item1_5", "item1_6", "item1_7", "item1_8", "item1_9", "item1_10", "item1_11", "item1_12", MORE_ITEM],
|
||||
"test2": ["item2_1", "item2_2", "duplicateItem1", "item2_3", "item2_4", "item2_5", "item2_6", "item2_7", "item2_8", "item2_9", MORE_ITEM],
|
||||
"test3": ["item3_1", "item3_2", "item3_3", "item3_4", "item3_5", "item3_6", "item3_7", "item3_8"],
|
||||
"test4": ["item4_1", "item4_2", "duplicateItem2", "item4_3", "item4_4", "item4_5", "item4_6", "item4_7", "item4_8", "item4_9"],
|
||||
"test6": ["item6_1", "item6_2", "item6_3", "item6_4", "duplicateItem3", "item6_5", "item6_6", "item6_7", "item6_8", "item6_9", MORE_ITEM],
|
||||
"test7": ["item7_1", "item7_2", "item7_3", "item7_4", "item7_5", "item7_6", "item7_7", "item7_8", "item7_9", "item7_10"],
|
||||
"test8": ["item8_1", "item8_2", "item8_3", "item8_4", "item8_5", "duplicateItem4"],
|
||||
"test9": ["item9_1", "item9_2", "item9_3", "item9_4", "duplicateItem5", MORE_ITEM],
|
||||
"test10": ["item10_1", "item10_2", "item10_3", "item10_4", "duplicateItem6"]
|
||||
]
|
||||
testScenario(new Scenario(contributors, results))
|
||||
}
|
||||
|
||||
void "test only collisions"() {
|
||||
def contributors = [
|
||||
(createTestContributor("test1", 10, "item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9", "item10", "item11", "item12")) : 5,
|
||||
(createTestContributor("test2", 9, "item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9", "item10", "item11", "item12")) : 5,
|
||||
(createTestContributor("test3", 8, "item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9", "item10", "item11", "item12")) : 5,
|
||||
(createTestContributor("test4", 7, "item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9", "item10", "item11", "item12")) : 5,
|
||||
(createTestContributor("test5", 6, "item1", "item2", "item3", "item4", "item5", "item6", "item7", "item8", "item9", "item10", "item11", "item12")) : 5
|
||||
]
|
||||
def results = [
|
||||
"test1" : ["item1", "item2", "item3", "item4", "item5", MORE_ITEM],
|
||||
"test2" : ["item6", "item7", "item8", "item9", "item10", MORE_ITEM],
|
||||
"test3" : ["item11", "item12"],
|
||||
"test4" : [],
|
||||
"test5" : []
|
||||
]
|
||||
testScenario(new Scenario(contributors, results))
|
||||
}
|
||||
|
||||
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)
|
||||
ProgressIndicator indicator = searcher.search(scenario.contributorsAndLimits, "tst")
|
||||
|
||||
try {
|
||||
collector.awaitFinish(1000)
|
||||
}
|
||||
catch (TimeoutException ignored) {
|
||||
Assert.fail("Search timeout exceeded")
|
||||
}
|
||||
catch (InterruptedException ignored) {
|
||||
}
|
||||
finally {
|
||||
indicator.cancel()
|
||||
}
|
||||
scenario.results.forEach({ contributorId, results ->
|
||||
List<String> values = collector.getContributorValues(contributorId)
|
||||
Assert.assertEquals(results, values)
|
||||
})
|
||||
collector.clear()
|
||||
}
|
||||
|
||||
private static SearchEverywhereContributor<Object> createTestContributor(String id, int fixedPriority, String... items) {
|
||||
return new SearchEverywhereContributor<Object>() {
|
||||
@NotNull
|
||||
@Override
|
||||
String getSearchProviderId() {
|
||||
return id
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
String getGroupName() {
|
||||
return id
|
||||
}
|
||||
|
||||
@Override
|
||||
int getSortWeight() {
|
||||
return 0
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean showInFindResults() {
|
||||
return false
|
||||
}
|
||||
|
||||
@Override
|
||||
int getElementPriority(@NotNull Object element, @NotNull String searchPattern) {
|
||||
return fixedPriority
|
||||
}
|
||||
|
||||
@Override
|
||||
void fetchElements(@NotNull String pattern,
|
||||
@NotNull ProgressIndicator progressIndicator,
|
||||
@NotNull Processor<? super Object> consumer) {
|
||||
boolean flag = true
|
||||
Iterator<String> iterator = Arrays.asList(items).iterator()
|
||||
while (flag && iterator.hasNext()) {
|
||||
String next = iterator.next()
|
||||
flag = consumer.process(next)
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean processSelectedItem(@NotNull Object selected, int modifiers, @NotNull String searchText) {
|
||||
return false
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
ListCellRenderer<? super Object> getElementsRenderer() {
|
||||
throw new UnsupportedOperationException()
|
||||
}
|
||||
|
||||
@Override
|
||||
Object getDataForItem(@NotNull Object element, @NotNull String dataId) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Scenario {
|
||||
private final Map<SearchEverywhereContributor<Object>, Integer> contributorsAndLimits
|
||||
private final Map<String, List<String>> results
|
||||
|
||||
Scenario(Map<SearchEverywhereContributor<Object>, Integer> contributorsAndLimits, Map<String, List<String>> results) {
|
||||
this.contributorsAndLimits = contributorsAndLimits
|
||||
this.results = results
|
||||
}
|
||||
}
|
||||
|
||||
static class SearchResultsCollector implements SESearcher.Listener {
|
||||
|
||||
private final Map<String, List<String>> myMap = new ConcurrentHashMap<>()
|
||||
private final AtomicBoolean myFinished = new AtomicBoolean(false)
|
||||
private final Phaser myPhaser = new Phaser(2)
|
||||
|
||||
List<String> getContributorValues(String contributorId) {
|
||||
List<String> values = myMap.get(contributorId)
|
||||
return values != null ? values : []
|
||||
}
|
||||
|
||||
void awaitFinish(long timeout) throws TimeoutException, InterruptedException {
|
||||
int phase = myPhaser.arrive()
|
||||
myPhaser.awaitAdvanceInterruptibly(phase, timeout, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
|
||||
void clear() {
|
||||
myMap.clear()
|
||||
myFinished.set(false)
|
||||
}
|
||||
|
||||
@Override
|
||||
void elementsAdded(@NotNull List<? extends SearchEverywhereFoundElementInfo> added) {
|
||||
added.forEach({ info ->
|
||||
List<String> list = myMap.computeIfAbsent(info.getContributor().getSearchProviderId(), { s -> new ArrayList<>() })
|
||||
list.add((String)info.getElement())
|
||||
})
|
||||
}
|
||||
|
||||
@Override
|
||||
void elementsRemoved(@NotNull List<? extends SearchEverywhereFoundElementInfo> removed) {
|
||||
removed.forEach({ info ->
|
||||
List<String> list = myMap.get(info.getContributor().getSearchProviderId())
|
||||
Assert.assertNotNull("Trying to remove object, that wasn't added", list)
|
||||
list.remove(info.getElement())
|
||||
})
|
||||
}
|
||||
|
||||
@Override
|
||||
void searchFinished(@NotNull Map<SearchEverywhereContributor<?>, Boolean> hasMoreContributors) {
|
||||
hasMoreContributors.entrySet()
|
||||
.stream()
|
||||
.filter({ entry -> entry.getValue() })
|
||||
.map({ entry -> entry.getKey() })
|
||||
.forEach({ contributor ->
|
||||
List<String> list = myMap.get(contributor.getSearchProviderId())
|
||||
Assert.assertNotNull("If section has MORE item it cannot be empty", list)
|
||||
list.add(MORE_ITEM)
|
||||
})
|
||||
|
||||
boolean set = myFinished.compareAndSet(false, true)
|
||||
Assert.assertTrue("More than one finish event", set)
|
||||
|
||||
myPhaser.arrive()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -855,6 +855,10 @@
|
||||
<description>Defines whether to show "What's new" notification after IDE update or not.</description>
|
||||
</experimentalFeature>
|
||||
|
||||
<experimentalFeature id="search.everywhere.mixed.results" internalFeature="true" percentOfUsers="0">
|
||||
<description>Use mixed list of results on All tab in Search Everywhere (instead of Files/Classes/Actions groups)</description>
|
||||
</experimentalFeature>
|
||||
|
||||
<rawEditorTypedHandler implementationClass="com.intellij.openapi.editor.impl.EditorFactoryImpl$MyRawTypedHandler"/>
|
||||
|
||||
<postStartupActivity implementation="com.intellij.diagnostic.AnalyzePendingSnapshotActivity"/>
|
||||
|
||||
@@ -165,6 +165,10 @@
|
||||
<keyboard-shortcut first-keystroke="meta 3"/>
|
||||
<keyboard-shortcut first-keystroke="meta alt U"/>
|
||||
</action>
|
||||
<action id="SearchEverywhere.NavigateToNextGroup">
|
||||
<keyboard-shortcut first-keystroke="PAGE_DOWN"/>
|
||||
<keyboard-shortcut first-keystroke="meta DOWN"/>
|
||||
</action>
|
||||
<action id="EditorToggleShowWhitespaces"/>
|
||||
<action id="EditorToggleUseSoftWraps"/>
|
||||
<action id="Refresh"/>
|
||||
@@ -181,5 +185,9 @@
|
||||
<keyboard-shortcut first-keystroke="meta control 4"/>
|
||||
<keyboard-shortcut first-keystroke="meta alt COMMA"/>
|
||||
</action>
|
||||
<action id="SearchEverywhere.NavigateToPrevGroup">
|
||||
<keyboard-shortcut first-keystroke="PAGE_UP"/>
|
||||
<keyboard-shortcut first-keystroke="meta UP"/>
|
||||
</action>
|
||||
|
||||
</keymap>
|
||||
Reference in New Issue
Block a user