private ActionCallback update(
      DocumentEvent.EventType type, boolean adjustSelection, boolean now) {
    if (myUpdateRejected) {
      return new ActionCallback.Rejected();
    }
    String text = getFilterText();
    if (text.isEmpty()) {
      myContext.setHoldingFilter(false);
      myFiltered = null;
    } else {
      myContext.setHoldingFilter(true);
      myHits = myRegistrar.getConfigurables(myGroups, type, myFiltered, text, myProject);
      myFiltered = myHits.getAll();
    }
    mySearch
        .getTextEditor()
        .setBackground(
            myFiltered != null && myFiltered.isEmpty()
                ? LightColors.RED
                : UIUtil.getTextFieldBackground());

    Configurable current = myContext.getCurrentConfigurable();

    boolean shouldMoveSelection =
        myHits == null
            || (!myHits.getNameFullHits().contains(current)
                && !myHits.getContentHits().contains(current));

    if (shouldMoveSelection
        && type != DocumentEvent.EventType.INSERT
        && (myFiltered == null || myFiltered.contains(current))) {
      shouldMoveSelection = false;
    }

    Configurable candidate = adjustSelection ? current : null;
    if (shouldMoveSelection && myHits != null) {
      if (!myHits.getNameHits().isEmpty()) {
        candidate = findConfigurable(myHits.getNameHits(), myHits.getNameFullHits());
      } else if (!myHits.getContentHits().isEmpty()) {
        candidate = findConfigurable(myHits.getContentHits(), null);
      }
    }
    updateSpotlight(false);

    if ((myFiltered == null || !myFiltered.isEmpty())
        && candidate == null
        && myLastSelected != null) {
      candidate = myLastSelected;
      myLastSelected = null;
    }
    if (candidate == null && current != null) {
      myLastSelected = current;
    }
    SimpleNode node = !adjustSelection ? null : findNode(candidate);
    ActionCallback callback = fireUpdate(node, adjustSelection, now);
    myDocumentWasChanged = true;
    return callback;
  }
  protected void filter(String filter) {
    final SearchableOptionsRegistrar optionsRegistrar = SearchableOptionsRegistrar.getInstance();
    final Set<String> search = optionsRegistrar.getProcessedWords(filter);

    final ArrayList<IdeaPluginDescriptor> desc = new ArrayList<IdeaPluginDescriptor>();

    final List<IdeaPluginDescriptor> toProcess = toProcess();
    for (IdeaPluginDescriptor descriptor : filtered) {
      if (!toProcess.contains(descriptor)) {
        toProcess.add(descriptor);
      }
    }
    filtered.clear();
    for (IdeaPluginDescriptor descriptor : toProcess) {
      if (isPluginDescriptorAccepted(descriptor)
          && PluginManagerMain.isAccepted(filter, search, descriptor)) {
        desc.add(descriptor);
      } else {
        filtered.add(descriptor);
      }
    }
    filter(desc);
  }
abstract class SettingsFilter extends ElementFilter.Active.Impl<SimpleNode> {
  final OptionsEditorContext myContext = new OptionsEditorContext(this);
  final Project myProject;

  boolean myDocumentWasChanged;

  private final SearchTextField mySearch;
  private final ConfigurableGroup[] myGroups;

  private SearchableOptionsRegistrar myRegistrar = SearchableOptionsRegistrar.getInstance();
  private Set<Configurable> myFiltered;
  private ConfigurableHit myHits;

  private boolean myUpdateRejected;
  private Configurable myLastSelected;

  SettingsFilter(Project project, ConfigurableGroup[] groups, SearchTextField search) {
    myProject = project;
    myGroups = groups;
    mySearch = search;
    mySearch.addDocumentListener(
        new DocumentAdapter() {
          @Override
          protected void textChanged(DocumentEvent event) {
            update(event.getType(), true, false);
            // request focus if needed on changing the filter text
            IdeFocusManager manager = IdeFocusManager.findInstanceByComponent(mySearch);
            if (manager.getFocusedDescendantFor(mySearch) == null) {
              manager.requestFocus(mySearch, true);
            }
          }
        });
    mySearch
        .getTextEditor()
        .addMouseListener(
            new MouseAdapter() {
              @Override
              public void mousePressed(MouseEvent event) {
                if (!mySearch.getText().isEmpty()) {
                  if (!myContext.isHoldingFilter()) {
                    setHoldingFilter(true);
                  }
                  if (!mySearch.getTextEditor().isFocusOwner()) {
                    mySearch.selectText();
                  }
                }
              }
            });
  }

