| /* |
| * 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.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.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_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<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 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("<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; |
| 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)); |
| DataManager.registerDataProvider(panel, this); |
| 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, e.getProject()); |
| 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<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; |
| } |
| }; |
| } |
| } |
| } |
| 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().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<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..."); |
| |
| //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<ActivateToolWindowAction> actions = new ArrayList<ActivateToolWindowAction>(); |
| for (ActivateToolWindowAction action : ToolWindowsGroup.getToolWindowActions(project)) { |
| String text = action.getTemplatePresentation().getText(); |
| if (text != null && StringUtil.startsWithIgnoreCase(text, pattern)) { |
| actions.add(action); |
| |
| if (actions.size() == MAX_TOOL_WINDOWS) { |
| break; |
| } |
| } |
| } |
| |
| check(); |
| |
| if (actions.isEmpty()) { |
| return; |
| } |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| myListModel.titleIndex.toolWindows = myListModel.size(); |
| for (Object toolWindow : actions) { |
| myListModel.addElement(toolWindow); |
| } |
| } |
| }); |
| } |
| |
| private SearchResult getActionsOrSettings(final String pattern, final int max, final boolean actions) { |
| final SearchResult result = new SearchResult(); |
| 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 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<Object>() { |
| @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<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); |
| } |
| 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<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() { |
| public void run() { |
| |
| final int i = history.fqn.indexOf("://"); |
| if (i != -1) { |
| final String langId = history.fqn.substring(0, i); |
| final Language language = Language.findLanguageByID(langId); |
| final String psiFqn = history.fqn.substring(i + 3); |
| if (language != null) { |
| final PsiElement psi = |
| LanguagePsiElementExternalizer.INSTANCE.forLanguage(language).findByQualifiedName(project, psiFqn); |
| if (psi != null) { |
| elements.add(psi); |
| final PsiFile psiFile = psi.getContainingFile(); |
| if (psiFile != null) { |
| final VirtualFile file = psiFile.getVirtualFile(); |
| if (file != null) { |
| myAlreadyAddedFiles.add(file); |
| } |
| } |
| } |
| } |
| } |
| } |
| }); |
| } |
| break; |
| case FILE: |
| final VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(history.fqn); |
| if (file != null) { |
| elements.add(file); |
| } |
| break; |
| case SETTING: |
| break; |
| case ACTION: |
| final AnAction action = ActionManager.getInstance().getAction(history.fqn); |
| if (action != null) { |
| elements.add(action); |
| myAlreadyAddedActions.add(action); |
| } |
| break; |
| case RUN_CONFIGURATION: |
| if (!DumbService.isDumb(project)) { |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| final ChooseRunConfigurationPopup.ItemWrapper runConfiguration = getRunConfigurationByName(history.fqn); |
| if (runConfiguration != null) { |
| elements.add(runConfiguration); |
| } |
| } |
| }); |
| } |
| break; |
| } |
| } |
| } |
| final Consumer<Object> consumer = new Consumer<Object>() { |
| @Override |
| public void consume(Object o) { |
| if (isSetting(o) || isVirtualFile(o) || isActionValue(o) || o instanceof PsiElement) { |
| if (o instanceof AnAction && myAlreadyAddedActions.contains(o)) { |
| return; |
| } |
| elements.add(o); |
| } |
| } |
| }; |
| |
| 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(); |
| 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<Boolean>() { |
| @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<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.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(); |
| } |
| } |
| } |
| |