SearchResultPanel (@NotNull Consumer<? super PluginsGroupComponent> selectionListener) { SearchUpDownPopupController marketplaceController = new SearchUpDownPopupController(mySearchTextField) { @Override protected @NotNull List<String> getAttributes() { List<String> attributes = new ArrayList<>(); attributes.add(SearchWords.TAG.getValue()); attributes.add(SearchWords.SORT_BY.getValue()); attributes.add(SearchWords.VENDOR.getValue()); if (!UpdateSettings.getInstance().getPluginHosts().isEmpty()) { attributes.add(SearchWords.REPOSITORY.getValue()); } attributes.add(SearchWords.STAFF_PICKS.getValue()); attributes.add(SearchWords.SUGGESTED.getValue()); if (getPluginsViewCustomizer() != NoOpPluginsViewCustomizer.INSTANCE) { attributes.add(SearchWords.INTERNAL.getValue()); } return attributes; } @Override protected @Nullable List<String> getValues(@NotNull String attribute) { SearchWords word = SearchWords.find(attribute); if (word == null) return null; return switch (word) { case TAG -> { if (myTagsSorted == null || myTagsSorted.isEmpty()) { Set<String> allTags = new HashSet<>(); for (PluginNode descriptor : CustomPluginRepositoryService.getInstance().getCustomRepositoryPlugins()) { List<String> tags = descriptor.getTags(); if (tags != null && !tags.isEmpty()) { allTags.addAll(tags); } } try { ProcessIOExecutorService.INSTANCE.submit(() -> { allTags.addAll(myMarketplaceRequests.getMarketplaceTagsSupplier().get()); }).get(); } catch (InterruptedException | ExecutionException e) { LOG.error("Error while getting tags from marketplace", e); } myTagsSorted = ContainerUtil.sorted(allTags, String::compareToIgnoreCase); } yield myTagsSorted; } case SORT_BY -> ContainerUtil.map( Arrays.asList(SortBy.DOWNLOADS, SortBy.NAME, SortBy.RATING, SortBy.UPDATE_DATE), sort -> sort.getQuery() ); case VENDOR -> { if (myVendorsSorted == null || myVendorsSorted.isEmpty()) { LinkedHashSet<String> vendors = new LinkedHashSet<>(); try { ProcessIOExecutorService.INSTANCE.submit(() -> { vendors.addAll(myMarketplaceRequests.getMarketplaceVendorsSupplier().get()); }).get(); } catch (InterruptedException | ExecutionException e) { LOG.error("Error while getting vendors from marketplace", e); } myVendorsSorted = new ArrayList<>(vendors); } yield myVendorsSorted; } case REPOSITORY -> UpdateSettings.getInstance().getPluginHosts(); case INTERNAL, SUGGESTED, STAFF_PICKS -> null; }; } @Override protected void showPopupForQuery() { showSearchPanel(mySearchTextField.getText()); } @Override protected void handleEnter() { if (!mySearchTextField.getText().isEmpty()) { handleTrigger("marketplace.suggest.popup.enter"); } } @Override protected void handlePopupListFirstSelection() { handleTrigger("marketplace.suggest.popup.select"); } private void handleTrigger(@NonNls String key) { if (myPopup != null && myPopup.type == SearchPopup.Type.SearchQuery) { FeatureUsageTracker.getInstance().triggerFeatureUsed(key); } } }; myMarketplaceSortByGroup = new DefaultActionGroup(); for (SortBy option : SortBy.getEntries()) { myMarketplaceSortByGroup.addAction(new MarketplaceSortByAction(option)); } myMarketplaceSortByAction = new LinkComponent() { @Override protected boolean isInClickableArea(Point pt) { return true; } @Override public AccessibleContext getAccessibleContext() { if (accessibleContext == null) { accessibleContext = new AccessibleLinkComponent(); } return accessibleContext; } protected class AccessibleLinkComponent extends AccessibleLinkLabel { @Override public AccessibleRole getAccessibleRole() { return AccessibleRole.COMBO_BOX; } } }; myMarketplaceSortByAction.setIcon(new Icon() { @Override public void paintIcon(Component c, Graphics g, int x, int y) { getIcon().paintIcon(c, g, x, y + 1); } @Override public int getIconWidth() { return getIcon().getIconWidth(); } @Override public int getIconHeight() { return getIcon().getIconHeight(); } private static @NotNull Icon getIcon() { return AllIcons.General.ButtonDropTriangle; } }); // TODO: icon myMarketplaceSortByAction.setPaintUnderline(false); myMarketplaceSortByAction.setIconTextGap(JBUIScale.scale(4)); myMarketplaceSortByAction.setHorizontalTextPosition(SwingConstants.LEFT); myMarketplaceSortByAction.setForeground(PluginsGroupComponent.SECTION_HEADER_FOREGROUND); //noinspection unchecked myMarketplaceSortByAction.setListener( (component, __) -> showRightBottomPopup(component.getParent().getParent(), IdeBundle.message("plugins.configurable.sort.by"), myMarketplaceSortByGroup), null); DumbAwareAction.create(event -> myMarketplaceSortByAction.doClick()) .registerCustomShortcutSet(KeyEvent.VK_DOWN, 0, myMarketplaceSortByAction); myMarketplaceSortByCallback = updateAction -> { MarketplaceSortByAction removeAction = null; MarketplaceSortByAction addAction = null; if (updateAction.myState) { for (AnAction action : myMarketplaceSortByGroup.getChildren(null)) { MarketplaceSortByAction sortByAction = (MarketplaceSortByAction)action; if (sortByAction != updateAction && sortByAction.myState) { sortByAction.myState = false; removeAction = sortByAction; break; } } addAction = updateAction; } else { if (updateAction.myOption == SortBy.RELEVANCE) { updateAction.myState = true; return; } for (AnAction action : myMarketplaceSortByGroup.getChildren(null)) { MarketplaceSortByAction sortByAction = (MarketplaceSortByAction)action; if (sortByAction.myOption == SortBy.RELEVANCE) { sortByAction.myState = true; break; } } removeAction = updateAction; } List<String> queries = new ArrayList<>(); new SearchQueryParser.Marketplace(mySearchTextField.getText()) { // FIXME: it's unused - why hasn't it been removed? @Override protected void addToSearchQuery(@NotNull String query) { queries.add(query); } @Override protected void handleAttribute(@NotNull String name, @NotNull String value) { queries.add(name + SearchQueryParser.wrapAttribute(value)); } }; if (removeAction != null) { String query = removeAction.getQuery(); if (query != null) { queries.remove(query); } } if (addAction != null) { String query = addAction.getQuery(); if (query != null) { queries.add(query); } } String query = StringUtil.join(queries, " "); mySearchTextField.setTextIgnoreEvents(query); if (query.isEmpty()) { myMarketplaceTab.hideSearchPanel(); } else { myMarketplaceTab.showSearchPanel(query); } }; MultiSelectionEventHandler eventHandler = new MultiSelectionEventHandler(); marketplaceController.setSearchResultEventHandler(eventHandler); PluginsGroupComponentWithProgress panel = new PluginsGroupComponentWithProgress(eventHandler) { @Override protected @NotNull ListPluginComponent createListComponent(@NotNull IdeaPluginDescriptor descriptor, @NotNull PluginsGroup group) { return new ListPluginComponent(myPluginModel, descriptor, group, mySearchListener, true); } }; panel.setSelectionListener(selectionListener); registerCopyProvider(panel); Project project = ProjectUtil.getActiveProject(); myMarketplaceSearchPanel = new SearchResultPanel(marketplaceController, panel, true, 0, 0) { @Override protected void handleQuery(@NotNull String query, @NotNull PluginsGroup result) { try { SearchQueryParser.Marketplace parser = new SearchQueryParser.Marketplace(query); if (parser.internal) { PluginsViewCustomizer.PluginsGroupDescriptor groupDescriptor = getPluginsViewCustomizer().getInternalPluginsGroupDescriptor(); if (groupDescriptor != null) { if (parser.searchQuery == null) { result.descriptors.addAll(groupDescriptor.getPlugins()); } else { for (IdeaPluginDescriptor pluginDescriptor : groupDescriptor.getPlugins()) { if (StringUtil.containsIgnoreCase(pluginDescriptor.getName(), parser.searchQuery)) { result.descriptors.add(pluginDescriptor); } } } ContainerUtil.removeDuplicates(result.descriptors); result.sortByName(); return; } } Map<String, List<PluginNode>> customRepositoriesMap = CustomPluginRepositoryService.getInstance().getCustomRepositoryPluginMap(); if (parser.suggested && project != null) { result.descriptors.addAll(PluginsAdvertiserStartupActivityKt.findSuggestedPlugins(project, customRepositoriesMap)); } else if (!parser.repositories.isEmpty()) { for (String repository : parser.repositories) { List<PluginNode> descriptors = customRepositoriesMap.get(repository); if (descriptors == null) { continue; } if (parser.searchQuery == null) { result.descriptors.addAll(descriptors); } else { for (PluginNode descriptor : descriptors) { if (StringUtil.containsIgnoreCase(descriptor.getName(), parser.searchQuery)) { result.descriptors.add(descriptor); } } } } ContainerUtil.removeDuplicates(result.descriptors); result.sortByName(); } else { List<PluginNode> pluginsFromMarketplace = myMarketplaceRequests.searchPlugins(parser.getUrlQuery(), 10000, true); // compare plugin versions between marketplace & custom repositories List<PluginNode> customPlugins = ContainerUtil.flatten(customRepositoriesMap.values()); Collection<PluginNode> plugins = RepositoryHelper.mergePluginsFromRepositories(pluginsFromMarketplace, customPlugins, false); result.descriptors.addAll(0, plugins); if (parser.searchQuery != null) { List<PluginNode> descriptors = ContainerUtil.filter(customPlugins, descriptor -> StringUtil.containsIgnoreCase(descriptor.getName(), parser.searchQuery)); result.descriptors.addAll(0, descriptors); } ContainerUtil.removeDuplicates(result.descriptors); if (!result.descriptors.isEmpty()) { String title = IdeBundle.message("plugin.manager.action.label.sort.by.1"); for (AnAction action : myMarketplaceSortByGroup.getChildren(null)) { MarketplaceSortByAction sortByAction = (MarketplaceSortByAction)action; sortByAction.setState(parser); if (sortByAction.myState) { title = IdeBundle.message("plugin.manager.action.label.sort.by", sortByAction.myOption.getPresentableNameSupplier().get()); } } myMarketplaceSortByAction.setText(title); result.addRightAction(myMarketplaceSortByAction); Collection<IdeaPluginDescriptor> updates = PluginUpdatesService.getUpdates(); if (!ContainerUtil.isEmpty(updates)) { myPostFillGroupCallback = () -> { applyUpdates(myPanel, updates); selectionListener.accept(myMarketplacePanel); }; } } } PluginManagerUsageCollector.performMarketplaceSearch(ProjectUtil.getActiveProject(), parser, result.descriptors); } catch (IOException e) { LOG.info(e); ApplicationManager.getApplication().invokeLater( () -> myPanel.getEmptyText() .setText(IdeBundle.message("plugins.configurable.search.result.not.loaded")) .appendSecondaryText( IdeBundle.message("plugins.configurable.check.internet"), StatusText.DEFAULT_ATTRIBUTES, null), ModalityState.any() ); } } }; return myMarketplaceSearchPanel; }