  abstract Configurable getConfigurable(SimpleNode node);

  abstract SimpleNode findNode(Configurable configurable);

  abstract void updateSpotlight(boolean now);

  @Override
  public boolean shouldBeShowing(SimpleNode node) {
    if (myFiltered != null) {
      Configurable configurable = getConfigurable(node);
      if (configurable != null) {
        if (!myFiltered.contains(configurable)) {
          if (myHits != null) {
            Set<Configurable> configurables = myHits.getNameFullHits();
            while (node != null) {
              if (configurable != null) {
                if (configurables.contains(configurable)) {
                  return true;
                }
              }
              node = node.getParent();
              configurable = getConfigurable(node);
            }
          }
          return false;
        }
      }
    }
    return true;
  }

  String getFilterText() {
    String text = mySearch.getText();
    return text == null ? "" : text.trim();
  }

  void setHoldingFilter(boolean holding) {
    myContext.setHoldingFilter(holding);
    updateSpotlight(false);
  }

  boolean contains(Configurable configurable) {
    return myHits != null && myHits.getNameHits().contains(configurable);
  }

  ActionCallback update(boolean adjustSelection, boolean now) {
    return update(DocumentEvent.EventType.CHANGE, adjustSelection, now);
  }

  ActionCallback update(String text, boolean adjustSelection, boolean now) {
    try {
      myUpdateRejected = true;
      mySearch.setText(text);
    } finally {
      myUpdateRejected = false;
    }
    return update(adjustSelection, now);
  }

  private ActionCallback update(
      DocumentEvent.EventType type, boolean adjustSelection, boolean now) {
    if (myUpdateRejected) {
      return new ActionCallback.Rejected();
    }
    String text = getFilterText();
    if (text.isEmpty()) {
      myContext.setHoldingFilter(false);
      myFiltered = null;
    } else {
      myContext.setHoldingFilter(true);
      myHits = myRegistrar.getConfigurables(myGroups, type, myFiltered, text, myProject);
      myFiltered = myHits.getAll();
    }
    mySearch
        .getTextEditor()
        .setBackground(
            myFiltered != null && myFiltered.isEmpty()
                ? LightColors.RED
                : UIUtil.getTextFieldBackground());

    Configurable current = myContext.getCurrentConfigurable();

    boolean shouldMoveSelection =
        myHits == null
            || (!myHits.getNameFullHits().contains(current)
                && !myHits.getContentHits().contains(current));

    if (shouldMoveSelection
        && type != DocumentEvent.EventType.INSERT
        && (myFiltered == null || myFiltered.contains(current))) {
      shouldMoveSelection = false;
    }

    Configurable candidate = adjustSelection ? current : null;
    if (shouldMoveSelection && myHits != null) {
      if (!myHits.getNameHits().isEmpty()) {
        candidate = findConfigurable(myHits.getNameHits(), myHits.getNameFullHits());
      } else if (!myHits.getContentHits().isEmpty()) {
        candidate = findConfigurable(myHits.getContentHits(), null);
      }
    }
    updateSpotlight(false);

    if ((myFiltered == null || !myFiltered.isEmpty())
        && candidate == null
        && myLastSelected != null) {
      candidate = myLastSelected;
      myLastSelected = null;
    }
    if (candidate == null && current != null) {
      myLastSelected = current;
    }
    SimpleNode node = !adjustSelection ? null : findNode(candidate);
    ActionCallback callback = fireUpdate(node, adjustSelection, now);
    myDocumentWasChanged = true;
    return callback;
  }

  private static Configurable findConfigurable(
      Set<Configurable> configurables, Set<Configurable> hits) {
    Configurable candidate = null;
    for (Configurable configurable : configurables) {
      if (hits != null && hits.contains(configurable)) {
        return configurable;
      }
      if (candidate == null && !isEmptyParent(configurable)) {
        candidate = configurable;
      }
    }
    return candidate;
  }

  private static boolean isEmptyParent(Configurable configurable) {
    SearchableConfigurable.Parent parent =
        ConfigurableWrapper.cast(SearchableConfigurable.Parent.class, configurable);
    return parent != null && !parent.hasOwnContent();
  }
}