private void createInstalledTab() {
    myInstalledSearchGroup = new DefaultActionGroup();

    for (InstalledSearchOption option : InstalledSearchOption.values()) {
      myInstalledSearchGroup.add(new InstalledSearchOptionAction(option));
    }

    myInstalledTab = new PluginsTab() {
      @Override
      protected void createSearchTextField(int flyDelay) {
        super.createSearchTextField(flyDelay);

        JBTextField textField = mySearchTextField.getTextEditor();
        textField.putClientProperty("search.extension", ExtendableTextComponent.Extension
          .create(AllIcons.Actions.More, AllIcons.Actions.More, IdeBundle.message("plugins.configurable.search.options"), // TODO: icon
                  () -> showRightBottomPopup(textField, IdeBundle.message("plugins.configurable.show"), myInstalledSearchGroup)));
        textField.putClientProperty("JTextField.variant", null);
        textField.putClientProperty("JTextField.variant", "search");

        mySearchTextField.setHistoryPropertyName("InstalledPluginsSearchHistory");
      }

      @Override
      protected @NotNull PluginDetailsPageComponent createDetailsPanel(@NotNull LinkListener<Object> searchListener) {
        PluginDetailsPageComponent detailPanel = new PluginDetailsPageComponent(myPluginModel, searchListener, false);
        myPluginModel.addDetailPanel(detailPanel);
        return detailPanel;
      }

      @Override
      protected @NotNull JComponent createPluginsPanel(@NotNull Consumer<? super PluginsGroupComponent> selectionListener) {
        MultiSelectionEventHandler eventHandler = new MultiSelectionEventHandler();
        myInstalledPanel = new PluginsGroupComponent(eventHandler) {
          @Override
          protected @NotNull ListPluginComponent createListComponent(@NotNull IdeaPluginDescriptor descriptor,
                                                                     @NotNull PluginsGroup group) {
            return new ListPluginComponent(myPluginModel, descriptor, group, mySearchListener, false);
          }
        };

        myInstalledPanel.setSelectionListener(selectionListener);
        myInstalledPanel.getAccessibleContext().setAccessibleName(IdeBundle.message("plugin.manager.installed.panel.accessible.name"));
        registerCopyProvider(myInstalledPanel);

        //noinspection ConstantConditions
        ((SearchUpDownPopupController)myInstalledSearchPanel.controller).setEventHandler(eventHandler);

        try {
          PluginLogo.startBatchMode();

          PluginsGroup installing = new PluginsGroup(IdeBundle.message("plugins.configurable.installing"), PluginsGroupType.INSTALLING);
          installing.descriptors.addAll(MyPluginModel.getInstallingPlugins());
          if (!installing.descriptors.isEmpty()) {
            installing.sortByName();
            installing.titleWithCount();
            myInstalledPanel.addGroup(installing);
          }

          PluginsGroup downloaded = new PluginsGroup(IdeBundle.message("plugins.configurable.downloaded"), PluginsGroupType.INSTALLED);
          downloaded.descriptors.addAll(InstalledPluginsState.getInstance().getInstalledPlugins());

          Map<Boolean, List<IdeaPluginDescriptorImpl>> visiblePlugins = PluginManager
            .getVisiblePlugins(RegistryManager.getInstance().is("plugins.show.implementation.details"))
            .collect(Collectors.partitioningBy(IdeaPluginDescriptorImpl::isBundled));

          List<IdeaPluginDescriptorImpl> nonBundledPlugins = visiblePlugins.get(Boolean.FALSE);
          downloaded.descriptors.addAll(nonBundledPlugins);

          LinkListener<Object> updateAllListener = new LinkListener<>() {
            @Override
            public void linkSelected(LinkLabel<Object> aSource, Object aLinkData) {
              myUpdateAll.setEnabled(false);
              myUpdateAllBundled.setEnabled(false);

              for (UIPluginGroup group : getInstalledGroups()) {
                if (group.excluded) {
                  continue;
                }
                for (ListPluginComponent plugin : group.plugins) {
                  plugin.updatePlugin();
                }
              }
            }
          };
          myUpdateAll.setListener(updateAllListener, null);
          downloaded.addRightAction(myUpdateAll);
          downloaded.addRightAction(myUpdateCounter);

          if (!downloaded.descriptors.isEmpty()) {
            downloaded.sortByName();

            long enabledNonBundledCount = nonBundledPlugins.stream()
              .map(IdeaPluginDescriptorImpl::getPluginId)
              .filter(descriptor -> !PluginManagerCore.isDisabled(descriptor))
              .count();
            downloaded.titleWithCount(Math.toIntExact(enabledNonBundledCount));
            myInstalledPanel.addGroup(downloaded);
            myPluginModel.addEnabledGroup(downloaded);
          }

          myPluginModel.setDownloadedGroup(myInstalledPanel, downloaded, installing);

          String defaultCategory = IdeBundle.message("plugins.configurable.other.bundled");
          visiblePlugins.get(Boolean.TRUE)
            .stream()
            .collect(Collectors.groupingBy(descriptor -> StringUtil.defaultIfEmpty(descriptor.getDisplayCategory(), defaultCategory)))
            .entrySet()
            .stream()
            .map(entry -> new ComparablePluginsGroup(entry.getKey(), entry.getValue()))
            .sorted((o1, o2) -> defaultCategory.equals(o1.title) ? 1 :
                                defaultCategory.equals(o2.title) ? -1 :
                                o1.compareTo(o2))
            .forEachOrdered(group -> {
              myInstalledPanel.addGroup(group);
              myPluginModel.addEnabledGroup(group);
            });

          myUpdateAllBundled.setListener(updateAllListener, null);
          myBundledUpdateGroup.addRightAction(myUpdateAllBundled);
          myBundledUpdateGroup.addRightAction(myUpdateCounterBundled);

          myPluginUpdatesService.calculateUpdates(updates -> {
            if (ContainerUtil.isEmpty(updates)) {
              clearUpdates(myInstalledPanel);
              clearUpdates(myInstalledSearchPanel.getPanel());
            }
            else {
              applyUpdates(myInstalledPanel, updates);
              applyUpdates(myInstalledSearchPanel.getPanel(), updates);
            }
            applyBundledUpdates(updates);
            selectionListener.accept(myInstalledPanel);
            selectionListener.accept(myInstalledSearchPanel.getPanel());
          });
        }
        finally {
          PluginLogo.endBatchMode();
        }

        return createScrollPane(myInstalledPanel, true);
      }

      @Override
      protected void updateMainSelection(@NotNull Consumer<? super PluginsGroupComponent> selectionListener) {
        selectionListener.accept(myInstalledPanel);
      }

      @Override
      public void hideSearchPanel() {
        super.hideSearchPanel();
        if (myInstalledSearchSetState) {
          for (AnAction action : myInstalledSearchGroup.getChildren(ActionManager.getInstance())) {
            ((InstalledSearchOptionAction)action).setState(null);
          }
        }
        myPluginModel.setInvalidFixCallback(null);
      }

      @Override
      protected void onSearchReset() {
        PluginManagerUsageCollector.searchReset();
      }

      @Override
      protected @NotNull SearchResultPanel createSearchPanel(@NotNull Consumer<? super PluginsGroupComponent> selectionListener) {
        SearchUpDownPopupController installedController = new SearchUpDownPopupController(mySearchTextField) {
          @Override
          protected @NotNull @NonNls List<String> getAttributes() {
            return Arrays
              .asList(
                "/downloaded",
                "/outdated",
                "/enabled",
                "/disabled",
                "/invalid",
                "/bundled",
                SearchWords.VENDOR.getValue(),
                SearchWords.TAG.getValue()
              );
          }

          @Override
          protected @Nullable SortedSet<String> getValues(@NotNull String attribute) {
            return SearchWords.VENDOR.getValue().equals(attribute) ?
                   myPluginModel.getVendors() :
                   SearchWords.TAG.getValue().equals(attribute) ?
                   myPluginModel.getTags() :
                   null;
          }

          @Override
          protected void showPopupForQuery() {
            showSearchPanel(mySearchTextField.getText());
          }
        };

        MultiSelectionEventHandler eventHandler = new MultiSelectionEventHandler();
        installedController.setSearchResultEventHandler(eventHandler);

        PluginsGroupComponent panel = new PluginsGroupComponent(eventHandler) {
          @Override
          protected @NotNull ListPluginComponent createListComponent(@NotNull IdeaPluginDescriptor descriptor,
                                                                     @NotNull PluginsGroup group) {
            return new ListPluginComponent(myPluginModel, descriptor, group, mySearchListener, false);
          }
        };

        panel.setSelectionListener(selectionListener);
        registerCopyProvider(panel);

        myInstalledSearchCallback = updateAction -> {
          List<String> queries = new ArrayList<>();
          new SearchQueryParser.Installed(mySearchTextField.getText()) {
            @Override
            protected void addToSearchQuery(@NotNull String query) {
              queries.add(query);
            }

            @Override
            protected void handleAttribute(@NotNull String name, @NotNull String value) {
              if (!updateAction.myState) {
                queries.add(name + (value.isEmpty() ? "" : SearchQueryParser.wrapAttribute(value)));
              }
            }
          };

          if (updateAction.myState) {
            for (AnAction action : myInstalledSearchGroup.getChildren(ActionManager.getInstance())) {
              if (action != updateAction) {
                ((InstalledSearchOptionAction)action).myState = false;
              }
            }

            queries.add(updateAction.getQuery());
          }
          else {
            queries.remove(updateAction.getQuery());
          }

          try {
            myInstalledSearchSetState = false;

            String query = StringUtil.join(queries, " ");
            mySearchTextField.setTextIgnoreEvents(query);
            if (query.isEmpty()) {
              myInstalledTab.hideSearchPanel();
            }
            else {
              myInstalledTab.showSearchPanel(query);
            }
          }
          finally {
            myInstalledSearchSetState = true;
          }
        };

        myInstalledSearchPanel = new SearchResultPanel(installedController, panel, false, 0, 0) {
          @Override
          protected void setEmptyText(@NotNull String query) {
            myPanel.getEmptyText().setText(IdeBundle.message("plugins.configurable.nothing.found"));
            if (query.contains("/downloaded") || query.contains("/outdated") ||
                query.contains("/enabled") || query.contains("/disabled") ||
                query.contains("/invalid") || query.contains("/bundled")) {
              return;
            }
            myPanel.getEmptyText().appendSecondaryText(IdeBundle.message("plugins.configurable.search.in.marketplace"),
                                                       SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES,
                                                       e -> myTabHeaderComponent.setSelectionWithEvents(MARKETPLACE_TAB));
          }

          @Override
          protected void handleQuery(@NotNull String query, @NotNull PluginsGroup result) {
            int searchIndex = PluginManagerUsageCollector.updateAndGetSearchIndex();
            myPluginModel.setInvalidFixCallback(null);

            SearchQueryParser.Installed parser = new SearchQueryParser.Installed(query);

            if (myInstalledSearchSetState) {
              for (AnAction action : myInstalledSearchGroup.getChildren(ActionManager.getInstance())) {
                ((InstalledSearchOptionAction)action).setState(parser);
              }
            }

            List<IdeaPluginDescriptor> descriptors = myPluginModel.getInstalledDescriptors();

            if (!parser.vendors.isEmpty()) {
              for (Iterator<IdeaPluginDescriptor> I = descriptors.iterator(); I.hasNext(); ) {
                if (!MyPluginModel.isVendor(I.next(), parser.vendors)) {
                  I.remove();
                }
              }
            }
            if (!parser.tags.isEmpty()) {
              for (Iterator<IdeaPluginDescriptor> I = descriptors.iterator(); I.hasNext(); ) {
                if (!ContainerUtil.intersects(getTags(I.next()), parser.tags)) {
                  I.remove();
                }
              }
            }
            for (Iterator<IdeaPluginDescriptor> I = descriptors.iterator(); I.hasNext(); ) {
              IdeaPluginDescriptor descriptor = I.next();
              if (parser.attributes) {
                if (parser.enabled && (!myPluginModel.isEnabled(descriptor) || myPluginModel.hasErrors(descriptor))) {
                  I.remove();
                  continue;
                }
                if (parser.disabled && (myPluginModel.isEnabled(descriptor) || myPluginModel.hasErrors(descriptor))) {
                  I.remove();
                  continue;
                }
                if (parser.bundled && !descriptor.isBundled()) {
                  I.remove();
                  continue;
                }
                if (parser.downloaded && descriptor.isBundled()) {
                  I.remove();
                  continue;
                }
                if (parser.invalid && !myPluginModel.hasErrors(descriptor)) {
                  I.remove();
                  continue;
                }
                if (parser.needUpdate && !PluginUpdatesService.isNeedUpdate(descriptor)) {
                  I.remove();
                  continue;
                }
              }
              if (parser.searchQuery != null && !containsQuery(descriptor, parser.searchQuery)) {
                I.remove();
              }
            }

            result.descriptors.addAll(descriptors);
            PluginManagerUsageCollector.performInstalledTabSearch(
              ProjectUtil.getActiveProject(), parser, result.descriptors, searchIndex, null);

            if (!result.descriptors.isEmpty()) {
              if (parser.invalid) {
                myPluginModel.setInvalidFixCallback(() -> {
                  PluginsGroup group = myInstalledSearchPanel.getGroup();
                  if (group.ui == null) {
                    myPluginModel.setInvalidFixCallback(null);
                    return;
                  }

                  PluginsGroupComponent resultPanel = myInstalledSearchPanel.getPanel();

                  for (IdeaPluginDescriptor descriptor : new ArrayList<>(group.descriptors)) {
                    if (!myPluginModel.hasErrors(descriptor)) {
                      resultPanel.removeFromGroup(group, descriptor);
                    }
                  }

                  group.titleWithCount();
                  myInstalledSearchPanel.fullRepaint();

                  if (group.descriptors.isEmpty()) {
                    myPluginModel.setInvalidFixCallback(null);
                    myInstalledSearchPanel.removeGroup();
                  }
                });
              }
              else if (parser.needUpdate) {
                result.rightAction = new LinkLabelButton<>(IdeBundle.message("plugin.manager.update.all"), null, (__, ___) -> {
                  result.rightAction.setEnabled(false);

                  for (ListPluginComponent plugin : result.ui.plugins) {
                    plugin.updatePlugin();
                  }
                });
              }

              Collection<IdeaPluginDescriptor> updates = PluginUpdatesService.getUpdates();
              if (!ContainerUtil.isEmpty(updates)) {
                myPostFillGroupCallback = () -> {
                  applyUpdates(myPanel, updates);
                  selectionListener.accept(myInstalledPanel);
                  selectionListener.accept(myInstalledSearchPanel.getPanel());
                };
              }
            }
          }
        };

        return myInstalledSearchPanel;
      }
    };

    myPluginModel.setCancelInstallCallback(descriptor -> {
      if (myInstalledSearchPanel == null) {
        return;
      }

      PluginsGroup group = myInstalledSearchPanel.getGroup();

      if (group.ui != null && group.ui.findComponent(descriptor) != null) {
        myInstalledSearchPanel.getPanel().removeFromGroup(group, descriptor);
        group.titleWithCount();
        myInstalledSearchPanel.fullRepaint();

        if (group.descriptors.isEmpty()) {
          myInstalledSearchPanel.removeGroup();
        }
      }
    });
  }
private void createInstalledTab()