/* * Copyright 2000-2014 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.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.search.BooleanOptionDescription; import com.intellij.ide.ui.search.OptionDescription; import com.intellij.ide.util.DefaultPsiElementCellRenderer; 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.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.text.MatcherHolder; 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 consulo.fileTypes.impl.VfsIconUtil; import consulo.ide.actions.QualifiedNameProviders; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.EmptyBorder; 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 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 = new JPanel(new BorderLayout()) { @Override protected void paintComponent(Graphics g) { if (myBalloon != null && !myBalloon.isDisposed() && myActionEvent != null && myActionEvent.getInputEvent() instanceof MouseEvent) { final Gradient gradient = getGradientColors(); ((Graphics2D)g).setPaint(new GradientPaint(0, 0, gradient.getStartColor(), 0, getHeight(), gradient.getEndColor())); g.fillRect(0, 0, getWidth(), getHeight()); } else { super.paintComponent(g); } } }; 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); label.setIcon(AllIcons.Actions.FindWhite); 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() { @Override public void run() { onFocusLost(); } }); } private void updateComponents() { myRenderer = new MyListRenderer(); myList = new JBList() { @Override public Dimension getPreferredSize() { final Dimension size = super.getPreferredSize(); return new Dimension(Math.max(myBalloon.getSize().width, Math.min(size.width - 2, POPUP_MAX_WIDTH)), myList.isEmpty() ? 60 : 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; IdeFocusManager.getGlobalInstance().doForceFocusWhenFocusSettlesDown(getField()); //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; } search.setText(""); 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(""); } @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; IdeFocusManager.getGlobalInstance().doForceFocusWhenFocusSettlesDown(editor); 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; } ListScrollingUtil.ensureIndexIsVisible(myList, more, forward ? 1 : -1); ListScrollingUtil.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 Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().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(); IdeFocusManager.getGlobalInstance().doForceFocusWhenFocusSettlesDown(getField()); 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() { @Override public void run() { NavigationUtil.activateFileWithPsiElement((PsiElement)value, true); } }; return; } else if (isVirtualFile(value)) { onDone = new Runnable() { @Override 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.getInstance().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 = new AnActionEvent(me, DataManager.getInstance().getDataContext(myFocusOwner), ActionPlaces.UNKNOWN, getTemplatePresentation(), ActionManager.getInstance(), 0); } if (e == null) return; final Project project = e.getProject(); if (project == null) return; 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() + ") "); 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(new EmptyBorder(0, 0, 0, 0)); 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() { rebuildList(myPopupField.getText()); } }); Component parent = UIUtil.findUltimateParent(window); registerDataProvider(panel, project); final RelativePoint showPoint; if (me != null) { final Component label = me.getComponent(); final Component button = label.getParent(); assert button != null; showPoint = new RelativePoint(button, new Point(button.getWidth() - panel.getPreferredSize().width, button.getHeight())); } else { 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 = QualifiedNameProviders.getQualifiedNameFromProviders(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); new DumbAwareAction() { @Override public void actionPerformed(AnActionEvent e) { if (myBalloon != null && myBalloon.isVisible()) { myBalloon.cancel(); } if (myPopup != null && myPopup.isVisible()) { myPopup.cancel(); } } }.registerCustomShortcutSet(CustomShortcutSet.fromString("ESCAPE"), 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().putClientProperty("JTextField.Search.noBorderRing", Boolean.TRUE); if (UIUtil.isUnderDarkBuildInLaf()) { 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); private String myLocationString; private DefaultPsiElementCellRenderer myPsiRenderer = new DefaultPsiElementCellRenderer() { { setFocusBorderEnabled(false); } }; private Icon myLocationIcon; private Project myProject; private JPanel myMainPanel = new JPanel(new BorderLayout()); private JLabel myTitle = new JLabel(); @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, pattern.toLowerCase().equals(pattern)); 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)) { MatcherHolder.associateMatcher(myFileRenderer, matcher); cmp = myFileRenderer.getListCellRendererComponent(list, file, index, isSelected, cellHasFocus); } else if (value instanceof PsiElement) { MatcherHolder.associateMatcher(myFileRenderer, matcher); cmp = myFileRenderer.getListCellRendererComponent(list, value, 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); 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(VfsIconUtil.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() <= JBUI.scale(16) && icon.getIconHeight() <= JBUI.scale(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") 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() { @Override public void run() { buildStructure(pattern); } }, true); updatePopup(); check(); buildToolWindows(pattern); check(); updatePopup(); check(); runReadAction(new Runnable() { @Override public void run() { buildRunConfigurations(pattern); } }, true); runReadAction(new Runnable() { @Override public void run() { buildClasses(pattern); } }, true); runReadAction(new Runnable() { @Override public void run() { buildFiles(pattern); } }, false); runReadAction(new Runnable() { @Override 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, true)) { 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)) { 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, 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, 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, ChooseByNamePopup chooseByNamePopup) { final SearchResult symbols = new SearchResult(); if (!Registry.is("search.everywhere.symbols")) { return symbols; } final GlobalSearchScope scope = GlobalSearchScope.projectScope(project); if (chooseByNamePopup == null) return symbols; final ChooseByNameItemProvider provider = chooseByNamePopup.getProvider(); provider.filterElements(chooseByNamePopup, pattern, false, myProgressIndicator, new Processor<Object>() { @Override public boolean process(Object o) { if (o instanceof PsiElement && !(((PsiElement)o).getParent() instanceof PsiFile)) { final PsiElement element = (PsiElement)o; final PsiFile file = element.getContainingFile(); if (!myListModel.contains(o) && //some elements are non-physical like DB columns (file == null || (file.getVirtualFile() != null && scope.accept(file.getVirtualFile())))) { symbols.add(o); } } symbols.needMore = symbols.size() == max; return !symbols.needMore; } }); return symbols; } private SearchResult getClasses(String pattern, boolean includeLibs, final int max, ChooseByNamePopup chooseByNamePopup) { final SearchResult classes = new SearchResult(); if (chooseByNamePopup == null) { 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 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) && (showAll.get() || 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; } }); 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); } } }); } } 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() { @Override 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) { Pair<PsiElement, QualifiedNameProvider> pair = QualifiedNameProviders.findElementByQualifiedName(psiFqn, project); final PsiElement psi = pair == null ? null : pair.getFirst(); 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() { @Override 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.equals("#")) { final HashSet<String> ids = new HashSet<String>(); for (SearchTopHitProvider provider : SearchTopHitProvider.EP_NAME.getExtensions()) { check(); if (provider instanceof OptionsTopHitProvider) { if (!ids.contains(((OptionsTopHitProvider)provider).getId())) { consumer.consume(provider); ids.add(((OptionsTopHitProvider)provider).getId()); } } } } 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) { final AnAction action = (AnAction)element; final AnActionEvent e = new AnActionEvent(myActionEvent.getInputEvent(), myActionEvent.getDataContext(), myActionEvent.getPlace(), action.getTemplatePresentation(), myActionEvent.getActionManager(), myActionEvent.getModifiers()); ActionUtil.performDumbAwareUpdate(action, e, false); final Presentation presentation = e.getPresentation(); if (!presentation.isEnabled() || !presentation.isVisible() || StringUtil.isEmpty(presentation.getText())) { 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); } } }); } } 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.buildConfigurables(project)); } if (myStructureModel == null && myFileEditor != null && Registry.is("search.everywhere.structure")) { runReadAction(new Runnable() { @Override 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()) { final ActionCallback callback = ListDelegationUtil.installKeyboardDelegation(getField().getTextEditor(), myList); JBScrollPane content = new JBScrollPane(myList); 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); } }).createPopup(); myPopup.setMinimumSize(new Dimension(myBalloon.getSize().width, 30)); myPopup.getContent().setBorder(new EmptyBorder(0, 0, 0, 0)); Disposer.register(myPopup, new Disposable() { @Override public void dispose() { callback.setDone(); resetFields(); myNonProjectCheckBox.setSelected(false); 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) { label.setIcon(AllIcons.Actions.FindPlain); } } } myActionEvent = null; } }); myPopup.show(new RelativePoint(getField().getParent(), new Point(0, getField().getParent().getHeight()))); //updatePopupBounds(); ActionManager.getInstance().addAnActionListener(new AnActionListener.Adapter() { @Override public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { if (action instanceof TextComponentEditorAction) { return; } myPopup.cancel(); } }, myPopup); } else { myList.revalidate(); myList.repaint(); } ListScrollingUtil.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() { @Override 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, DEFAULT_MORE_STEP_COUNT, myFileChooseByName) : id == WidgetID.RUN_CONFIGURATIONS ? getConfigurations(pattern, DEFAULT_MORE_STEP_COUNT) : id == WidgetID.SYMBOLS ? getSymbols(pattern, DEFAULT_MORE_STEP_COUNT, 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; } } ListScrollingUtil.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; } } 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; } } } }); 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 = 30; } Dimension sz = new Dimension(size.width, myList.getPreferredSize().height); 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) { myPopup.setLocation(new Point(r.x - 1, r.y)); } 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; } @Override 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(); } } }