/*
 * Copyright 2000-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.ide.actions;

import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.codeInsight.navigation.NavigationUtil;
import com.intellij.execution.Executor;
import com.intellij.execution.ExecutorRegistry;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.actions.ChooseRunConfigurationPopup;
import com.intellij.execution.actions.ExecutorProvider;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.impl.RunDialog;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.IdeTooltipManager;
import com.intellij.ide.SearchTopHitProvider;
import com.intellij.ide.structureView.StructureView;
import com.intellij.ide.structureView.StructureViewBuilder;
import com.intellij.ide.structureView.StructureViewModel;
import com.intellij.ide.structureView.StructureViewTreeElement;
import com.intellij.ide.ui.OptionsTopHitProvider;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.laf.darcula.ui.DarculaTextBorder;
import com.intellij.ide.ui.laf.darcula.ui.DarculaTextFieldUI;
import com.intellij.ide.ui.search.BooleanOptionDescription;
import com.intellij.ide.ui.search.OptionDescription;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.ide.util.gotoByName.*;
import com.intellij.ide.util.treeView.smartTree.TreeElement;
import com.intellij.lang.Language;
import com.intellij.lang.LanguagePsiElementExternalizer;
import com.intellij.navigation.ItemPresentation;
import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.actionSystem.ex.AnActionListener;
import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
import com.intellij.openapi.actionSystem.impl.ActionToolbarImpl;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.actions.TextComponentEditorAction;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.OpenFileDescriptor;
import com.intellij.openapi.fileEditor.impl.EditorHistoryManager;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.keymap.MacKeymapUtil;
import com.intellij.openapi.keymap.impl.ModifierKeyDoubleClickHandler;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.ComponentPopupBuilder;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.VirtualFilePathWrapper;
import com.intellij.openapi.wm.*;
import com.intellij.openapi.wm.impl.IdeFrameImpl;
import com.intellij.pom.Navigatable;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.MinusculeMatcher;
import com.intellij.psi.codeStyle.NameUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.ui.*;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.border.CustomLineBorder;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.JBList;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.components.OnOffButton;
import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.ui.popup.AbstractPopup;
import com.intellij.ui.popup.PopupPositionManager;
import com.intellij.util.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.Matcher;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.StatusText;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.accessibility.Accessible;
import javax.accessibility.AccessibleContext;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author Konstantin Bulenkov
 */
@SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
public class SearchEverywhereAction extends AnAction implements CustomComponentAction, DumbAware, DataProvider, RightAlignedToolbarAction {
  public static final String SE_HISTORY_KEY = "SearchEverywhereHistoryKey";
  public static final int SEARCH_FIELD_COLUMNS = 25;
  private static final int MAX_CLASSES = 6;
  private static final int MAX_FILES = 6;
  private static final int MAX_RUN_CONFIGURATION = 6;
  private static final int MAX_TOOL_WINDOWS = 4;
  private static final int MAX_SYMBOLS = 6;
  private static final int MAX_SETTINGS = 5;
  private static final int MAX_ACTIONS = 5;
  private static final int MAX_STRUCTURE = 10;
  private static final int MAX_RECENT_FILES = 10;
  private static final int DEFAULT_MORE_STEP_COUNT = 15;
  public static final int MAX_SEARCH_EVERYWHERE_HISTORY = 50;
  public static final int MAX_TOP_HIT = 15;
  private static final int POPUP_MAX_WIDTH = 600;
  private static final Logger LOG = Logger.getInstance("#" + SearchEverywhereAction.class.getName());

  private SearchEverywhereAction.MyListRenderer myRenderer;
  MySearchTextField myPopupField;
  private volatile GotoClassModel2 myClassModel;
  private volatile GotoFileModel myFileModel;
  private volatile GotoActionItemProvider myActionProvider;
  private volatile GotoSymbolModel2 mySymbolsModel;
  private Component myFocusComponent;
  private JBPopup myPopup;
  private Map<String, String> myConfigurables = new HashMap<String, String>();

