/* * 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.actions.ChooseRunConfigurationPopup; import com.intellij.execution.actions.ExecutorProvider; import com.intellij.execution.executors.DefaultRunExecutor; 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.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.DefaultPsiElementCellRenderer; import com.intellij.ide.util.PropertiesComponent; import com.intellij.ide.util.gotoByName.*; 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.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.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.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.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; 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.text.Matcher; import com.intellij.util.ui.EmptyIcon; 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.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.event.DocumentEvent; import java.awt.*; import java.awt.event.*; import java.lang.reflect.Field; import java.util.*; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; /** * @author Konstantin Bulenkov */ @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized") public class SearchEverywhereAction extends AnAction implements CustomComponentAction, DumbAware{ 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_RECENT_FILES = 10; private static final int DEFAULT_MORE_STEP_COUNT = 15; public static final int MAX_SEARCH_EVERYWHERE_HISTORY = 50; 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 myConfigurables = new HashMap(); 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 Editor myEditor; 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() { 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.min(size.width - 2, POPUP_MAX_WIDTH), size.height); } }; 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("Search Everywhere
Press " + shortcutText + " to access
- Classes
- Files
- Tool Windows
- Actions
- Settings"); } 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; 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; } 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 SwingUtilities.invokeLater(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(); 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.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; myCurrentWorker.doWhenProcessed(new Runnable() { @Override public void run() { myCalcThread = new CalcThread(project, pattern, false); myPopupActualWidth = 0; myCurrentWorker = myCalcThread.start(); } }); } @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); 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; 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()); } @Override public Dimension getPreferredSize() { return new Dimension(410, super.getPreferredSize().height); } }; 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); myNonProjectCheckBox.setForeground(new JBColor(Gray._240, Gray._200)); myNonProjectCheckBox.setText("Include non-project items (" + getShortcut() + ")"); if (!NonProjectScopeDisablerEP.isSearchInNonProjectDisabled()) { topPanel.add(myNonProjectCheckBox, BorderLayout.EAST); } panel.add(myPopupField, BorderLayout.CENTER); panel.add(topPanel, BorderLayout.NORTH); panel.setBorder(IdeBorderFactory.createEmptyBorder(3, 5, 4, 5)); final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, editor); myBalloon = builder .setCancelOnClickOutside(true) .setModalContext(false) .setRequestFocus(true) .createPopup(); myBalloon.getContent().setBorder(new EmptyBorder(0,0,0,0)); final Window window = WindowManager.getInstance().suggestParentWindow(e.getProject()); //noinspection ConstantConditions e.getProject().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); 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(e.getProject()); focusManager.requestFocus(editor, true); FeatureUsageTracker.getInstance().triggerFeatureUsed(IdeActions.ACTION_SEARCH_EVERYWHERE); } 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 history = new ArrayList(); 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); } private void registerDataProvider(JPanel panel) { 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; } 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 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); } }; GotoFileCellRenderer myFileRenderer = new GotoFileCellRenderer(400); 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; PsiFile file = null; 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).findFile((VirtualFile)value)) != null)) { myFileRenderer.setPatternMatcher(matcher); cmp = myFileRenderer.getListCellRendererComponent(list, file == null ? value : file, index, isSelected, cellHasFocus); } else if (value instanceof PsiElement) { myPsiRenderer.setPatternMatcher(matcher); cmp = myPsiRenderer.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(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 { 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 myAlreadyAddedFiles = new ArrayList(); private final ArrayList myAlreadyAddedActions = new ArrayList(); 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..."); //noinspection unchecked myList.setModel(myListModel); } }); if (pattern.trim().length() == 0) { buildModelFromRecentFiles(); updatePopup(); return; } checkModelsUpToDate(); check(); buildTopHit(pattern); check(); buildRecentFiles(pattern); check(); 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); buildActionsAndSettings(pattern); updatePopup(); runReadAction(new Runnable() { public void run() { buildSymbols(pattern); } }, true); } catch (ProcessCanceledException ignore) { myDone.setRejected(); } catch (Exception e) { LOG.error(e); myDone.setRejected(); } finally { if (!isCanceled()) { 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) { final List actions = new ArrayList(); 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(); final MinusculeMatcher matcher = new MinusculeMatcher("*" +pattern, NameUtil.MatchingCaseSensitivity.NONE); if (myActionProvider == null) { myActionProvider = createActionProvider(); } myActionProvider.filterElements(pattern, true, new Processor() { @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 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(); 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) { if (pattern.indexOf('.') != -1) { //todo[kb] it's not a mistake. If we search for "*.png" or "index.xml" in SearchEverywhere //todo[kb] we don't want to see Java classes started with Png or Xml. This approach should be reworked someday. return; } check(); 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(); final GlobalSearchScope scope = GlobalSearchScope.projectScope(project); chooseByNamePopup.getProvider().filterElements(chooseByNamePopup, pattern, false, myProgressIndicator, new Processor() { @Override public boolean process(Object o) { if (o instanceof PsiElement) { 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() { @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); } 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) { return files; } final GlobalSearchScope scope = GlobalSearchScope.projectScope(project); chooseByNamePopup.getProvider().filterElements(chooseByNamePopup, pattern, true, myProgressIndicator, new Processor() { @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 files = new ArrayList(); final List 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 elements = new ArrayList(); 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 consumer = new Consumer() { @Override public void consume(Object o) { if (isSetting(o) || isVirtualFile(o) || isActionValue(o) || o instanceof PsiElement) { if (o instanceof AnAction && myAlreadyAddedActions.contains(o)) { return; } elements.add(o); } } }; final ActionManager actionManager = ActionManager.getInstance(); final List actions = AbbreviationManager.getInstance().findActions(pattern); for (String actionId : actions) { consumer.consume(actionManager.getAction(actionId)); } for (SearchTopHitProvider provider : SearchTopHitProvider.EP_NAME.getExtensions()) { check(); provider.consumeTopHits(pattern, consumer); } if (elements.size() > 0) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (isCanceled()) return; for (Object element : elements.toArray()) { 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 : elements) { 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.getConfigurables(project, 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); final ComponentPopupBuilder builder = JBPopupFactory.getInstance() .createComponentPopupBuilder(new JBScrollPane(myList), null); myPopup = builder .setRequestFocus(false) .setCancelKeyEnabled(false) .setCancelCallback(new Computable() { @Override public Boolean compute() { return myBalloon == null || myBalloon.isDisposed() || !getField().getTextEditor().hasFocus(); } }) .createPopup(); 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() { 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; } } } }); 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 = 70; } 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, 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; public void shift(int index, int shift) { if (runConfigurations >= index) runConfigurations += shift; if (classes >= index) classes += shift; if (files >= index) files += shift; if (actions >= index) actions += shift; if (settings >= index) settings += shift; if (symbols >= index) symbols += shift; } } static class TitleIndex { volatile int topHit = -1; volatile int recentFiles = -1; volatile int runConfigurations = -1; volatile int classes = -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; 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) ? "Preferences" : "Preferences (" + 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 getTitle(int index) { if (index == topHit) return index == 0 ? "Top Hit" : "Top Hits"; if (index == recentFiles) return gotoRecentFilesTitle; 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; 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 (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 { 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(); try { final Field field = DefaultListModel.class.getDeclaredField("delegate"); field.setAccessible(true); myDelegate = (Vector)field.get(this); } catch (NoSuchFieldException ignore) {} catch (IllegalAccessException ignore) {} } 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.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 }; } 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(); } } }