  private Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, ApplicationManager.getApplication());
  private Alarm myUpdateAlarm = new Alarm(ApplicationManager.getApplication());
  private JBList myList;
  private JCheckBox myNonProjectCheckBox;
  private AnActionEvent myActionEvent;
  private Set<AnAction> myDisabledActions = new HashSet<AnAction>();
  private Component myContextComponent;
  private CalcThread myCalcThread;
  private static AtomicBoolean ourShiftIsPressed = new AtomicBoolean(false);
  private static AtomicBoolean showAll = new AtomicBoolean(false);
  private volatile ActionCallback myCurrentWorker = ActionCallback.DONE;
  private int myHistoryIndex = 0;
  boolean mySkipFocusGain = false;

  static {
    ModifierKeyDoubleClickHandler.getInstance().registerAction(IdeActions.ACTION_SEARCH_EVERYWHERE, KeyEvent.VK_SHIFT, -1);

    IdeEventQueue.getInstance().addPostprocessor(new IdeEventQueue.EventDispatcher() {
      @Override
      public boolean dispatch(AWTEvent event) {
        if (event instanceof KeyEvent) {
          final int keyCode = ((KeyEvent)event).getKeyCode();
          if (keyCode == KeyEvent.VK_SHIFT) {
            ourShiftIsPressed.set(event.getID() == KeyEvent.KEY_PRESSED);
          }
        }
        return false;
      }
    }, null);
  }

  private volatile JBPopup myBalloon;
  private int myPopupActualWidth;
  private Component myFocusOwner;
  private ChooseByNamePopup myFileChooseByName;
  private ChooseByNamePopup myClassChooseByName;
  private ChooseByNamePopup mySymbolsChooseByName;
  private StructureViewModel myStructureModel;


  private Editor myEditor;
  private FileEditor myFileEditor;
  private PsiFile myFile;
  private HistoryItem myHistoryItem;

  @Override
  public JComponent createCustomComponent(Presentation presentation) {
    JPanel panel = JBUI.Panels.simplePanel();
    panel.setOpaque(false);

    final JLabel label = new JBLabel(AllIcons.Actions.FindPlain) {
      {
        enableEvents(AWTEvent.MOUSE_EVENT_MASK);
        enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK);
      }
    };
    panel.add(label, BorderLayout.CENTER);
    initTooltip(label);
    label.addMouseListener(new MouseAdapter() {
      @Override
      public void mousePressed(MouseEvent e) {
        if (myBalloon != null) {
          myBalloon.cancel();
        }
        myFocusOwner = IdeFocusManager.findInstance().getFocusOwner();
        label.setToolTipText(null);
        IdeTooltipManager.getInstance().hideCurrentNow(false);
        actionPerformed(null, e);
      }

      @Override
      public void mouseEntered(MouseEvent e) {
        if (myBalloon == null || myBalloon.isDisposed()) {
          label.setIcon(AllIcons.Actions.Find);
        }
      }

      @Override
      public void mouseExited(MouseEvent e) {
        if (myBalloon == null || myBalloon.isDisposed()) {
          label.setIcon(AllIcons.Actions.FindPlain);
        }
      }
    });

    return panel;
  }

  private static Gradient getGradientColors() {
    return new Gradient(
      new JBColor(new Color(101, 147, 242), new Color(64, 80, 94)),
      new JBColor(new Color(46, 111, 205), new Color(53, 65, 87)));
  }

  public SearchEverywhereAction() {
    updateComponents();
    //noinspection SSBasedInspection
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        onFocusLost();
      }
    });

  }

  private void updateComponents() {
    myRenderer = new MyListRenderer();
    myList = new JBList(new SearchListModel()) {
      int lastKnownHeight = JBUI.scale(30);
      @Override
      public Dimension getPreferredSize() {
        final Dimension size = super.getPreferredSize();
        if (size.height == -1) {
          size.height = lastKnownHeight;
        } else {
          lastKnownHeight = size.height;
        }
        return new Dimension(Math.max(myBalloon.getSize().width, Math.min(size.width - 2, POPUP_MAX_WIDTH)), myList.isEmpty() ? JBUI.scale(30) : size.height);
      }

      @Override
      public void clearSelection() {
        //avoid blinking
      }

      @Override
      public Object getSelectedValue() {
        try {
          return super.getSelectedValue();
        } catch (Exception e) {
          return null;
        }
      }
    };
    myList.setCellRenderer(myRenderer);
    myList.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(MouseEvent e) {
        e.consume();
        final int i = myList.locationToIndex(e.getPoint());
        if (i != -1) {
          mySkipFocusGain = true;
          getField().requestFocus();
          //noinspection SSBasedInspection
          SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
              myList.setSelectedIndex(i);
              doNavigate(i);
            }
          });
        }
      }
    });

    myNonProjectCheckBox = new JCheckBox();
    myNonProjectCheckBox.setOpaque(false);
    myNonProjectCheckBox.setAlignmentX(1.0f);
    myNonProjectCheckBox.addActionListener(new ActionListener() {
      @Override
      public void actionPerformed(ActionEvent e) {
        if (showAll.get() != myNonProjectCheckBox.isSelected()) {
          showAll.set(!showAll.get());
          final JTextField editor = UIUtil.findComponentOfType(myBalloon.getContent(), JTextField.class);
          if (editor != null) {
            final String pattern = editor.getText();
            myAlarm.cancelAllRequests();
            myAlarm.addRequest(new Runnable() {
              @Override
              public void run() {
                if (editor.hasFocus()) {
                  rebuildList(pattern);
                }
              }
            }, 30);
          }
        }
      }
    });
  }

  private static void initTooltip(JLabel label) {
    final String shortcutText;
    shortcutText = getShortcut();

    label.setToolTipText("<html><body>Search Everywhere<br/>Press <b>"
                                 + shortcutText
                                 + "</b> to access<br/> - Classes<br/> - Files<br/> - Tool Windows<br/> - Actions<br/> - Settings</body></html>");

  }

  @Nullable
  @Override
  public Object getData(@NonNls String dataId) {
    return null;
  }

  private static String getShortcut() {
    String shortcutText;
    final Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_SEARCH_EVERYWHERE);
    if (shortcuts.length == 0) {
      shortcutText = "Double " + (SystemInfo.isMac ? MacKeymapUtil.SHIFT : "Shift");
    } else {
      shortcutText = KeymapUtil.getShortcutsText(shortcuts);
    }
    return shortcutText;
  }

  private void initSearchField(final MySearchTextField search) {
    final JTextField editor = search.getTextEditor();
//    onFocusLost();
    editor.getDocument().addDocumentListener(new DocumentAdapter() {
      @Override
      protected void textChanged(DocumentEvent e) {
        final String pattern = editor.getText();
        if (editor.hasFocus()) {
          rebuildList(pattern);
        }
      }
    });
    editor.addFocusListener(new FocusAdapter() {
      @Override
      public void focusGained(FocusEvent e) {
        if (mySkipFocusGain) {
          mySkipFocusGain = false;
          return;
        }
        String text = "";
        if (myEditor != null) {
          text = myEditor.getSelectionModel().getSelectedText();
          text = text == null ? "" : text.trim();
        }

        search.setText(text);
        search.getTextEditor().setForeground(UIUtil.getLabelForeground());
        //titleIndex = new TitleIndexes();
        editor.setColumns(SEARCH_FIELD_COLUMNS);
        myFocusComponent = e.getOppositeComponent();
        //noinspection SSBasedInspection
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            final JComponent parent = (JComponent)editor.getParent();
            parent.revalidate();
            parent.repaint();
          }
        });
        //if (myPopup != null && myPopup.isVisible()) {
        //  myPopup.cancel();
        //  myPopup = null;
        //}
        rebuildList(text);
      }

      @Override
      public void focusLost(FocusEvent e) {
        if ( myPopup instanceof AbstractPopup && myPopup.isVisible()
             && ((myList == e.getOppositeComponent()) || ((AbstractPopup)myPopup).getPopupWindow() == e.getOppositeComponent())) {
          return;
        }
        if (myNonProjectCheckBox == e.getOppositeComponent()) {
          mySkipFocusGain = true;
          editor.requestFocus();
          return;
        }
        onFocusLost();
      }
    });
  }

  private void jumpNextGroup(boolean forward) {
    final int index = myList.getSelectedIndex();
    final SearchListModel model = getModel();
    if (index >= 0) {
      final int newIndex = forward ? model.next(index) : model.prev(index);
      myList.setSelectedIndex(newIndex);
      int more = model.next(newIndex) - 1;
      if (more < newIndex) {
        more = myList.getItemsCount() - 1;
      }
      ScrollingUtil.ensureIndexIsVisible(myList, more, forward ? 1 : -1);
      ScrollingUtil.ensureIndexIsVisible(myList, newIndex, forward ? 1 : -1);
    }
  }

  private SearchListModel getModel() {
    return (SearchListModel)myList.getModel();
  }

  private ActionCallback onFocusLost() {
    final ActionCallback result = new ActionCallback();
    //noinspection SSBasedInspection
    UIUtil.invokeLaterIfNeeded(new Runnable() {
      @Override
      public void run() {
        try {
          if (myCalcThread != null) {
            myCalcThread.cancel();
            //myCalcThread = null;
          }
          myAlarm.cancelAllRequests();
          if (myBalloon != null && !myBalloon.isDisposed() && myPopup != null && !myPopup.isDisposed()) {
            myBalloon.cancel();
            myPopup.cancel();
          }

          //noinspection SSBasedInspection
          SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
              ActionToolbarImpl.updateAllToolbarsImmediately();
            }
          });
        }
        finally {
          result.setDone();
        }
      }
    });
    return result;
  }

  private SearchTextField getField() {
    return myPopupField;
  }

  private void doNavigate(final int index) {
    final DataManager dataManager = DataManager.getInstance();
    if (dataManager == null) return;
    final Project project = CommonDataKeys.PROJECT.getData(dataManager.getDataContext(getField().getTextEditor()));
    final Executor executor = ourShiftIsPressed.get()
                              ? DefaultRunExecutor.getRunExecutorInstance()
                              : ExecutorRegistry.getInstance().getExecutorById(ToolWindowId.DEBUG);
    assert project != null;
    final SearchListModel model = getModel();
    if (isMoreItem(index)) {
      final String pattern = myPopupField.getText();
      WidgetID wid = null;
      if (index == model.moreIndex.classes) wid = WidgetID.CLASSES;
      else if (index == model.moreIndex.files) wid = WidgetID.FILES;
      else if (index == model.moreIndex.settings) wid = WidgetID.SETTINGS;
      else if (index == model.moreIndex.actions) wid = WidgetID.ACTIONS;
      else if (index == model.moreIndex.symbols) wid = WidgetID.SYMBOLS;
      else if (index == model.moreIndex.runConfigurations) wid = WidgetID.RUN_CONFIGURATIONS;
      if (wid != null) {
        final WidgetID widgetID = wid;
        myCurrentWorker.doWhenProcessed(new Runnable() {
          @Override
          public void run() {
            myCalcThread = new CalcThread(project, pattern, true);
            myPopupActualWidth = 0;
            myCurrentWorker = myCalcThread.insert(index, widgetID);
          }
        });

        return;
      }
    }
    final String pattern = getField().getText();
    final Object value = myList.getSelectedValue();
    saveHistory(project, pattern, value);
    IdeFocusManager focusManager = IdeFocusManager.findInstanceByComponent(getField().getTextEditor());
    if (myPopup != null && myPopup.isVisible()) {
      myPopup.cancel();
    }

    if (value instanceof BooleanOptionDescription) {
      final BooleanOptionDescription option = (BooleanOptionDescription)value;
      option.setOptionState(!option.isOptionEnabled());
      myList.revalidate();
      myList.repaint();
      getField().requestFocus();
      return;
    }

    if (value instanceof OptionsTopHitProvider) {
      //noinspection SSBasedInspection
      SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
          getField().setText("#" + ((OptionsTopHitProvider)value).getId() + " ");
        }
      });
      return;
    }
    Runnable onDone = null;

    AccessToken token = ApplicationManager.getApplication().acquireReadActionLock();
    try {
      if (value instanceof PsiElement) {
        onDone = new Runnable() {
          public void run() {
            NavigationUtil.activateFileWithPsiElement((PsiElement)value, true);
          }
        };
        return;
      }
      else if (isVirtualFile(value)) {
        onDone = new Runnable() {
          public void run() {
            OpenSourceUtil.navigate(true, new OpenFileDescriptor(project, (VirtualFile)value));
          }
        };
        return;
      }
      else if (isActionValue(value) || isSetting(value) || isRunConfiguration(value)) {
        focusManager.requestDefaultFocus(true);
        final Component comp = myContextComponent;
        final AnActionEvent event = myActionEvent;
        IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(new Runnable() {
          @Override
          public void run() {
            Component c = comp;
            if (c == null) {
              c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
            }

            if (isRunConfiguration(value)) {
              ((ChooseRunConfigurationPopup.ItemWrapper)value).perform(project, executor, dataManager.getDataContext(c));
            } else {
              GotoActionAction.openOptionOrPerformAction(value, pattern, project, c, event);
              if (isToolWindowAction(value)) return;
            }
          }
        });
        return;
      }
      else if (value instanceof Navigatable) {
        onDone = new Runnable() {
          @Override
          public void run() {
            OpenSourceUtil.navigate(true, (Navigatable)value);
          }
        };
        return;
      }
    }
    finally {
      token.finish();
      final ActionCallback callback = onFocusLost();
      if (onDone != null) {
        callback.doWhenDone(onDone);
      }
    }
    focusManager.requestDefaultFocus(true);
  }

  private boolean isMoreItem(int index) {
    final SearchListModel model = getModel();
    return index == model.moreIndex.classes ||
           index == model.moreIndex.files ||
           index == model.moreIndex.settings ||
           index == model.moreIndex.actions ||
           index == model.moreIndex.symbols ||
           index == model.moreIndex.runConfigurations;
  }

  private void rebuildList(final String pattern) {
    assert EventQueue.isDispatchThread() : "Must be EDT";
    if (myCalcThread != null && !myCurrentWorker.isProcessed()) {
      myCurrentWorker = myCalcThread.cancel();
    }
    if (myCalcThread != null && !myCalcThread.isCanceled()) {
      myCalcThread.cancel();
    }
    final Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(getField().getTextEditor()));

    assert project != null;
    myRenderer.myProject = project;
    final Runnable run = new Runnable() {
      @Override
      public void run() {
        myCalcThread = new CalcThread(project, pattern, false);
        myPopupActualWidth = 0;
        myCurrentWorker = myCalcThread.start();
      }
    };
    if (myCurrentWorker.isDone()) {
      myCurrentWorker.doWhenDone(run);
    } else {
      myCurrentWorker.doWhenRejected(run);
    }
  }

  @Override
  public void actionPerformed(AnActionEvent e) {
    actionPerformed(e, null);
  }

  public void actionPerformed(AnActionEvent e, MouseEvent me) {
    if (myBalloon != null && myBalloon.isVisible()) {
      showAll.set(!showAll.get());
      myNonProjectCheckBox.setSelected(showAll.get());
//      myPopupField.getTextEditor().setBackground(showAll.get() ? new JBColor(new Color(0xffffe4), new Color(0x494539)) : UIUtil.getTextFieldBackground());
      rebuildList(myPopupField.getText());
      return;
    }
    myCurrentWorker = ActionCallback.DONE;
    if (e != null) {
      myEditor = e.getData(CommonDataKeys.EDITOR);
      myFileEditor = e.getData(PlatformDataKeys.FILE_EDITOR);
      myFile = e.getData(CommonDataKeys.PSI_FILE);
    }
    if (e == null && myFocusOwner != null) {
      e = AnActionEvent.createFromAnAction(this, me, ActionPlaces.UNKNOWN, DataManager.getInstance().getDataContext(myFocusOwner));
    }
    if (e == null) return;
    final Project project = e.getProject();
    if (project == null) return;

    //noinspection SSBasedInspection
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        LookupManager.getInstance(project).hideActiveLookup();
      }
    });

    updateComponents();
    myContextComponent = PlatformDataKeys.CONTEXT_COMPONENT.getData(e.getDataContext());
    Window wnd = myContextComponent != null ? SwingUtilities.windowForComponent(myContextComponent)
      : KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
    if (wnd == null && myContextComponent instanceof Window) {
      wnd = (Window)myContextComponent;
    }
    if (wnd == null || wnd.getParent() != null) return;
    myActionEvent = e;
    if (myPopupField != null) {
      Disposer.dispose(myPopupField);
    }
    myPopupField = new MySearchTextField();
    myPopupField.getTextEditor().addKeyListener(new KeyAdapter() {
      @Override
      public void keyTyped(KeyEvent e) {
        myHistoryIndex = 0;
        myHistoryItem = null;
      }

      @Override
      public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
          myList.repaint();
        }
      }

      @Override
      public void keyReleased(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_SHIFT) {
          myList.repaint();
        }
      }
    });
    initSearchField(myPopupField);
    myPopupField.setOpaque(false);
    final JTextField editor = myPopupField.getTextEditor();
    editor.setColumns(SEARCH_FIELD_COLUMNS);
    final JPanel panel = new JPanel(new BorderLayout()) {
      @Override
      protected void paintComponent(Graphics g) {
        final Gradient gradient = getGradientColors();
        ((Graphics2D)g).setPaint(new GradientPaint(0, 0, gradient.getStartColor(), 0, getHeight(), gradient.getEndColor()));
        g.fillRect(0, 0, getWidth(), getHeight());
      }
    };
    final JLabel title = new JLabel(" Search Everywhere:       ");
    final JPanel topPanel = new NonOpaquePanel(new BorderLayout());
    title.setForeground(new JBColor(Gray._240, Gray._200));
    if (SystemInfo.isMac) {
      title.setFont(title.getFont().deriveFont(Font.BOLD, title.getFont().getSize() - 1f));
    } else {
      title.setFont(title.getFont().deriveFont(Font.BOLD));
    }
    topPanel.add(title, BorderLayout.WEST);
    final JPanel controls = new JPanel(new BorderLayout());
    controls.setOpaque(false);
    final JLabel settings = new JLabel(AllIcons.General.SearchEverywhereGear);
    new ClickListener(){
      @Override
      public boolean onClick(@NotNull MouseEvent event, int clickCount) {
        showSettings();
        return true;
      }
    }.installOn(settings);
    controls.add(settings, BorderLayout.EAST);
    myNonProjectCheckBox.setForeground(new JBColor(Gray._240, Gray._200));
    myNonProjectCheckBox.setText("Include non-project items (" + getShortcut() + ")  ");
    if (!NonProjectScopeDisablerEP.isSearchInNonProjectDisabled()) {
      controls.add(myNonProjectCheckBox, BorderLayout.WEST);
    }
    topPanel.add(controls, BorderLayout.EAST);
    panel.add(myPopupField, BorderLayout.CENTER);
    panel.add(topPanel, BorderLayout.NORTH);
    panel.setBorder(IdeBorderFactory.createEmptyBorder(3, 5, 4, 5));
    DataManager.registerDataProvider(panel, this);
    final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, editor);
    myBalloon = builder
      .setCancelOnClickOutside(true)
      .setModalContext(false)
      .setRequestFocus(true)
      .setCancelCallback(new Computable<Boolean>() {
        @Override
        public Boolean compute() {
          return !mySkipFocusGain;
        }
      })
      .createPopup();
    myBalloon.getContent().setBorder(JBUI.Borders.empty());
    final Window window = WindowManager.getInstance().suggestParentWindow(project);

    project.getMessageBus().connect(myBalloon).subscribe(DumbService.DUMB_MODE, new DumbService.DumbModeListener() {
      @Override
      public void enteredDumbMode() {
      }

      @Override
      public void exitDumbMode() {
        ApplicationManager.getApplication().invokeLater(new Runnable() {
          @Override
          public void run() {
            rebuildList(myPopupField.getText());
          }
        });
      }
    });

    Component parent = UIUtil.findUltimateParent(window);
    registerDataProvider(panel, project);
    final RelativePoint showPoint;
    if (parent != null) {
      int height = UISettings.getInstance().SHOW_MAIN_TOOLBAR ? 135 : 115;
      if (parent instanceof IdeFrameImpl && ((IdeFrameImpl)parent).isInFullScreen()) {
        height -= 20;
      }
      showPoint = new RelativePoint(parent, new Point((parent.getSize().width - panel.getPreferredSize().width) / 2, height));
    } else {
      showPoint = JBPopupFactory.getInstance().guessBestPopupLocation(e.getDataContext());
    }
    myList.setFont(UIUtil.getListFont());
    myBalloon.show(showPoint);
    initSearchActions(myBalloon, myPopupField);
    IdeFocusManager focusManager = IdeFocusManager.getInstance(project);
    focusManager.requestFocus(editor, true);
    FeatureUsageTracker.getInstance().triggerFeatureUsed(IdeActions.ACTION_SEARCH_EVERYWHERE);
  }

  private void showSettings() {
    myPopupField.setText("");
    final SearchListModel model = new SearchListModel();
    //model.addElement(new SEOption("Show current file structure elements", "search.everywhere.structure"));
    model.addElement(new SEOption("Show files", "search.everywhere.files"));
    model.addElement(new SEOption("Show symbols", "search.everywhere.symbols"));
    model.addElement(new SEOption("Show tool windows", "search.everywhere.toolwindows"));
    model.addElement(new SEOption("Show run configurations", "search.everywhere.configurations"));
    model.addElement(new SEOption("Show actions", "search.everywhere.actions"));
    model.addElement(new SEOption("Show IDE settings", "search.everywhere.settings"));

    if (myCalcThread != null && !myCurrentWorker.isProcessed()) {
      myCurrentWorker = myCalcThread.cancel();
    }
    if (myCalcThread != null && !myCalcThread.isCanceled()) {
      myCalcThread.cancel();
    }
    myCurrentWorker.doWhenProcessed(new Runnable() {
      @Override
      public void run() {
        myList.setModel(model);
        updatePopupBounds();
      }
    });
  }

  static class SEOption extends BooleanOptionDescription {
    private final String myKey;

    public SEOption(String option, String registryKey) {
      super(option, null);
      myKey = registryKey;
    }

    @Override
    public boolean isOptionEnabled() {
      return Registry.is(myKey);
    }

    @Override
    public void setOptionState(boolean enabled) {
      Registry.get(myKey).setValue(enabled);
    }
  }

  private static void saveHistory(Project project, String text, Object value) {
    if (project == null || project.isDisposed() || !project.isInitialized()) {
      return;
    }
    HistoryType type = null;
    String fqn = null;
    if (isActionValue(value)) {
      type = HistoryType.ACTION;
      AnAction action = (AnAction)(value instanceof GotoActionModel.ActionWrapper ? ((GotoActionModel.ActionWrapper)value).getAction() : value);
      fqn = ActionManager.getInstance().getId(action);
    } else if (value instanceof VirtualFile) {
      type = HistoryType.FILE;
      fqn = ((VirtualFile)value).getUrl();
    } else if (value instanceof ChooseRunConfigurationPopup.ItemWrapper) {
      type = HistoryType.RUN_CONFIGURATION;
      fqn = ((ChooseRunConfigurationPopup.ItemWrapper)value).getText();
    } else if (value instanceof PsiElement) {
      final PsiElement psiElement = (PsiElement)value;
      final Language language = psiElement.getLanguage();
      final String name = LanguagePsiElementExternalizer.INSTANCE.forLanguage(language).getQualifiedName(psiElement);
      if (name != null) {
        type = HistoryType.PSI;
        fqn = language.getID() + "://" + name;
      }
    }

    final PropertiesComponent storage = PropertiesComponent.getInstance(project);
    final String[] values = storage.getValues(SE_HISTORY_KEY);
    List<HistoryItem> history = new ArrayList<HistoryItem>();
    if (values != null) {
      for (String s : values) {
        final String[] split = s.split("\t");
        if (split.length != 3 || text.equals(split[0])) {
          continue;
        }
        if (!StringUtil.isEmpty(split[0])) {
          history.add(new HistoryItem(split[0], split[1], split[2]));
        }
      }
    }
    history.add(0, new HistoryItem(text, type == null ? null : type.name(), fqn));

    if (history.size() > MAX_SEARCH_EVERYWHERE_HISTORY) {
      history = history.subList(0, MAX_SEARCH_EVERYWHERE_HISTORY);
    }
    final String[] newValues = new String[history.size()];
    for (int i = 0; i < newValues.length; i++) {
      newValues[i] = history.get(i).toString();
    }
    storage.setValues(SE_HISTORY_KEY, newValues);
  }

  public Executor getExecutor() {
    return ourShiftIsPressed.get() ? DefaultRunExecutor.getRunExecutorInstance()
                                   : ExecutorRegistry.getInstance().getExecutorById(ToolWindowId.DEBUG);
  }

  private void registerDataProvider(JPanel panel, final Project project) {
    DataManager.registerDataProvider(panel, new DataProvider() {
      @Nullable
      @Override
      public Object getData(@NonNls String dataId) {
        final Object value = myList.getSelectedValue();
        if (CommonDataKeys.PSI_ELEMENT.is(dataId) && value instanceof PsiElement) {
          return value;
        } else if (CommonDataKeys.VIRTUAL_FILE.is(dataId) && value instanceof VirtualFile) {
          return value;
        } else if (CommonDataKeys.NAVIGATABLE.is(dataId)) {
              if (value instanceof Navigatable) return value;
              if (value instanceof ChooseRunConfigurationPopup.ItemWrapper) {
                final Object config = ((ChooseRunConfigurationPopup.ItemWrapper)value).getValue();
                if (config instanceof RunnerAndConfigurationSettings) {
                  return new Navigatable() {
                    @Override
                    public void navigate(boolean requestFocus) {
                      RunDialog.editConfiguration(project, (RunnerAndConfigurationSettings)config, "Edit Configuration", getExecutor());
                    }

                    @Override
                    public boolean canNavigate() {
                      return true;
                    }

                    @Override
                    public boolean canNavigateToSource() {
                      return true;
                    }
                  };
                }
              }
        } else if (PlatformDataKeys.SEARCH_INPUT_TEXT.is(dataId)) {
          return myPopupField == null ? null : myPopupField.getText();
        }
        return null;
      }
    });
  }

  private void initSearchActions(JBPopup balloon, MySearchTextField searchTextField) {
    final JTextField editor = searchTextField.getTextEditor();
    new DumbAwareAction(){
      @Override
      public void actionPerformed(AnActionEvent e) {
        jumpNextGroup(true);
      }
    }.registerCustomShortcutSet(CustomShortcutSet.fromString("TAB"), editor, balloon);
    new DumbAwareAction(){
      @Override
      public void actionPerformed(AnActionEvent e) {
        jumpNextGroup(false);
      }
    }.registerCustomShortcutSet(CustomShortcutSet.fromString("shift TAB"), editor, balloon);
    final AnAction escape = ActionManager.getInstance().getAction("EditorEscape");
    new DumbAwareAction(){
      @Override
      public void actionPerformed(AnActionEvent e) {
        if (myBalloon != null && myBalloon.isVisible()) {
          myBalloon.cancel();
        }
        if (myPopup != null && myPopup.isVisible()) {
          myPopup.cancel();
        }
      }
    }.registerCustomShortcutSet(escape == null ? CommonShortcuts.ESCAPE : escape.getShortcutSet(), editor, balloon);
    new DumbAwareAction(){
      @Override
      public void actionPerformed(AnActionEvent e) {
        final int index = myList.getSelectedIndex();
        if (index != -1) {
          doNavigate(index);
        }
      }
    }.registerCustomShortcutSet(CustomShortcutSet.fromString("ENTER", "shift ENTER"), editor, balloon);
    new DumbAwareAction(){
      @Override
      public void actionPerformed(AnActionEvent e) {
        final PropertiesComponent storage = PropertiesComponent.getInstance(e.getProject());
        final String[] values = storage.getValues(SE_HISTORY_KEY);
        if (values != null) {
          if (values.length > myHistoryIndex) {
            final List<String> data = StringUtil.split(values[myHistoryIndex], "\t");
            myHistoryItem = new HistoryItem(data.get(0), data.get(1), data.get(2));
            myHistoryIndex++;
            editor.setText(myHistoryItem.pattern);
            editor.setCaretPosition(myHistoryItem.pattern.length());
            editor.moveCaretPosition(0);
          }
        }
      }

      @Override
      public void update(AnActionEvent e) {
        e.getPresentation().setEnabled(editor.getCaretPosition() == 0);
      }
    }.registerCustomShortcutSet(CustomShortcutSet.fromString("LEFT"), editor, balloon);
  }

  private static class MySearchTextField extends SearchTextField implements DataProvider, Disposable {
    public MySearchTextField() {
      super(false);
      getTextEditor().setOpaque(false);
      getTextEditor().setUI((DarculaTextFieldUI)DarculaTextFieldUI.createUI(getTextEditor()));
      getTextEditor().setBorder(new DarculaTextBorder());

      getTextEditor().putClientProperty("JTextField.Search.noBorderRing", Boolean.TRUE);
      if (UIUtil.isUnderDarcula()) {
        getTextEditor().setBackground(Gray._45);
        getTextEditor().setForeground(Gray._240);
      }
    }

    @Override
    protected boolean isSearchControlUISupported() {
      return true;
    }

    @Override
    protected boolean hasIconsOutsideOfTextField() {
      return false;
    }

    @Override
    protected void showPopup() {
    }

    @Nullable
    @Override
    public Object getData(@NonNls String dataId) {
      if (PlatformDataKeys.PREDEFINED_TEXT.is(dataId)) {
        return getTextEditor().getText();
      }
      return null;
    }

    @Override
    public void dispose() {
    }
  }

  private class MyListRenderer extends ColoredListCellRenderer {
    ColoredListCellRenderer myLocation = new ColoredListCellRenderer() {
      @Override
      protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
        setPaintFocusBorder(false);
        append(myLocationString, SimpleTextAttributes.GRAYED_ATTRIBUTES);
        setIcon(myLocationIcon);
      }
    };
    SearchEverywherePsiRenderer myFileRenderer = new SearchEverywherePsiRenderer(myList);
    @SuppressWarnings("unchecked")
    ListCellRenderer myActionsRenderer = new GotoActionModel.GotoActionListCellRenderer(Function.TO_STRING);

    private String myLocationString;
    private Icon myLocationIcon;
    private Project myProject;
    private MyAccessibleComponent myMainPanel = new MyAccessibleComponent(new BorderLayout());
    private JLabel myTitle = new JLabel();

    private class MyAccessibleComponent extends JPanel {
      private Accessible myAccessible;
      public MyAccessibleComponent(LayoutManager layout) {
        super(layout);
      }
      void setAccessible(Accessible comp) {
        myAccessible = comp;
      }
      @Override
      public AccessibleContext getAccessibleContext() {
        return myAccessible != null ? myAccessible.getAccessibleContext() : super.getAccessibleContext();
      }
    }

    @Override
    public void clear() {
      super.clear();
      myLocation.clear();
      myLocationString = null;
      myLocationIcon = null;
    }

    public void setLocationString(String locationString) {
      myLocationString = locationString;
    }

    @Override
    public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
      Component cmp;
      PsiElement file;
      myLocationString = null;
      String pattern = "*" + myPopupField.getText();
      Matcher matcher = NameUtil.buildMatcher(pattern, 0, true, true);
      if (isMoreItem(index)) {
        cmp = More.get(isSelected);
      } else if (value instanceof VirtualFile
                 && myProject != null
                 && ((((VirtualFile)value).isDirectory() && (file = PsiManager.getInstance(myProject).findDirectory((VirtualFile)value)) != null )
                     || (file = PsiManager.getInstance(myProject).findFile((VirtualFile)value)) != null)) {
        myFileRenderer.setPatternMatcher(matcher);
        cmp = myFileRenderer.getListCellRendererComponent(list, file, index, isSelected, cellHasFocus);
      } else if (value instanceof PsiElement) {
        myFileRenderer.setPatternMatcher(matcher);
        cmp = myFileRenderer.getListCellRendererComponent(list, value, index, isSelected, isSelected);
      } else if (value instanceof GotoActionModel.ActionWrapper) {
        cmp = myActionsRenderer.getListCellRendererComponent(list, new GotoActionModel.MatchedValue(((GotoActionModel.ActionWrapper)value), pattern), index, isSelected, isSelected);
      } else {
        cmp = super.getListCellRendererComponent(list, value, index, isSelected, isSelected);
        final JPanel p = new JPanel(new BorderLayout());
        p.setBackground(UIUtil.getListBackground(isSelected));
        p.add(cmp, BorderLayout.CENTER);
        cmp = p;
      }
      if (myLocationString != null || value instanceof BooleanOptionDescription) {
        final JPanel panel = new JPanel(new BorderLayout());
        panel.setBackground(UIUtil.getListBackground(isSelected));
        panel.add(cmp, BorderLayout.CENTER);
        final Component rightComponent;
        if (value instanceof BooleanOptionDescription) {
          final OnOffButton button = new OnOffButton();
          button.setSelected(((BooleanOptionDescription)value).isOptionEnabled());
          rightComponent = button;
        }
        else {
          rightComponent = myLocation.getListCellRendererComponent(list, value, index, isSelected, isSelected);
        }
        panel.add(rightComponent, BorderLayout.EAST);
        cmp = panel;
      }

      Color bg = cmp.getBackground();
      if (bg == null) {
        cmp.setBackground(UIUtil.getListBackground(isSelected));
        bg = cmp.getBackground();
      }
      myMainPanel.setBorder(new CustomLineBorder(bg, 0, 0, 2, 0));
      String title = getModel().titleIndex.getTitle(index);
      myMainPanel.removeAll();
      if (title != null) {
        myTitle.setText(title);
        myMainPanel.add(createTitle(" " + title), BorderLayout.NORTH);
      }
      myMainPanel.add(cmp, BorderLayout.CENTER);
      if (cmp instanceof Accessible) {
        myMainPanel.setAccessible((Accessible)cmp);
      }
      final int width = myMainPanel.getPreferredSize().width;
      if (width > myPopupActualWidth) {
        myPopupActualWidth = width;
        //schedulePopupUpdate();
      }
      return myMainPanel;
    }

    @Override
    protected void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) {
      setPaintFocusBorder(false);
      setIcon(EmptyIcon.ICON_16);
      AccessToken token = ApplicationManager.getApplication().acquireReadActionLock();
      try {
        if (value instanceof PsiElement) {
          String name = myClassModel.getElementName(value);
          assert name != null;
          append(name);
        } else if (value instanceof ChooseRunConfigurationPopup.ItemWrapper) {
          final ChooseRunConfigurationPopup.ItemWrapper wrapper = (ChooseRunConfigurationPopup.ItemWrapper)value;
          append(wrapper.getText());
          setIcon(wrapper.getIcon());
          setLocationString(ourShiftIsPressed.get() ? "Run" : "Debug");
          myLocationIcon = ourShiftIsPressed.get() ? AllIcons.Toolwindows.ToolWindowRun : AllIcons.Toolwindows.ToolWindowDebugger;
        } else if (isVirtualFile(value)) {
          final VirtualFile file = (VirtualFile)value;
          if (file instanceof VirtualFilePathWrapper) {
            append(((VirtualFilePathWrapper)file).getPresentablePath());
          } else {
            append(file.getName());
          }
          setIcon(IconUtil.getIcon(file, Iconable.ICON_FLAG_READ_STATUS, myProject));
        }
        else if (isActionValue(value)) {
          final GotoActionModel.ActionWrapper actionWithParentGroup = value instanceof GotoActionModel.ActionWrapper ? (GotoActionModel.ActionWrapper)value : null;
          final AnAction anAction = actionWithParentGroup == null ? (AnAction)value : actionWithParentGroup.getAction();
          final Presentation templatePresentation = anAction.getTemplatePresentation();
          Icon icon = templatePresentation.getIcon();
          if (anAction instanceof ActivateToolWindowAction) {
            final String id = ((ActivateToolWindowAction)anAction).getToolWindowId();
            ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(id);
            if (toolWindow != null) {
              icon = toolWindow.getIcon();
            }
          }

          append(templatePresentation.getText());
          if (actionWithParentGroup != null) {
            final String groupName = actionWithParentGroup.getGroupName();
            if (!StringUtil.isEmpty(groupName)) {
              setLocationString(groupName);
            }
          }

          final String groupName = actionWithParentGroup == null ? null : actionWithParentGroup.getGroupName();
          if (!StringUtil.isEmpty(groupName)) {
            setLocationString(groupName);
          }
          if (icon != null && icon.getIconWidth() <= 16 && icon.getIconHeight() <= 16) {
            setIcon(IconUtil.toSize(icon, 16, 16));
          }
        }
        else if (isSetting(value)) {
          String text = getSettingText((OptionDescription)value);
          append(text);
          final String id = ((OptionDescription)value).getConfigurableId();
          final String name = myConfigurables.get(id);
          if (name != null) {
            setLocationString(name);
          }
        }
        else if (value instanceof OptionsTopHitProvider) {
          append("#" + ((OptionsTopHitProvider)value).getId());
        }
        else {
          ItemPresentation presentation = null;
          if (value instanceof ItemPresentation) {
            presentation = (ItemPresentation)value;
          }
          else if (value instanceof NavigationItem) {
            presentation = ((NavigationItem)value).getPresentation();
          }
          if (presentation != null) {
            final String text = presentation.getPresentableText();
            append(text == null ? value.toString() : text);
            final String location = presentation.getLocationString();
            if (!StringUtil.isEmpty(location)) {
              setLocationString(location);
            }
            Icon icon = presentation.getIcon(false);
            if (icon != null) setIcon(icon);
          }
        }
      }
      finally {
        token.finish();
      }
    }

    public void recalculateWidth() {
      ListModel model = myList.getModel();
      myTitle.setIcon(EmptyIcon.ICON_16);
      myTitle.setFont(getTitleFont());
      int index = 0;
      while (index < model.getSize()) {
        String title = getModel().titleIndex.getTitle(index);
        if (title != null) {
          myTitle.setText(title);
        }
        index++;
      }

      myTitle.setForeground(Gray._122);
      myTitle.setAlignmentY(BOTTOM_ALIGNMENT);
    }
  }

  private static String getSettingText(OptionDescription value) {
    String hit = value.getHit();
    if (hit == null) {
      hit = value.getOption();
    }
    hit = StringUtil.unescapeXml(hit);
    if (hit.length() > 60) {
      hit = hit.substring(0, 60) + "...";
    }
    hit = hit.replace("  ", " "); //avoid extra spaces from mnemonics and xml conversion
    String text = hit.trim();
    if (text.endsWith(":")) {
      text = text.substring(0, text.length() - 1);
    }
    return text;
  }

  private static boolean isActionValue(Object o) {
    return o instanceof GotoActionModel.ActionWrapper || o instanceof AnAction;
  }

  private static boolean isSetting(Object o) {
    return o instanceof OptionDescription;
  }

  private static boolean isRunConfiguration(Object o) {
    return o instanceof ChooseRunConfigurationPopup.ItemWrapper;
  }

  private static boolean isVirtualFile(Object o) {
    return o instanceof VirtualFile;
  }

  private static Font getTitleFont() {
    return UIUtil.getLabelFont().deriveFont(UIUtil.getFontSize(UIUtil.FontSize.SMALL));
  }

  enum WidgetID {CLASSES, FILES, ACTIONS, SETTINGS, SYMBOLS, RUN_CONFIGURATIONS}

  @SuppressWarnings({"SSBasedInspection", "unchecked"})
  private class CalcThread implements Runnable {
    private final Project project;
    private final String pattern;
    private final ProgressIndicator myProgressIndicator = new ProgressIndicatorBase();
    private final ActionCallback myDone = new ActionCallback();
    private final SearchListModel myListModel;
    private final ArrayList<VirtualFile> myAlreadyAddedFiles = new ArrayList<VirtualFile>();
    private final ArrayList<AnAction> myAlreadyAddedActions = new ArrayList<AnAction>();


    public CalcThread(Project project, String pattern, boolean reuseModel) {
      this.project = project;
      this.pattern = pattern;
      myListModel = reuseModel ? (SearchListModel)myList.getModel() : new SearchListModel();
    }

    @Override
    public void run() {
      try {
        check();

        //noinspection SSBasedInspection
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            // this line must be called on EDT to avoid context switch at clear().append("text") Don't touch. Ask [kb]
            myList.getEmptyText().setText("Searching...");

            if (myList.getModel() instanceof SearchListModel) {
              //noinspection unchecked
              myAlarm.cancelAllRequests();
              myAlarm.addRequest(new Runnable() {
                @Override
                public void run() {
                  if (!myDone.isRejected()) {
                    myList.setModel(myListModel);
                    updatePopup();
                  }
                }
              }, 50);
            } else {
              myList.setModel(myListModel);
            }
          }
        });

        if (pattern.trim().length() == 0) {
          buildModelFromRecentFiles();
          //updatePopup();
          return;
        }

        checkModelsUpToDate();              check();
        buildTopHit(pattern);               check();

        if (!pattern.startsWith("#")) {
          buildRecentFiles(pattern);
          check();
          runReadAction(new Runnable() {
            public void run() {
              buildStructure(pattern);
            }
          }, true);
          updatePopup();
          check();
          buildToolWindows(pattern);
          check();
          updatePopup();
          check();

          runReadAction(new Runnable() {
            public void run() {
              buildRunConfigurations(pattern);
            }
          }, true);
          runReadAction(new Runnable() {
            public void run() {
              buildClasses(pattern);
            }
          }, true);
          runReadAction(new Runnable() {
            public void run() {
              buildFiles(pattern);
            }
          }, false);
          runReadAction(new Runnable() {
            public void run() {
              buildSymbols(pattern);
            }
          }, true);

          buildActionsAndSettings(pattern);

          updatePopup();

        }
        updatePopup();
      }
      catch (ProcessCanceledException ignore) {
        myDone.setRejected();
      }
      catch (Exception e) {
        LOG.error(e);
        myDone.setRejected();
      }
      finally {
        if (!isCanceled()) {
          //noinspection SSBasedInspection
          SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
              myList.getEmptyText().setText(StatusText.DEFAULT_EMPTY_TEXT);
            }
          });
          updatePopup();
        }
        if (!myDone.isProcessed()) {
          myDone.setDone();
        }
      }
    }

    private void runReadAction(Runnable action, boolean checkDumb) {
      if (!checkDumb || !DumbService.getInstance(project).isDumb()) {
        ApplicationManager.getApplication().runReadAction(action);
        updatePopup();
      }
    }

    protected void check() {
      myProgressIndicator.checkCanceled();
      if (myDone.isRejected()) throw new ProcessCanceledException();
      if (myBalloon == null || myBalloon.isDisposed()) throw new ProcessCanceledException();
    }

    private synchronized void buildToolWindows(String pattern) {
      if (!Registry.is("search.everywhere.toolwindows")) {
        return;
      }
      final List<ActivateToolWindowAction> actions = new ArrayList<ActivateToolWindowAction>();
      for (ActivateToolWindowAction action : ToolWindowsGroup.getToolWindowActions(project)) {
        String text = action.getTemplatePresentation().getText();
        if (text != null && StringUtil.startsWithIgnoreCase(text, pattern)) {
          actions.add(action);

          if (actions.size() == MAX_TOOL_WINDOWS) {
            break;
          }
        }
      }

      check();

      if (actions.isEmpty()) {
        return;
      }

      SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
          myListModel.titleIndex.toolWindows = myListModel.size();
          for (Object toolWindow : actions) {
            myListModel.addElement(toolWindow);
          }
        }
      });
    }

    private SearchResult getActionsOrSettings(final String pattern, final int max, final boolean actions) {
      final SearchResult result = new SearchResult();
      if ((actions && !Registry.is("search.everywhere.actions")) || (!actions && !Registry.is("search.everywhere.settings"))) {
        return result;
      }
      final MinusculeMatcher matcher = new MinusculeMatcher("*" +pattern, NameUtil.MatchingCaseSensitivity.NONE);
      if (myActionProvider == null) {
        myActionProvider = createActionProvider();
      }

      myActionProvider.filterElements(pattern, true, new Processor<GotoActionModel.MatchedValue>() {
        @Override
        public boolean process(GotoActionModel.MatchedValue matched) {
          check();
          Object object = matched.value;
          if (myListModel.contains(object)) return true;

          if (!actions && isSetting(object)) {
            if (matcher.matches(getSettingText((OptionDescription)object))) {
              result.add(object);
            }
          }
          else if (actions && !isToolWindowAction(object) && isActionValue(object)) {
            AnAction action = object instanceof AnAction ? ((AnAction)object) : ((GotoActionModel.ActionWrapper)object).getAction();
            Object lock = myCalcThread;
            if (lock != null) {
              synchronized (lock) {
                if (isEnabled(action)) {
                  result.add(object);
                }
              }
            }
          }
          return result.size() <= max;
        }
      });

      return result;
    }

    private synchronized void buildActionsAndSettings(String pattern) {
      final SearchResult actions = getActionsOrSettings(pattern, MAX_ACTIONS, true);
      final SearchResult settings = getActionsOrSettings(pattern, MAX_SETTINGS, false);

      check();

      SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
          if (isCanceled()) return;
          if (actions.size() > 0) {
            myListModel.titleIndex.actions = myListModel.size();
            for (Object action : actions) {
              myListModel.addElement(action);
            }
          }
          myListModel.moreIndex.actions = actions.size() >= MAX_ACTIONS ? myListModel.size() - 1 : -1;
          if (settings.size() > 0) {
            myListModel.titleIndex.settings = myListModel.size();
            for (Object setting : settings) {
              myListModel.addElement(setting);
            }
          }
          myListModel.moreIndex.settings = settings.size() >= MAX_SETTINGS ? myListModel.size() - 1 : -1;
        }
      });
    }

    private synchronized void buildFiles(final String pattern) {
      final SearchResult files = getFiles(pattern, showAll.get(), MAX_FILES, myFileChooseByName);

      check();

      if (files.size() > 0) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            if (isCanceled()) return;
            myListModel.titleIndex.files = myListModel.size();
            for (Object file : files) {
              myListModel.addElement(file);
            }
            myListModel.moreIndex.files = files.needMore ? myListModel.size() - 1 : -1;
          }
        });
      }
    }

    private synchronized void buildStructure(final String pattern) {
      if (!Registry.is("search.everywhere.structure") || myStructureModel == null) return;
      final List<StructureViewTreeElement> elements = new ArrayList<StructureViewTreeElement>();
      final MinusculeMatcher matcher = new MinusculeMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE);
      fillStructure(myStructureModel.getRoot(), elements, matcher);
      if (elements.size() > 0) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            if (isCanceled()) return;
            myListModel.titleIndex.structure = myListModel.size();
            for (Object element : elements) {
              myListModel.addElement(element);
            }
            myListModel.moreIndex.files = -1;
          }
        });
      }
    }

    private void fillStructure(StructureViewTreeElement element, List<StructureViewTreeElement> elements, Matcher matcher) {
      final TreeElement[] children = element.getChildren();
      check();
      for (TreeElement child : children) {
        check();
        if (child instanceof StructureViewTreeElement) {
          final String text = child.getPresentation().getPresentableText();
          if (text != null && matcher.matches(text)) {
            elements.add((StructureViewTreeElement)child);
          }
          fillStructure((StructureViewTreeElement)child, elements, matcher);
        }
      }
    }


    private synchronized void buildSymbols(final String pattern) {
      final SearchResult symbols = getSymbols(pattern, MAX_SYMBOLS, showAll.get(), mySymbolsChooseByName);
      check();

      if (symbols.size() > 0) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            if (isCanceled()) return;
            myListModel.titleIndex.symbols = myListModel.size();
            for (Object file : symbols) {
              myListModel.addElement(file);
            }
            myListModel.moreIndex.symbols = symbols.needMore ? myListModel.size() - 1 : -1;
          }
        });
      }
    }

    @Nullable
    private ChooseRunConfigurationPopup.ItemWrapper getRunConfigurationByName(String name) {
      final ChooseRunConfigurationPopup.ItemWrapper[] wrappers =
        ChooseRunConfigurationPopup.createSettingsList(project, new ExecutorProvider() {
          @Override
          public Executor getExecutor() {
            return ExecutorRegistry.getInstance().getExecutorById(ToolWindowId.DEBUG);
          }
        }, false);

      for (ChooseRunConfigurationPopup.ItemWrapper wrapper : wrappers) {
        if (wrapper.getText().equals(name)) {
          return wrapper;
        }
      }
      return null;
    }

    private synchronized void buildRunConfigurations(String pattern) {
      final SearchResult runConfigurations = getConfigurations(pattern, MAX_RUN_CONFIGURATION);

      if (runConfigurations.size() > 0) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            if (isCanceled()) return;
            myListModel.titleIndex.runConfigurations = myListModel.size();
            for (Object runConfiguration : runConfigurations) {
              myListModel.addElement(runConfiguration);
            }
            myListModel.moreIndex.runConfigurations = runConfigurations.needMore ? myListModel.getSize() - 1 : -1;
          }
        });
      }
    }

    private SearchResult getConfigurations(String pattern, int max) {
      SearchResult configurations = new SearchResult();
      if (!Registry.is("search.everywhere.configurations")) {
        return configurations;
      }
      MinusculeMatcher matcher = new MinusculeMatcher(pattern, NameUtil.MatchingCaseSensitivity.NONE);
      final ChooseRunConfigurationPopup.ItemWrapper[] wrappers =
        ChooseRunConfigurationPopup.createSettingsList(project, new ExecutorProvider() {
          @Override
          public Executor getExecutor() {
            return ExecutorRegistry.getInstance().getExecutorById(ToolWindowId.DEBUG);
          }
        }, false);
      check();
      for (ChooseRunConfigurationPopup.ItemWrapper wrapper : wrappers) {
        if (matcher.matches(wrapper.getText()) && !myListModel.contains(wrapper)) {
          if (configurations.size() == max) {
            configurations.needMore = true;
            break;
          }
          configurations.add(wrapper);
        }
        check();
      }

      return configurations;
    }


    private synchronized void buildClasses(final String pattern) {
      final SearchResult classes = getClasses(pattern, showAll.get(), MAX_CLASSES, myClassChooseByName);
      check();

      if (classes.size() > 0) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            if (isCanceled()) return;
            myListModel.titleIndex.classes = myListModel.size();
            for (Object file : classes) {
              myListModel.addElement(file);
            }
            myListModel.moreIndex.classes = -1;
            if (classes.needMore) {
              myListModel.moreIndex.classes = myListModel.size() - 1;
            }
          }
        });
      }
    }

    private SearchResult getSymbols(String pattern, final int max, final boolean includeLibs, ChooseByNamePopup chooseByNamePopup) {
      final SearchResult symbols = new SearchResult();
      if (!Registry.is("search.everywhere.symbols") || shouldSkipPattern(pattern)) {
        return symbols;
      }
      final GlobalSearchScope scope = GlobalSearchScope.projectScope(project);
      if (chooseByNamePopup == null) return symbols;
      final ChooseByNameItemProvider provider = chooseByNamePopup.getProvider();
      provider.filterElements(chooseByNamePopup, pattern, includeLibs,
                              myProgressIndicator, new Processor<Object>() {
          @Override
          public boolean process(Object o) {
            if (isSymbol(o)) {
              final PsiElement element = (PsiElement)o;
              final PsiFile file = element.getContainingFile();
              if (!myListModel.contains(o) && !symbols.contains(o) &&
                  //some elements are non-physical like DB columns
                  (file == null || (file.getVirtualFile() != null && (includeLibs || scope.accept(file.getVirtualFile()))))) {
                symbols.add(o);
              }
            }
            symbols.needMore = symbols.size() == max;
            return !symbols.needMore;
          }
        });

      if (!includeLibs && symbols.isEmpty()) {
        return getSymbols(pattern, max, true, chooseByNamePopup);
      }

      return symbols;
    }

    protected boolean isSymbol(Object o) {
      if (o instanceof PsiElement) {
        final PsiElement e = (PsiElement)o;
        //todo[kb] need a better way to avoid mixing java classes with symbols. Same to other languages where
        //todo[kb] symbol provider returns classes. We need kind of suppressor API & EP here.
        return !e.getLanguage().is(Language.findLanguageByID("JAVA")) || !(e.getParent() instanceof PsiFile);
      }
      return false;
    }

    private SearchResult getClasses(String pattern, boolean includeLibs, final int max, ChooseByNamePopup chooseByNamePopup) {
      final SearchResult classes = new SearchResult();
      if (chooseByNamePopup == null || shouldSkipPattern(pattern)) {
        return classes;
      }
      chooseByNamePopup.getProvider().filterElements(chooseByNamePopup, pattern, includeLibs,
                                                      myProgressIndicator, new Processor<Object>() {
          @Override
          public boolean process(Object o) {
            if (o instanceof PsiElement && !myListModel.contains(o) && !classes.contains(o)) {
              if (classes.size() == max) {
                classes.needMore = true;
                return false;
              }
              classes.add(o);
              if (o instanceof PsiNamedElement) {
                final String name = ((PsiNamedElement)o).getName();
                final PsiFile file = ((PsiNamedElement)o).getContainingFile();
                if (file != null) {
                  final VirtualFile virtualFile = file.getVirtualFile();
                  if (StringUtil.equals(name, virtualFile.getNameWithoutExtension())) {
                    myAlreadyAddedFiles.add(virtualFile);
                  }
                }
              }
            }
            return true;
          }
        });
      if (!includeLibs && classes.isEmpty()) {
        return getClasses(pattern, true, max, chooseByNamePopup);
      }
      return classes;
    }

    private SearchResult getFiles(final String pattern, final boolean includeLibs, final int max, ChooseByNamePopup chooseByNamePopup) {
      final SearchResult files = new SearchResult();
      if (chooseByNamePopup == null || !Registry.is("search.everywhere.files")) {
        return files;
      }
      final GlobalSearchScope scope = GlobalSearchScope.projectScope(project);
      chooseByNamePopup.getProvider().filterElements(chooseByNamePopup, pattern, true,
                                                      myProgressIndicator, new Processor<Object>() {
          @Override
          public boolean process(Object o) {
            VirtualFile file = null;
            if (o instanceof VirtualFile) {
              file = (VirtualFile)o;
            } else if (o instanceof PsiFile) {
              file = ((PsiFile)o).getVirtualFile();
            } else if (o instanceof PsiDirectory) {
              file = ((PsiDirectory)o).getVirtualFile();
            }
            if (file != null
                && !(pattern.indexOf(' ') != -1 && file.getName().indexOf(' ') == -1)
                && (includeLibs || scope.accept(file)
                && !myListModel.contains(file)
                && !myAlreadyAddedFiles.contains(file))
                && !files.contains(file)) {
              if (files.size() == max) {
                files.needMore = true;
                return false;
              }
              files.add(file);
            }
            return true;
          }
        });
      if (!includeLibs && files.isEmpty()) {
        return getFiles(pattern, true, max, chooseByNamePopup);
      }

      return files;
    }

    private synchronized void buildRecentFiles(String pattern) {
      final MinusculeMatcher matcher = new MinusculeMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE);
      final ArrayList<VirtualFile> files = new ArrayList<VirtualFile>();
      final List<VirtualFile> selected = Arrays.asList(FileEditorManager.getInstance(project).getSelectedFiles());
      for (VirtualFile file : ArrayUtil.reverseArray(EditorHistoryManager.getInstance(project).getFiles())) {
        if (StringUtil.isEmptyOrSpaces(pattern) || matcher.matches(file.getName())) {
          if (!files.contains(file) && !selected.contains(file)) {
            files.add(file);
          }
        }
        if (files.size() > MAX_RECENT_FILES) break;
      }

      if (files.size() > 0) {
        myAlreadyAddedFiles.addAll(files);

        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            if (isCanceled()) return;
            myListModel.titleIndex.recentFiles = myListModel.size();
            for (Object file : files) {
              myListModel.addElement(file);
            }
            updatePopup();
          }
        });
      }
    }

    private boolean isCanceled() {
      return myProgressIndicator.isCanceled() || myDone.isRejected();
    }

    private synchronized void buildTopHit(String pattern) {
      final List<Object> elements = new ArrayList<Object>();
      final HistoryItem history = myHistoryItem;
      if (history != null) {
        final HistoryType type = parseHistoryType(history.type);
        if (type != null) {
          switch (type){
            case PSI:
              if (!DumbService.isDumb(project)) {
                ApplicationManager.getApplication().runReadAction(new Runnable() {
                  public void run() {

                    final int i = history.fqn.indexOf("://");
                    if (i != -1) {
                      final String langId = history.fqn.substring(0, i);
                      final Language language = Language.findLanguageByID(langId);
                      final String psiFqn = history.fqn.substring(i + 3);
                      if (language != null) {
                        final PsiElement psi =
                          LanguagePsiElementExternalizer.INSTANCE.forLanguage(language).findByQualifiedName(project, psiFqn);
                        if (psi != null) {
                          elements.add(psi);
                          final PsiFile psiFile = psi.getContainingFile();
                          if (psiFile != null) {
                            final VirtualFile file = psiFile.getVirtualFile();
                            if (file != null) {
                              myAlreadyAddedFiles.add(file);
                            }
                          }
                        }
                      }
                    }
                  }
                });
              }
              break;
            case FILE:
              final VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(history.fqn);
              if (file != null) {
                elements.add(file);
              }
              break;
            case SETTING:
              break;
            case ACTION:
              final AnAction action = ActionManager.getInstance().getAction(history.fqn);
              if (action != null) {
                elements.add(action);
                myAlreadyAddedActions.add(action);
              }
              break;
            case RUN_CONFIGURATION:
              if (!DumbService.isDumb(project)) {
                ApplicationManager.getApplication().runReadAction(new Runnable() {
                  public void run() {
                    final ChooseRunConfigurationPopup.ItemWrapper runConfiguration = getRunConfigurationByName(history.fqn);
                    if (runConfiguration != null) {
                      elements.add(runConfiguration);
                    }
                  }
                });
              }
              break;
          }
        }
      }
      final Consumer<Object> consumer = new Consumer<Object>() {
        @Override
        public void consume(Object o) {
          if (isSetting(o) || isVirtualFile(o) || isActionValue(o) || o instanceof PsiElement || o instanceof OptionsTopHitProvider) {
            if (o instanceof AnAction && myAlreadyAddedActions.contains(o)) {
              return;
            }
            elements.add(o);
          }
        }
      };

      if (pattern.startsWith("#") && !pattern.contains(" ")) {
        String id = pattern.substring(1);
        final HashSet<String> ids = new HashSet<String>();
        for (SearchTopHitProvider provider : SearchTopHitProvider.EP_NAME.getExtensions()) {
          check();
          if (provider instanceof OptionsTopHitProvider) {
            final String providerId = ((OptionsTopHitProvider)provider).getId();
            if (!ids.contains(providerId) && StringUtil.startsWithIgnoreCase(providerId, id)) {
              consumer.consume(provider);
              ids.add(providerId);
            }
          }
        }
      } else {
        final ActionManager actionManager = ActionManager.getInstance();
        final List<String> actions = AbbreviationManager.getInstance().findActions(pattern);
        for (String actionId : actions) {
          consumer.consume(actionManager.getAction(actionId));
        }

        for (SearchTopHitProvider provider : SearchTopHitProvider.EP_NAME.getExtensions()) {
          check();
          if (provider instanceof OptionsTopHitProvider && !((OptionsTopHitProvider)provider).isEnabled(project)) {
            continue;
          }
          provider.consumeTopHits(pattern, consumer, project);
        }
      }
      if (elements.size() > 0) {
        SwingUtilities.invokeLater(new Runnable() {
          @Override
          public void run() {
            if (isCanceled()) return;

            for (Object element : new ArrayList(elements)) {
              if (element instanceof AnAction) {
                if (!isEnabled((AnAction)element)) {
                  elements.remove(element);
                }
                if (isCanceled()) return;
              }
            }
            if (isCanceled() || elements.isEmpty()) return;
            myListModel.titleIndex.topHit = myListModel.size();
            for (Object element : ContainerUtil.getFirstItems(elements, MAX_TOP_HIT)) {
              myListModel.addElement(element);
            }
          }
        });
      }
    }

    protected boolean isEnabled(final AnAction action) {
      if (myDisabledActions.contains(action)) return false;
      final AnActionEvent e = new AnActionEvent(myActionEvent.getInputEvent(),
                                                myActionEvent.getDataContext(),
                                                myActionEvent.getPlace(),
                                                action.getTemplatePresentation().clone(),
                                                myActionEvent.getActionManager(),
                                                myActionEvent.getModifiers());

      UIUtil.invokeAndWaitIfNeeded(new Runnable() {
        @Override
        public void run() {
          ActionUtil.performDumbAwareUpdate(action, e, false);
        }
      });
      final Presentation presentation = e.getPresentation();
      final boolean enabled = presentation.isEnabled() && presentation.isVisible() && !StringUtil.isEmpty(presentation.getText());
      if (!enabled) {
        myDisabledActions.add(action);
      }
      return enabled;
    }

    private synchronized void checkModelsUpToDate() {
      if (myClassModel == null) {
        myClassModel = new GotoClassModel2(project);
        myFileModel = new GotoFileModel(project);
        mySymbolsModel = new GotoSymbolModel2(project);
        myFileChooseByName = ChooseByNamePopup.createPopup(project, myFileModel, (PsiElement)null);
        myClassChooseByName = ChooseByNamePopup.createPopup(project, myClassModel, (PsiElement)null);
        mySymbolsChooseByName = ChooseByNamePopup.createPopup(project, mySymbolsModel, (PsiElement)null);
        project.putUserData(ChooseByNamePopup.CHOOSE_BY_NAME_POPUP_IN_PROJECT_KEY, null);
        myActionProvider = createActionProvider();
        myConfigurables.clear();
        fillConfigurablesIds(null, ShowSettingsUtilImpl.getConfigurables(project, true));
      }
      if (myStructureModel == null && myFileEditor != null && Registry.is("search.everywhere.structure")) {
        runReadAction(new Runnable() {
          public void run() {
            StructureViewBuilder structureViewBuilder = myFileEditor.getStructureViewBuilder();
            if (structureViewBuilder == null) return;
            StructureView structureView = structureViewBuilder.createStructureView(myFileEditor, project);
            myStructureModel = structureView.getTreeModel();
          }
        }, true);
      }
    }

    private void buildModelFromRecentFiles() {
      buildRecentFiles("");
    }

    private GotoActionItemProvider createActionProvider() {
      GotoActionModel model = new GotoActionModel(project, myFocusComponent, myEditor, myFile) {
        @Override
        protected MatchMode actionMatches(String pattern, @NotNull AnAction anAction) {
          String text = anAction.getTemplatePresentation().getText();
          return text != null && NameUtil.buildMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE)
                   .matches(text) ? MatchMode.NAME : MatchMode.NONE;
        }
      };
      return new GotoActionItemProvider(model);
    }

    @SuppressWarnings("SSBasedInspection")
    private void updatePopup() {
      check();
      SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
          myListModel.update();
          myList.revalidate();
          myList.repaint();

          myRenderer.recalculateWidth();
          if (myBalloon == null || myBalloon.isDisposed()) {
            return;
          }
          if (myPopup == null || !myPopup.isVisible()) {
            ScrollingUtil.installActions(myList, getField().getTextEditor());
            JBScrollPane content = new JBScrollPane(myList) {
              {
                if (UIUtil.isUnderDarcula()) {
                  setBorder(null);
                }
              }
              @Override
              public Dimension getPreferredSize() {
                Dimension size = super.getPreferredSize();
                Dimension listSize = myList.getPreferredSize();
                if (size.height > listSize.height || myList.getModel().getSize() == 0) {
                  size.height = Math.max(JBUI.scale(30), listSize.height);
                }

                if (size.width < myBalloon.getSize().width) {
                  size.width = myBalloon.getSize().width;
                }

                return size;
              }
            };
            content.setMinimumSize(new Dimension(myBalloon.getSize().width, 30));
            final ComponentPopupBuilder builder = JBPopupFactory.getInstance()
              .createComponentPopupBuilder(content, null);
            myPopup = builder
              .setRequestFocus(false)
              .setCancelKeyEnabled(false)
              .setCancelCallback(new Computable<Boolean>() {
                @Override
                public Boolean compute() {
                  return myBalloon == null || myBalloon.isDisposed() || (!getField().getTextEditor().hasFocus() && !mySkipFocusGain);
                }
              })
              .setShowShadow(false)
              .setShowBorder(false)
              .createPopup();
            //myPopup.setMinimumSize(new Dimension(myBalloon.getSize().width, 30));
            myPopup.getContent().setBorder(null);
            Disposer.register(myPopup, new Disposable() {
              @Override
              public void dispose() {
                ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
                  public void run() {
                    resetFields();
                    myNonProjectCheckBox.setSelected(false);
                    //noinspection SSBasedInspection
                    SwingUtilities.invokeLater(new Runnable() {
                      @Override
                      public void run() {
                        ActionToolbarImpl.updateAllToolbarsImmediately();
                      }
                    });
                    if (myActionEvent != null && myActionEvent.getInputEvent() instanceof MouseEvent) {
                      final Component component = myActionEvent.getInputEvent().getComponent();
                      if (component != null) {
                        final JLabel label = UIUtil.getParentOfType(JLabel.class, component);
                        if (label != null) {
                          SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                              label.setIcon(AllIcons.Actions.FindPlain);
                            }
                          });
                        }
                      }
                    }
                    myActionEvent = null;
                  }
                });
              }
            });
            updatePopupBounds();
            myPopup.show(new RelativePoint(getField().getParent(), new Point(0, getField().getParent().getHeight())));

            ActionManager.getInstance().addAnActionListener(new AnActionListener.Adapter() {
              @Override
              public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
                if (action instanceof TextComponentEditorAction) {
                  return;
                }
                if (myPopup!=null) {
                  myPopup.cancel();
                }
              }
            }, myPopup);
          }
          else {
            myList.revalidate();
            myList.repaint();
          }
          ScrollingUtil.ensureSelectionExists(myList);
          if (myList.getModel().getSize() > 0) {
            updatePopupBounds();
          }
        }
      });
    }

    public ActionCallback cancel() {
      myProgressIndicator.cancel();
      myDone.setRejected();
      return myDone;
    }

    public ActionCallback insert(final int index, final WidgetID id) {
       ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
        public void run() {
          runReadAction(new Runnable() {
            @Override
            public void run() {
              try {
                final SearchResult result
                  = id == WidgetID.CLASSES ? getClasses(pattern, showAll.get(), DEFAULT_MORE_STEP_COUNT, myClassChooseByName)
                  : id == WidgetID.FILES ? getFiles(pattern, showAll.get(), DEFAULT_MORE_STEP_COUNT, myFileChooseByName)
                  : id == WidgetID.RUN_CONFIGURATIONS ? getConfigurations(pattern, DEFAULT_MORE_STEP_COUNT)
                  : id == WidgetID.SYMBOLS ? getSymbols(pattern, DEFAULT_MORE_STEP_COUNT, showAll.get(), mySymbolsChooseByName)
                  : id == WidgetID.ACTIONS ? getActionsOrSettings(pattern, DEFAULT_MORE_STEP_COUNT, true)
                  : id == WidgetID.SETTINGS ? getActionsOrSettings(pattern, DEFAULT_MORE_STEP_COUNT, false)
                  : new SearchResult();

                check();
                SwingUtilities.invokeLater(new Runnable() {
                  @Override
                  public void run() {
                    try {
                      int shift = 0;
                      int i = index+1;
                      for (Object o : result) {
                        //noinspection unchecked
                        myListModel.insertElementAt(o, i);
                        shift++;
                        i++;
                      }
                      MoreIndex moreIndex = myListModel.moreIndex;
                      myListModel.titleIndex.shift(index, shift);
                      moreIndex.shift(index, shift);

                      if (!result.needMore) {
                        switch (id) {
                          case CLASSES: moreIndex.classes = -1; break;
                          case FILES: moreIndex.files = -1; break;
                          case ACTIONS: moreIndex.actions = -1; break;
                          case SETTINGS: moreIndex.settings = -1; break;
                          case SYMBOLS: moreIndex.symbols = -1; break;
                          case RUN_CONFIGURATIONS: moreIndex.runConfigurations = -1; break;
                        }
                      }
                      ScrollingUtil.selectItem(myList, index);
                      myDone.setDone();
                    }
                    catch (Exception e) {
                      myDone.setRejected();
                    }
                  }
                });
              }
              catch (Exception e) {
                myDone.setRejected();
              }
            }
          }, true);
        }
      });
      return myDone;
    }

    public ActionCallback start() {
      ApplicationManager.getApplication().executeOnPooledThread(this);
      return myDone;
    }
  }

  private static boolean shouldSkipPattern(String pattern) {
    return Registry.is("search.everywhere.pattern.checking") && StringUtil.split(pattern, ".").size() == 2;
  }

  protected void resetFields() {
    if (myBalloon!= null) {
      myBalloon.cancel();
      myBalloon = null;
    }
    myCurrentWorker.doWhenProcessed(new Runnable() {
      @Override
      public void run() {
        myFileModel = null;
        if (myFileChooseByName != null) {
          myFileChooseByName.close(false);
          myFileChooseByName = null;
        }
        if (myClassChooseByName != null) {
          myClassChooseByName.close(false);
          myClassChooseByName = null;
        }
        if (mySymbolsChooseByName != null) {
          mySymbolsChooseByName.close(false);
          mySymbolsChooseByName = null;
        }
        final Object lock = myCalcThread;
        if (lock != null) {
          synchronized (lock) {
            myClassModel = null;
            myActionProvider = null;
            mySymbolsModel = null;
            myConfigurables.clear();
            myFocusComponent = null;
            myContextComponent = null;
            myFocusOwner = null;
            myRenderer.myProject = null;
            myPopup = null;
            myHistoryIndex = 0;
            myPopupActualWidth = 0;
            myCurrentWorker = ActionCallback.DONE;
            showAll.set(false);
            myCalcThread = null;
            myEditor = null;
            myFileEditor = null;
            myStructureModel = null;
            myDisabledActions.clear();
          }
        }
      }
    });
    mySkipFocusGain = false;
  }

  private void updatePopupBounds() {
    if (myPopup == null || !myPopup.isVisible()) {
      return;
    }
    final Container parent = getField().getParent();
    final Dimension size = myList.getParent().getParent().getPreferredSize();
    size.width = myPopupActualWidth - 2;
    if (size.width + 2 < parent.getWidth()) {
      size.width = parent.getWidth();
    }
    if (myList.getItemsCount() == 0) {
      size.height = JBUI.scale(30);
    }
    Dimension sz = new Dimension(size.width, myList.getPreferredSize().height);
    if (!SystemInfo.isMac) {
      if ((sz.width > POPUP_MAX_WIDTH || sz.height > POPUP_MAX_WIDTH)) {
        final JBScrollPane pane = new JBScrollPane();
        final int extraWidth = pane.getVerticalScrollBar().getWidth() + 1;
        final int extraHeight = pane.getHorizontalScrollBar().getHeight() + 1;
        sz = new Dimension(Math.min(POPUP_MAX_WIDTH, Math.max(getField().getWidth(), sz.width + extraWidth)), Math.min(POPUP_MAX_WIDTH, sz.height + extraHeight));
        sz.width += 20;
        sz.height+=2;
      } else {
        sz.width+=2;
        sz.height+=2;
      }
    }
    sz.width = Math.max(sz.width, myPopup.getSize().width);
    myPopup.setSize(sz);
    if (myActionEvent != null && myActionEvent.getInputEvent() == null) {
      final Point p = parent.getLocationOnScreen();
      p.y += parent.getHeight();
      if (parent.getWidth() < sz.width) {
        p.x -= sz.width - parent.getWidth();
      }
      myPopup.setLocation(p);
    } else {
      try {
        adjustPopup();
      }
      catch (Exception ignore) {}
    }
  }

  private void adjustPopup() {
//    new PopupPositionManager.PositionAdjuster(getField().getParent(), 0).adjust(myPopup, PopupPositionManager.Position.BOTTOM);
    final Dimension d = PopupPositionManager.PositionAdjuster.getPopupSize(myPopup);
    final JComponent myRelativeTo = myBalloon.getContent();
    Point myRelativeOnScreen = myRelativeTo.getLocationOnScreen();
    Rectangle screen = ScreenUtil.getScreenRectangle(myRelativeOnScreen);
    Rectangle popupRect = null;
    Rectangle r = new Rectangle(myRelativeOnScreen.x, myRelativeOnScreen.y + myRelativeTo.getHeight(), d.width, d.height);

      if (screen.contains(r)) {
        popupRect = r;
      }

    if (popupRect != null) {
      Point location = new Point(r.x, r.y);
      if (!location.equals(myPopup.getLocationOnScreen())) {
        myPopup.setLocation(location);
      }
    }
    else {
      if (r.y + d.height > screen.y + screen.height) {
        r.height =  screen.y + screen.height - r.y - 2;
      }
      if (r.width > screen.width) {
        r.width = screen.width - 50;
      }
      if (r.x + r.width > screen.x + screen.width) {
        r.x = screen.x + screen.width - r.width - 2;
      }

      myPopup.setSize(r.getSize());
      myPopup.setLocation(r.getLocation());
    }

  }

  private static boolean isToolWindowAction(Object o) {
    return isActionValue(o)
           && o instanceof GotoActionModel.ActionWrapper
           && ((GotoActionModel.ActionWrapper)o).getAction() instanceof ActivateToolWindowAction;
  }

  private void fillConfigurablesIds(String pathToParent, Configurable[] configurables) {
    for (Configurable configurable : configurables) {
      if (configurable instanceof SearchableConfigurable) {
        final String id = ((SearchableConfigurable)configurable).getId();
        String name = configurable.getDisplayName();
        if (pathToParent != null) {
          name = pathToParent + " -> " + name;
        }
        myConfigurables.put(id, name);
        if (configurable instanceof SearchableConfigurable.Parent) {
          fillConfigurablesIds(name, ((SearchableConfigurable.Parent)configurable).getConfigurables());
        }
      }
    }
  }

  static class MoreIndex {
    volatile int classes = -1;
    volatile int files = -1;
    volatile int actions = -1;
    volatile int settings = -1;
    volatile int symbols = -1;
    volatile int runConfigurations = -1;
    volatile int structure = -1;

    public void shift(int index, int shift) {
      if (runConfigurations >= index) runConfigurations += shift;
      if (classes >= index) classes += shift;
      if (files >= index) files += shift;
      if (symbols >= index) symbols += shift;
      if (actions >= index) actions += shift;
      if (settings >= index) settings += shift;
      if (structure >= index) structure += shift;
    }
  }

  static class TitleIndex {
    volatile int topHit = -1;
    volatile int recentFiles = -1;
    volatile int runConfigurations = -1;
    volatile int classes = -1;
    volatile int structure = -1;
    volatile int files = -1;
    volatile int actions = -1;
    volatile int settings = -1;
    volatile int toolWindows = -1;
    volatile int symbols = -1;

    final String gotoClassTitle;
    final String gotoFileTitle;
    final String gotoActionTitle;
    final String gotoSettingsTitle;
    final String gotoRecentFilesTitle;
    final String gotoRunConfigurationsTitle;
    final String gotoSymbolTitle;
    final String gotoStructureTitle;
    static final String toolWindowsTitle = "Tool Windows";

    TitleIndex() {
      String gotoClass = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("GotoClass"));
      gotoClassTitle = StringUtil.isEmpty(gotoClass) ? "Classes" : "Classes (" + gotoClass + ")";
      String gotoFile = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("GotoFile"));
      gotoFileTitle = StringUtil.isEmpty(gotoFile) ? "Files" : "Files (" + gotoFile + ")";
      String gotoAction = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("GotoAction"));
      gotoActionTitle = StringUtil.isEmpty(gotoAction) ? "Actions" : "Actions (" + gotoAction + ")";
      String gotoSettings = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("ShowSettings"));
      gotoSettingsTitle = StringUtil.isEmpty(gotoAction) ? ShowSettingsUtil.getSettingsMenuName() : ShowSettingsUtil.getSettingsMenuName() + " (" + gotoSettings + ")";
      String gotoRecentFiles = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("RecentFiles"));
      gotoRecentFilesTitle = StringUtil.isEmpty(gotoRecentFiles) ? "Recent Files" : "Recent Files (" + gotoRecentFiles + ")";
      String gotoSymbol = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("GotoSymbol"));
      gotoSymbolTitle = StringUtil.isEmpty(gotoSymbol) ? "Symbols" : "Symbols (" + gotoSymbol + ")";
      String gotoRunConfiguration = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("ChooseDebugConfiguration"));
      if (StringUtil.isEmpty(gotoRunConfiguration)) {
        gotoRunConfiguration = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("ChooseRunConfiguration"));
      }
      gotoRunConfigurationsTitle = StringUtil.isEmpty(gotoRunConfiguration) ? "Run Configurations" : "Run Configurations (" + gotoRunConfiguration + ")";
      String gotoStructure = KeymapUtil.getFirstKeyboardShortcutText(ActionManager.getInstance().getAction("FileStructurePopup"));
      gotoStructureTitle = StringUtil.isEmpty(gotoStructure) ? "File Structure" : "File Structure (" + gotoStructure + ")";
    }

    String getTitle(int index) {
      if (index == topHit) return index == 0 ? "Top Hit" : "Top Hits";
      if (index == recentFiles) return gotoRecentFilesTitle;
      if (index == structure) return gotoStructureTitle;
      if (index == runConfigurations) return gotoRunConfigurationsTitle;
      if (index == classes) return gotoClassTitle;
      if (index == files) return gotoFileTitle;
      if (index == toolWindows) return toolWindowsTitle;
      if (index == actions) return gotoActionTitle;
      if (index == settings) return gotoSettingsTitle;
      if (index == symbols) return gotoSymbolTitle;
      return null;
    }

    public void clear() {
      topHit = -1;
      runConfigurations = -1;
      recentFiles = -1;
      classes = -1;
      files = -1;
      structure = -1;
      actions = -1;
      settings = -1;
      toolWindows = -1;
    }

    public void shift(int index, int shift) {
      if (toolWindows != - 1 && toolWindows > index) toolWindows += shift;
      if (settings != - 1 && settings > index) settings += shift;
      if (actions != - 1 && actions > index) actions += shift;
      if (files != - 1 && files > index) files += shift;
      if (structure != - 1 && structure > index) structure += shift;
      if (classes != - 1 && classes > index) classes += shift;
      if (runConfigurations != - 1 && runConfigurations > index) runConfigurations += shift;
      if (symbols != - 1 && symbols > index) symbols += shift;
    }
  }

  static class SearchResult extends ArrayList<Object> {
    boolean needMore;
  }

  @SuppressWarnings("unchecked")
  private static class SearchListModel extends DefaultListModel {
    @SuppressWarnings("UseOfObsoleteCollectionType")
    Vector myDelegate;

    volatile TitleIndex titleIndex = new TitleIndex();
    volatile MoreIndex moreIndex = new MoreIndex();

    private SearchListModel() {
      super();
      myDelegate = ReflectionUtil.getField(DefaultListModel.class, this, Vector.class, "delegate");
    }

    int next(int index) {
      int[] all = getAll();
      Arrays.sort(all);
      for (int next : all) {
        if (next > index) return next;
      }
      return 0;
    }

    int[] getAll() {
      return new int[]{
        titleIndex.topHit,
        titleIndex.recentFiles,
        titleIndex.structure,
        titleIndex.runConfigurations,
        titleIndex.classes,
        titleIndex.files,
        titleIndex.actions,
        titleIndex.settings,
        titleIndex.toolWindows,
        titleIndex.symbols,
        moreIndex.classes,
        moreIndex.actions,
        moreIndex.files,
        moreIndex.settings,
        moreIndex.symbols,
        moreIndex.runConfigurations,
        moreIndex.structure
      };
    }

    int prev(int index) {
      int[] all = getAll();
      Arrays.sort(all);
      for (int i = all.length-1; i >= 0; i--) {
        if (all[i] != -1 && all[i] < index) return all[i];
      }
      return all[all.length - 1];
    }

    @Override
    public void addElement(Object obj) {
      myDelegate.add(obj);
    }

    public void update() {
      fireContentsChanged(this, 0, getSize() - 1);
    }
  }

  static class More extends JPanel {
    static final More instance = new More();
    final JLabel label = new JLabel("    ... more   ");

    private More() {
      super(new BorderLayout());
      add(label, BorderLayout.CENTER);
    }

    static More get(boolean isSelected) {
      instance.setBackground(UIUtil.getListBackground(isSelected));
      instance.label.setForeground(UIUtil.getLabelDisabledForeground());
      instance.label.setFont(getTitleFont());
      instance.label.setBackground(UIUtil.getListBackground(isSelected));
      return instance;
    }
  }

  private static JComponent createTitle(String titleText) {
    JLabel titleLabel = new JLabel(titleText);
    titleLabel.setFont(getTitleFont());
    titleLabel.setForeground(UIUtil.getLabelDisabledForeground());
    final Color bg = UIUtil.getListBackground();
    SeparatorComponent separatorComponent =
      new SeparatorComponent(titleLabel.getPreferredSize().height / 2, new JBColor(Gray._220, Gray._80), null);

    JPanel result = new JPanel(new BorderLayout(5, 10));
    result.add(titleLabel, BorderLayout.WEST);
    result.add(separatorComponent, BorderLayout.CENTER);
    result.setBackground(bg);

    return result;
  }

  private enum HistoryType {PSI, FILE, SETTING, ACTION, RUN_CONFIGURATION}

  @Nullable
  private static HistoryType parseHistoryType(@Nullable String name) {
    try {
      return HistoryType.valueOf(name);
    } catch (Exception e) {
      return null;
    }
  }

  private static class HistoryItem {
    final String pattern, type, fqn;

    private HistoryItem(String pattern, String type, String fqn) {
      this.pattern = pattern;
      this.type = type;
      this.fqn = fqn;
    }

    public String toString() {
      return pattern + "\t" + type + "\t" + fqn;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      HistoryItem item = (HistoryItem)o;

      if (!pattern.equals(item.pattern)) return false;

      return true;
    }

    @Override
    public int hashCode() {
      return pattern.hashCode();
    }
  }
}

