blob: 53d71d9794fa33d50b4c389935c01313dbdb6d21 [file] [log] [blame]
/*
* 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.util.gotoByName;
import com.intellij.Patches;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.find.findUsages.PsiElement2UsageTargetAdapter;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.actions.CopyReferenceAction;
import com.intellij.ide.actions.GotoFileAction;
import com.intellij.ide.actions.WindowAction;
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.openapi.Disposable;
import com.intellij.openapi.MnemonicHelper;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationAdapter;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.fileTypes.UnknownFileType;
import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.util.ProgressIndicatorBase;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.*;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.WindowManagerEx;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.MinusculeMatcher;
import com.intellij.psi.codeStyle.NameUtil;
import com.intellij.psi.statistics.StatisticsInfo;
import com.intellij.psi.statistics.StatisticsManager;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.ui.*;
import com.intellij.ui.components.JBList;
import com.intellij.ui.popup.AbstractPopup;
import com.intellij.ui.popup.PopupOwner;
import com.intellij.ui.popup.PopupPositionManager;
import com.intellij.ui.popup.PopupUpdateProcessor;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usages.*;
import com.intellij.util.Alarm;
import com.intellij.util.Consumer;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.indexing.FileBasedIndex;
import com.intellij.util.text.Matcher;
import com.intellij.util.text.MatcherHolder;
import com.intellij.util.ui.AsyncProcessIcon;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.GraphicsUtil;
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.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.PlainDocument;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class ChooseByNameBase {
private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.gotoByName.ChooseByNameBase");
protected final Project myProject;
protected final ChooseByNameModel myModel;
protected ChooseByNameItemProvider myProvider;
protected final String myInitialText;
private boolean mySearchInAnyPlace = false;
protected Component myPreviouslyFocusedComponent;
private boolean myInitialized;
protected final JPanelProvider myTextFieldPanel = new JPanelProvider();// Located in the layered pane
protected final MyTextField myTextField = new MyTextField();
private final CardLayout myCard = new CardLayout();
private final JPanel myCardContainer = new JPanel(myCard);
protected JCheckBox myCheckBox;
/**
* the tool area of the popup, it is just after card box
*/
private JComponent myToolArea;
protected JScrollPane myListScrollPane; // Located in the layered pane
private final MyListModel<Object> myListModel = new MyListModel<Object>();
protected final JList myList = new JBList(myListModel);
private final List<Pair<String, Integer>> myHistory = ContainerUtil.newArrayList();
private final List<Pair<String, Integer>> myFuture = ContainerUtil.newArrayList();
protected ChooseByNamePopupComponent.Callback myActionListener;
protected final Alarm myAlarm = new Alarm();
private final ListUpdater myListUpdater = new ListUpdater();
private boolean myDisposedFlag = false;
private ActionCallback myPostponedOkAction;
private final String[][] myNames = new String[2][];
private volatile CalcElementsThread myCalcElementsThread;
private static int VISIBLE_LIST_SIZE_LIMIT = 10;
private int myListSizeIncreasing = 30;
private int myMaximumListSizeLimit = 30;
@NonNls private static final String NOT_FOUND_IN_PROJECT_CARD = "syslib";
@NonNls private static final String NOT_FOUND_CARD = "nfound";
@NonNls private static final String CHECK_BOX_CARD = "chkbox";
@NonNls private static final String SEARCHING_CARD = "searching";
private final int myRebuildDelay;
private final Alarm myHideAlarm = new Alarm();
private boolean myShowListAfterCompletionKeyStroke = false;
protected JBPopup myTextPopup;
protected JBPopup myDropdownPopup;
private boolean myClosedByShiftEnter = false;
protected final int myInitialIndex;
private String myFindUsagesTitle;
private ShortcutSet myCheckBoxShortcut;
protected boolean myInitIsDone;
static final boolean ourLoadNamesEachTime = FileBasedIndex.ourEnableTracingOfKeyHashToVirtualFileMapping;
private boolean myFixLostTyping = true;
private boolean myAlwaysHasMore = false;
public boolean checkDisposed() {
if (myDisposedFlag && myPostponedOkAction != null && !myPostponedOkAction.isProcessed()) {
myPostponedOkAction.setRejected();
}
return myDisposedFlag;
}
public void setDisposed(boolean disposedFlag) {
myDisposedFlag = disposedFlag;
if (disposedFlag) {
setNamesSync(true, null);
setNamesSync(false, null);
}
}
private void setNamesSync(boolean checkboxState, @Nullable String[] value) {
synchronized (myNames) {
myNames[checkboxState ? 1 : 0] = value;
}
}
/**
* @param initialText initial text which will be in the lookup text field
*/
protected ChooseByNameBase(Project project, @NotNull ChooseByNameModel model, String initialText, PsiElement context) {
this(project, model, new DefaultChooseByNameItemProvider(context), initialText, 0);
}
@SuppressWarnings("UnusedDeclaration") // Used in MPS
protected ChooseByNameBase(Project project,
@NotNull ChooseByNameModel model,
@NotNull ChooseByNameItemProvider provider,
String initialText) {
this(project, model, provider, initialText, 0);
}
/**
* @param initialText initial text which will be in the lookup text field
*/
protected ChooseByNameBase(Project project,
@NotNull ChooseByNameModel model,
@NotNull ChooseByNameItemProvider provider,
String initialText,
final int initialIndex) {
myProject = project;
myModel = model;
myInitialText = initialText;
myProvider = provider;
myInitialIndex = initialIndex;
mySearchInAnyPlace = Registry.is("ide.goto.middle.matching") && model.useMiddleMatching();
myRebuildDelay = Registry.intValue("ide.goto.rebuild.delay");
myTextField.setText(myInitialText);
myInitIsDone = true;
}
public void setShowListAfterCompletionKeyStroke(boolean showListAfterCompletionKeyStroke) {
myShowListAfterCompletionKeyStroke = showListAfterCompletionKeyStroke;
}
public boolean isSearchInAnyPlace() {
return mySearchInAnyPlace;
}
public void setSearchInAnyPlace(boolean searchInAnyPlace) {
mySearchInAnyPlace = searchInAnyPlace;
}
public boolean isClosedByShiftEnter() {
return myClosedByShiftEnter;
}
public boolean isOpenInCurrentWindowRequested() {
return isClosedByShiftEnter();
}
/**
* Set tool area. The method may be called only before invoke.
*
* @param toolArea a tool area component
*/
public void setToolArea(JComponent toolArea) {
if (myToolArea != null) {
throw new IllegalStateException("Tool area is modifiable only before invoke()");
}
myToolArea = toolArea;
}
public void setFindUsagesTitle(@Nullable String findUsagesTitle) {
myFindUsagesTitle = findUsagesTitle;
}
public void invoke(final ChooseByNamePopupComponent.Callback callback,
final ModalityState modalityState,
boolean allowMultipleSelection) {
initUI(callback, modalityState, allowMultipleSelection);
}
@NotNull
public ChooseByNameModel getModel() {
return myModel;
}
public class JPanelProvider extends JPanel implements DataProvider {
private JBPopup myHint = null;
private boolean myFocusRequested = false;
JPanelProvider() {
}
@Override
public Object getData(String dataId) {
if (PlatformDataKeys.HELP_ID.is(dataId)) {
return myModel.getHelpId();
}
if (myCalcElementsThread != null) {
return null;
}
if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
Object element = getChosenElement();
if (element instanceof PsiElement) {
return element;
}
if (element instanceof DataProvider) {
return ((DataProvider)element).getData(dataId);
}
}
else if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
final List<Object> chosenElements = getChosenElements();
if (chosenElements != null) {
List<PsiElement> result = new ArrayList<PsiElement>(chosenElements.size());
for (Object element : chosenElements) {
if (element instanceof PsiElement) {
result.add((PsiElement)element);
}
}
return PsiUtilCore.toPsiElementArray(result);
}
}
else if (PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.is(dataId)) {
return getBounds();
}
return null;
}
public void registerHint(JBPopup h) {
if (myHint != null && myHint.isVisible() && myHint != h) {
myHint.cancel();
}
myHint = h;
}
public boolean focusRequested() {
boolean focusRequested = myFocusRequested;
myFocusRequested = false;
return focusRequested;
}
@Override
public void requestFocus() {
myFocusRequested = true;
}
public void unregisterHint() {
myHint = null;
}
public void hideHint() {
if (myHint != null) {
myHint.cancel();
}
}
@Nullable
public JBPopup getHint() {
return myHint;
}
public void updateHint(PsiElement element) {
if (myHint == null || !myHint.isVisible()) return;
final PopupUpdateProcessor updateProcessor = myHint.getUserData(PopupUpdateProcessor.class);
if (updateProcessor != null) {
updateProcessor.updatePopup(element);
}
}
public void repositionHint() {
if (myHint == null || !myHint.isVisible()) return;
PopupPositionManager.positionPopupInBestPosition(myHint, null, null);
}
}
/**
* @param modalityState - if not null rebuilds list in given {@link ModalityState}
*/
protected void initUI(final ChooseByNamePopupComponent.Callback callback,
final ModalityState modalityState,
final boolean allowMultipleSelection) {
myPreviouslyFocusedComponent = WindowManagerEx.getInstanceEx().getFocusedComponent(myProject);
myActionListener = callback;
myTextFieldPanel.setLayout(new BoxLayout(myTextFieldPanel, BoxLayout.Y_AXIS));
final JPanel hBox = new JPanel();
hBox.setLayout(new BoxLayout(hBox, BoxLayout.X_AXIS));
JPanel caption2Tools = new JPanel(new BorderLayout());
if (myModel.getPromptText() != null) {
JLabel label = new JLabel(myModel.getPromptText());
if (UIUtil.isUnderAquaLookAndFeel()) {
label.setBorder(new CompoundBorder(new EmptyBorder(0, 9, 0, 0), label.getBorder()));
}
label.setFont(UIUtil.getLabelFont().deriveFont(Font.BOLD));
caption2Tools.add(label, BorderLayout.WEST);
}
caption2Tools.add(hBox, BorderLayout.EAST);
myCardContainer.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 4)); // space between checkbox and filter/show all in view buttons
final String checkBoxName = myModel.getCheckBoxName();
myCheckBox = new JCheckBox(checkBoxName != null ? checkBoxName +
(myCheckBoxShortcut != null ? " (" +
KeymapUtil
.getShortcutsText(myCheckBoxShortcut.getShortcuts()) +
")" : "") : "");
myCheckBox.setAlignmentX(SwingConstants.RIGHT);
if (!SystemInfo.isMac) {
myCheckBox.setBorder(null);
}
myCheckBox.setSelected(myModel.loadInitialCheckBoxState());
if (checkBoxName == null) {
myCheckBox.setVisible(false);
}
addCard(myCheckBox, CHECK_BOX_CARD);
addCard(new HintLabel(myModel.getNotInMessage()), NOT_FOUND_IN_PROJECT_CARD);
addCard(new HintLabel(IdeBundle.message("label.choosebyname.no.matches.found")), NOT_FOUND_CARD);
JPanel searching = new JPanel(new BorderLayout(5, 0));
searching.add(new AsyncProcessIcon("searching"), BorderLayout.WEST);
searching.add(new HintLabel(IdeBundle.message("label.choosebyname.searching")), BorderLayout.CENTER);
addCard(searching, SEARCHING_CARD);
myCard.show(myCardContainer, CHECK_BOX_CARD);
if (isCheckboxVisible()) {
hBox.add(myCardContainer);
}
final DefaultActionGroup group = new DefaultActionGroup();
group.add(new ShowFindUsagesAction() {
@Override
public PsiElement[][] getElements() {
final Object[] objects = myListModel.toArray();
final List<PsiElement> prefixMatchElements = new ArrayList<PsiElement>(objects.length);
final List<PsiElement> nonPrefixMatchElements = new ArrayList<PsiElement>(objects.length);
List<PsiElement> curElements = prefixMatchElements;
for (Object object : objects) {
if (object instanceof PsiElement) {
curElements.add((PsiElement)object);
}
else if (object instanceof DataProvider) {
final PsiElement psi = CommonDataKeys.PSI_ELEMENT.getData((DataProvider)object);
if (psi != null) {
curElements.add(psi);
}
}
else if (object == NON_PREFIX_SEPARATOR) {
curElements = nonPrefixMatchElements;
}
}
return new PsiElement[][]{PsiUtilCore.toPsiElementArray(prefixMatchElements),
PsiUtilCore.toPsiElementArray(nonPrefixMatchElements)};
}
});
final ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true);
actionToolbar.setLayoutPolicy(ActionToolbar.NOWRAP_LAYOUT_POLICY);
final JComponent toolbarComponent = actionToolbar.getComponent();
toolbarComponent.setBorder(null);
if (myToolArea == null) {
myToolArea = new JLabel(EmptyIcon.create(1, 24));
}
hBox.add(myToolArea);
hBox.add(toolbarComponent);
myTextFieldPanel.add(caption2Tools);
final ActionMap actionMap = new ActionMap();
actionMap.setParent(myTextField.getActionMap());
actionMap.put(DefaultEditorKit.copyAction, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if (myTextField.getSelectedText() != null) {
actionMap.getParent().get(DefaultEditorKit.copyAction).actionPerformed(e);
return;
}
final Object chosenElement = getChosenElement();
if (chosenElement instanceof PsiElement) {
CopyReferenceAction.doCopy((PsiElement)chosenElement, myProject);
}
}
});
myTextField.setActionMap(actionMap);
myTextFieldPanel.add(myTextField);
EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
boolean presentationMode = UISettings.getInstance().PRESENTATION_MODE;
int size = presentationMode ? UISettings.getInstance().PRESENTATION_MODE_FONT_SIZE - 4 : scheme.getEditorFontSize();
Font editorFont = new Font(scheme.getEditorFontName(), Font.PLAIN, size);
myTextField.setFont(editorFont);
if (checkBoxName != null) {
if (myCheckBox != null && myCheckBoxShortcut != null) {
new AnAction("change goto check box", null, null) {
@Override
public void actionPerformed(AnActionEvent e) {
myCheckBox.setSelected(!myCheckBox.isSelected());
}
}.registerCustomShortcutSet(myCheckBoxShortcut, myTextField);
}
}
if (isCloseByFocusLost()) {
myTextField.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(@NotNull final FocusEvent e) {
cancelListUpdater(); // cancel thread as early as possible
myHideAlarm.addRequest(new Runnable() {
@Override
public void run() {
JBPopup popup = JBPopupFactory.getInstance().getChildFocusedPopup(e.getComponent());
if (popup != null) {
popup.addListener(new JBPopupListener.Adapter() {
@Override
public void onClosed(@NotNull LightweightWindowEvent event) {
if (event.isOk()) {
hideHint();
}
}
});
}
else {
Component oppositeComponent = e.getOppositeComponent();
if (oppositeComponent == myCheckBox) {
IdeFocusManager.getInstance(myProject).requestFocus(myTextField, true);
return;
}
if (oppositeComponent != null && !(oppositeComponent instanceof JFrame) &&
myList.isShowing() &&
(oppositeComponent == myList || SwingUtilities.isDescendingFrom(myList, oppositeComponent))) {
IdeFocusManager.getInstance(myProject).requestFocus(myTextField, true);// Otherwise me may skip some KeyEvents
return;
}
if (oppositeComponent != null) {
ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
ToolWindow toolWindow = toolWindowManager.getToolWindow(toolWindowManager.getActiveToolWindowId());
if (toolWindow != null) {
JComponent toolWindowComponent = toolWindow.getComponent();
if (SwingUtilities.isDescendingFrom(oppositeComponent, toolWindowComponent)) {
return; // Allow toolwindows to gain focus (used by QuickDoc shown in a toolwindow)
}
}
}
EventQueue queue = Toolkit.getDefaultToolkit().getSystemEventQueue();
if (queue instanceof IdeEventQueue) {
if (!((IdeEventQueue)queue).wasRootRecentlyClicked(oppositeComponent)) {
Component root = SwingUtilities.getRoot(myTextField);
if (root != null && root.isShowing()) {
IdeFocusManager.getInstance(myProject).requestFocus(myTextField, true);
return;
}
}
}
hideHint();
}
}
}, 5);
}
});
}
if (myCheckBox != null) {
myCheckBox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
rebuildList(false);
}
});
myCheckBox.setFocusable(false);
}
myTextField.getDocument().addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent e) {
clearPostponedOkAction(false);
rebuildList(false);
}
});
final Set<KeyStroke> upShortcuts = getShortcuts(IdeActions.ACTION_EDITOR_MOVE_CARET_UP);
final Set<KeyStroke> downShortcuts = getShortcuts(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN);
myTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(@NotNull KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER && (e.getModifiers() & InputEvent.SHIFT_MASK) != 0) {
myClosedByShiftEnter = true;
close(true);
}
if (!myListScrollPane.isVisible()) {
return;
}
final int keyCode;
// Add support for user-defined 'caret up/down' shortcuts.
KeyStroke stroke = KeyStroke.getKeyStrokeForEvent(e);
if (upShortcuts.contains(stroke)) {
keyCode = KeyEvent.VK_UP;
}
else if (downShortcuts.contains(stroke)) {
keyCode = KeyEvent.VK_DOWN;
}
else {
keyCode = e.getKeyCode();
}
switch (keyCode) {
case KeyEvent.VK_DOWN:
ListScrollingUtil.moveDown(myList, e.getModifiersEx());
break;
case KeyEvent.VK_UP:
ListScrollingUtil.moveUp(myList, e.getModifiersEx());
break;
case KeyEvent.VK_PAGE_UP:
ListScrollingUtil.movePageUp(myList);
break;
case KeyEvent.VK_PAGE_DOWN:
ListScrollingUtil.movePageDown(myList);
break;
case KeyEvent.VK_TAB:
close(true);
break;
case KeyEvent.VK_ENTER:
if (myList.getSelectedValue() == EXTRA_ELEM) {
myMaximumListSizeLimit += myListSizeIncreasing;
rebuildList(myList.getSelectedIndex(), myRebuildDelay, ModalityState.current(), null);
e.consume();
}
break;
}
if (myList.getSelectedValue() == NON_PREFIX_SEPARATOR) {
if (keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_PAGE_UP) {
ListScrollingUtil.moveUp(myList, e.getModifiersEx());
}
else {
ListScrollingUtil.moveDown(myList, e.getModifiersEx());
}
}
}
});
myTextField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
doClose(true);
}
});
myList.setFocusable(false);
myList.setSelectionMode(allowMultipleSelection ? ListSelectionModel.MULTIPLE_INTERVAL_SELECTION :
ListSelectionModel.SINGLE_SELECTION);
new ClickListener() {
@Override
public boolean onClick(@NotNull MouseEvent e, int clickCount) {
if (!myTextField.hasFocus()) {
IdeFocusManager.getInstance(myProject).requestFocus(myTextField, true);
}
if (clickCount == 2) {
int selectedIndex = myList.getSelectedIndex();
Rectangle selectedCellBounds = myList.getCellBounds(selectedIndex, selectedIndex);
if (selectedCellBounds != null && selectedCellBounds.contains(e.getPoint())) { // Otherwise it was reselected in the selection listener
if (myList.getSelectedValue() == EXTRA_ELEM) {
myMaximumListSizeLimit += myListSizeIncreasing;
rebuildList(selectedIndex, myRebuildDelay, ModalityState.current(), null);
}
else {
doClose(true);
}
}
return true;
}
return false;
}
}.installOn(myList);
myList.setCellRenderer(myModel.getListCellRenderer());
myList.setFont(editorFont);
myList.addListSelectionListener(new ListSelectionListener() {
private int myPreviousSelectionIndex = 0;
@Override
public void valueChanged(ListSelectionEvent e) {
if (myList.getSelectedValue() != NON_PREFIX_SEPARATOR) {
myPreviousSelectionIndex = myList.getSelectedIndex();
chosenElementMightChange();
updateDocumentation();
}
else if (allowMultipleSelection) {
myList.setSelectedIndex(myPreviousSelectionIndex);
}
}
});
myListScrollPane = ScrollPaneFactory.createScrollPane(myList);
myListScrollPane.setViewportBorder(new EmptyBorder(0, 0, 0, 0));
myTextFieldPanel.setBorder(new EmptyBorder(2, 2, 2, 2));
showTextFieldPanel();
myInitialized = true;
if (modalityState != null) {
rebuildList(myInitialIndex, 0, modalityState, null);
}
}
private void addCard(JComponent comp, String cardId) {
JPanel wrapper = new JPanel(new BorderLayout());
wrapper.add(comp, BorderLayout.EAST);
myCardContainer.add(wrapper, cardId);
}
public void setCheckBoxShortcut(ShortcutSet shortcutSet) {
myCheckBoxShortcut = shortcutSet;
}
@NotNull
private static Set<KeyStroke> getShortcuts(@NotNull String actionId) {
Set<KeyStroke> result = new HashSet<KeyStroke>();
Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
Shortcut[] shortcuts = keymap.getShortcuts(actionId);
if (shortcuts == null) {
return result;
}
for (Shortcut shortcut : shortcuts) {
if (shortcut instanceof KeyboardShortcut) {
KeyboardShortcut keyboardShortcut = (KeyboardShortcut)shortcut;
result.add(keyboardShortcut.getFirstKeyStroke());
}
}
return result;
}
private void hideHint() {
if (!myTextFieldPanel.focusRequested()) {
doClose(false);
myTextFieldPanel.hideHint();
}
}
/**
* Default rebuild list. It uses {@link #myRebuildDelay} and current modality state.
*/
public void rebuildList(boolean initial) {
// TODO this method is public, because the chooser does not listed for the model.
rebuildList(initial ? myInitialIndex : 0, myRebuildDelay, ModalityState.current(), null);
}
private void updateDocumentation() {
final JBPopup hint = myTextFieldPanel.getHint();
final Object element = getChosenElement();
if (hint != null) {
if (element instanceof PsiElement) {
myTextFieldPanel.updateHint((PsiElement)element);
}
else if (element instanceof DataProvider) {
final Object o = ((DataProvider)element).getData(CommonDataKeys.PSI_ELEMENT.getName());
if (o instanceof PsiElement) {
myTextFieldPanel.updateHint((PsiElement)o);
}
}
}
}
public String transformPattern(String pattern) {
return pattern;
}
protected void doClose(final boolean ok) {
if (checkDisposed()) return;
if (postponeCloseWhenListReady(ok)) return;
cancelListUpdater();
close(ok);
clearPostponedOkAction(ok);
myListModel.clear();
}
protected void cancelListUpdater() {
final CalcElementsThread calcElementsThread = myCalcElementsThread;
if (calcElementsThread != null && calcElementsThread.cancel()) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (!checkDisposed() && calcElementsThread == myCalcElementsThread) {
backgroundCalculationFinished(Collections.emptyList(), 0);
}
}
});
}
myListUpdater.cancelAll();
}
private boolean postponeCloseWhenListReady(boolean ok) {
if (!isToFixLostTyping()) return false;
final String text = myTextField.getText();
if (ok && myCalcElementsThread != null && text != null && !text.trim().isEmpty()) {
myPostponedOkAction = new ActionCallback();
IdeFocusManager.getInstance(myProject).typeAheadUntil(myPostponedOkAction);
return true;
}
return false;
}
public void setFixLostTyping(boolean fixLostTyping) {
myFixLostTyping = fixLostTyping;
}
protected boolean isToFixLostTyping() {
return myFixLostTyping && Registry.is("actionSystem.fixLostTyping");
}
@NotNull
private synchronized String[] ensureNamesLoaded(boolean checkboxState) {
String[] cached = getNamesSync(checkboxState);
if (cached != null) return cached;
if (checkboxState &&
myModel instanceof ContributorsBasedGotoByModel &&
((ContributorsBasedGotoByModel)myModel).sameNamesForProjectAndLibraries() &&
getNamesSync(false) != null) {
// there is no way in indices to have different keys for project symbols vs libraries, we always have same ones
String[] allNames = getNamesSync(false);
setNamesSync(true, allNames);
return allNames;
}
String[] result = myModel.getNames(checkboxState);
//noinspection ConstantConditions
assert result != null : "Model "+myModel+ "("+myModel.getClass()+") returned null names";
setNamesSync(checkboxState, result);
return result;
}
@NotNull
public String[] getNames(boolean checkboxState) {
if (ourLoadNamesEachTime) {
setNamesSync(checkboxState, null);
return ensureNamesLoaded(checkboxState);
}
return getNamesSync(checkboxState);
}
private String[] getNamesSync(boolean checkboxState) {
synchronized (myNames) {
return myNames[checkboxState ? 1 : 0];
}
}
@NotNull
protected Set<Object> filter(@NotNull Set<Object> elements) {
return elements;
}
protected abstract boolean isCheckboxVisible();
protected abstract boolean isShowListForEmptyPattern();
protected abstract boolean isCloseByFocusLost();
protected void showTextFieldPanel() {
final JLayeredPane layeredPane = getLayeredPane();
final Dimension preferredTextFieldPanelSize = myTextFieldPanel.getPreferredSize();
final int x = (layeredPane.getWidth() - preferredTextFieldPanelSize.width) / 2;
final int paneHeight = layeredPane.getHeight();
final int y = paneHeight / 3 - preferredTextFieldPanelSize.height / 2;
VISIBLE_LIST_SIZE_LIMIT = Math.max
(10, (paneHeight - (y + preferredTextFieldPanelSize.height)) / (preferredTextFieldPanelSize.height / 2) - 1);
ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(myTextFieldPanel, myTextField);
builder.setCancelCallback(new Computable<Boolean>() {
@Override
public Boolean compute() {
myTextPopup = null;
close(false);
return Boolean.TRUE;
}
}).setFocusable(true).setRequestFocus(true).setModalContext(false).setCancelOnClickOutside(false);
Point point = new Point(x, y);
SwingUtilities.convertPointToScreen(point, layeredPane);
Rectangle bounds = new Rectangle(point, new Dimension(preferredTextFieldPanelSize.width + 20, preferredTextFieldPanelSize.height));
myTextPopup = builder.createPopup();
myTextPopup.setSize(bounds.getSize());
myTextPopup.setLocation(bounds.getLocation());
new MnemonicHelper().register(myTextFieldPanel);
if (myProject != null && !myProject.isDefault()) {
DaemonCodeAnalyzer.getInstance(myProject).disableUpdateByTimer(myTextPopup);
}
Disposer.register(myTextPopup, new Disposable() {
@Override
public void dispose() {
cancelListUpdater();
}
});
myTextPopup.show(layeredPane);
if (myTextPopup instanceof AbstractPopup) {
Window window = ((AbstractPopup)myTextPopup).getPopupWindow();
if (window instanceof JDialog) {
((JDialog)window).getRootPane().putClientProperty(WindowAction.NO_WINDOW_ACTIONS, Boolean.TRUE);
}
}
}
private JLayeredPane getLayeredPane() {
JLayeredPane layeredPane;
final Window window = WindowManager.getInstance().suggestParentWindow(myProject);
Component parent = UIUtil.findUltimateParent(window);
if (parent instanceof JFrame) {
layeredPane = ((JFrame)parent).getLayeredPane();
}
else if (parent instanceof JDialog) {
layeredPane = ((JDialog)parent).getLayeredPane();
}
else {
throw new IllegalStateException("cannot find parent window: project=" + myProject +
(myProject != null ? "; open=" + myProject.isOpen() : "") +
"; window=" + window);
}
return layeredPane;
}
protected void rebuildList(final int pos,
final int delay,
@NotNull final ModalityState modalityState,
@Nullable final Runnable postRunnable) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (!myInitialized) {
return;
}
myAlarm.cancelAllRequests();
myListUpdater.cancelAll();
final CalcElementsThread calcElementsThread = myCalcElementsThread;
if (calcElementsThread != null) {
calcElementsThread.cancel();
}
final String text = myTextField.getText();
if (!canShowListForEmptyPattern() &&
(text == null || text.trim().isEmpty())) {
myListModel.clear();
hideList();
myTextFieldPanel.hideHint();
myCard.show(myCardContainer, CHECK_BOX_CARD);
return;
}
ListCellRenderer cellRenderer = myList.getCellRenderer();
if (cellRenderer instanceof ExpandedItemListCellRendererWrapper) {
cellRenderer = ((ExpandedItemListCellRendererWrapper)cellRenderer).getWrappee();
}
if (cellRenderer instanceof MatcherHolder) {
final String pattern = transformPattern(text);
final Matcher matcher = buildPatternMatcher(isSearchInAnyPlace() ? "*" + pattern : pattern);
((MatcherHolder)cellRenderer).setPatternMatcher(matcher);
}
final Runnable request = new Runnable() {
@Override
public void run() {
scheduleCalcElements(text, myCheckBox.isSelected(), modalityState, new Consumer<Set<?>>() {
@Override
public void consume(Set<?> elements) {
ApplicationManager.getApplication().assertIsDispatchThread();
if (checkDisposed()) {
return;
}
backgroundCalculationFinished(elements, pos);
if (postRunnable != null) {
postRunnable.run();
}
}
});
}
};
if (delay > 0) {
myAlarm.addRequest(request, delay, ModalityState.stateForComponent(myTextField));
}
else {
request.run();
}
}
private void backgroundCalculationFinished(Collection<?> result, int toSelect) {
myCalcElementsThread = null;
setElementsToList(toSelect, result);
myList.repaint();
chosenElementMightChange();
if (result.isEmpty()) {
myTextFieldPanel.hideHint();
}
}
public void scheduleCalcElements(String text,
boolean checkboxState,
ModalityState modalityState,
Consumer<Set<?>> callback) {
scheduleCalcElements(new CalcElementsThread(text, checkboxState, callback, modalityState, false));
}
private void scheduleCalcElements(final CalcElementsThread thread) {
myCalcElementsThread = thread;
ApplicationManager.getApplication().executeOnPooledThread(thread);
}
private boolean isShowListAfterCompletionKeyStroke() {
return myShowListAfterCompletionKeyStroke;
}
private void setElementsToList(int pos, @NotNull Collection<?> elements) {
myListUpdater.cancelAll();
if (checkDisposed()) return;
if (elements.isEmpty()) {
myListModel.clear();
myTextField.setForeground(JBColor.red);
myListUpdater.cancelAll();
hideList();
clearPostponedOkAction(false);
return;
}
Object[] oldElements = myListModel.toArray();
Object[] newElements = elements.toArray();
List<ModelDiff.Cmd> commands = ModelDiff.createDiffCmds(myListModel, oldElements, newElements);
if (commands == null) {
myListUpdater.doPostponedOkIfNeeded();
return; // Nothing changed
}
myTextField.setForeground(UIUtil.getTextFieldForeground());
if (commands.isEmpty()) {
if (pos <= 0) {
pos = detectBestStatisticalPosition();
}
ListScrollingUtil.selectItem(myList, Math.min(pos, myListModel.size() - 1));
myList.setVisibleRowCount(Math.min(VISIBLE_LIST_SIZE_LIMIT, myList.getModel().getSize()));
showList();
myTextFieldPanel.repositionHint();
}
else {
showList();
myListUpdater.appendToModel(commands, pos);
}
}
private int detectBestStatisticalPosition() {
if (myModel instanceof Comparator) {
return 0;
}
int best = 0;
int bestPosition = 0;
int bestMatch = Integer.MIN_VALUE;
final int count = myListModel.getSize();
Matcher matcher = buildPatternMatcher(transformPattern(myTextField.getText()));
final String statContext = statisticsContext();
for (int i = 0; i < count; i++) {
final Object modelElement = myListModel.getElementAt(i);
String text = EXTRA_ELEM.equals(modelElement) || NON_PREFIX_SEPARATOR.equals(modelElement) ? null : myModel.getFullName(modelElement);
if (text != null) {
String shortName = myModel.getElementName(modelElement);
int match = shortName != null && matcher instanceof MinusculeMatcher
? ((MinusculeMatcher)matcher).matchingDegree(shortName) : Integer.MIN_VALUE;
int stats = StatisticsManager.getInstance().getUseCount(new StatisticsInfo(statContext, text));
if (match > bestMatch || match == bestMatch && stats > best) {
best = stats;
bestPosition = i;
bestMatch = match;
}
}
}
if (bestPosition < count - 1 && myListModel.getElementAt(bestPosition) == NON_PREFIX_SEPARATOR) {
bestPosition++;
}
return bestPosition;
}
@NotNull
@NonNls
protected String statisticsContext() {
return "choose_by_name#" + myModel.getPromptText() + "#" + myCheckBox.isSelected() + "#" + myTextField.getText();
}
private static class MyListModel<T> extends DefaultListModel implements ModelDiff.Model<T> {
@Override
public void addToModel(int idx, T element) {
if (idx < size()) {
add(idx, element);
}
else {
addElement(element);
}
}
@Override
public void removeRangeFromModel(int start, int end) {
if (start < size() && size() != 0) {
removeRange(start, Math.min(end, size()-1));
}
}
}
private class ListUpdater {
private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
private static final int DELAY = 10;
private static final int MAX_BLOCKING_TIME = 30;
private final List<ModelDiff.Cmd> myCommands = Collections.synchronizedList(new ArrayList<ModelDiff.Cmd>());
public void cancelAll() {
myCommands.clear();
myAlarm.cancelAllRequests();
}
public void appendToModel(@NotNull List<ModelDiff.Cmd> commands, final int selectionPos) {
myAlarm.cancelAllRequests();
myCommands.addAll(commands);
if (myCommands.isEmpty() || checkDisposed()) {
return;
}
myAlarm.addRequest(new Runnable() {
@Override
public void run() {
if (checkDisposed()) {
return;
}
final long startTime = System.currentTimeMillis();
while (!myCommands.isEmpty() && System.currentTimeMillis() - startTime < MAX_BLOCKING_TIME) {
final ModelDiff.Cmd cmd = myCommands.remove(0);
cmd.apply();
}
myList.setVisibleRowCount(Math.min(VISIBLE_LIST_SIZE_LIMIT, myList.getModel().getSize()));
if (!myCommands.isEmpty()) {
myAlarm.addRequest(this, DELAY);
}
else {
doPostponedOkIfNeeded();
}
if (!checkDisposed()) {
showList();
myTextFieldPanel.repositionHint();
if (!myListModel.isEmpty()) {
int pos = selectionPos <= 0 ? detectBestStatisticalPosition() : selectionPos;
ListScrollingUtil.selectItem(myList, Math.min(pos, myListModel.size() - 1));
}
}
}
}, DELAY);
}
private void doPostponedOkIfNeeded() {
if (myPostponedOkAction != null) {
if (getChosenElement() != null) {
doClose(true);
}
clearPostponedOkAction(checkDisposed());
}
}
}
private void clearPostponedOkAction(boolean success) {
if (myPostponedOkAction != null) {
if (success) {
myPostponedOkAction.setDone();
}
else {
myPostponedOkAction.setRejected();
}
}
myPostponedOkAction = null;
}
protected abstract void showList();
protected abstract void hideList();
protected abstract void close(boolean isOk);
@Nullable
public Object getChosenElement() {
final List<Object> elements = getChosenElements();
return elements != null && elements.size() == 1 ? elements.get(0) : null;
}
protected List<Object> getChosenElements() {
return ContainerUtil.filter(myList.getSelectedValues(), new Condition<Object>() {
@Override
public boolean value(Object o) {
return o != EXTRA_ELEM && o != NON_PREFIX_SEPARATOR;
}
});
}
protected void chosenElementMightChange() {
}
protected final class MyTextField extends JTextField implements PopupOwner, TypeSafeDataProvider {
private final KeyStroke myCompletionKeyStroke;
private final KeyStroke forwardStroke;
private final KeyStroke backStroke;
private boolean completionKeyStrokeHappened = false;
private MyTextField() {
super(40);
if (!(getUI() instanceof DarculaTextFieldUI)) {
setUI((DarculaTextFieldUI)DarculaTextFieldUI.createUI(this));
}
setBorder(new DarculaTextBorder());
enableEvents(AWTEvent.KEY_EVENT_MASK);
myCompletionKeyStroke = getShortcut(IdeActions.ACTION_CODE_COMPLETION);
forwardStroke = getShortcut(IdeActions.ACTION_GOTO_FORWARD);
backStroke = getShortcut(IdeActions.ACTION_GOTO_BACK);
setFocusTraversalKeysEnabled(false);
putClientProperty("JTextField.variant", "search");
setDocument(new PlainDocument() {
@Override
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
super.insertString(offs, str, a);
if (str != null && str.length() > 1) {
handlePaste(str);
}
}
});
}
@Nullable
private KeyStroke getShortcut(String actionCodeCompletion) {
final Shortcut[] shortcuts = KeymapManager.getInstance().getActiveKeymap().getShortcuts(actionCodeCompletion);
for (final Shortcut shortcut : shortcuts) {
if (shortcut instanceof KeyboardShortcut) {
return ((KeyboardShortcut)shortcut).getFirstKeyStroke();
}
}
return null;
}
@Override
public void calcData(final DataKey key, @NotNull final DataSink sink) {
if (LangDataKeys.POSITION_ADJUSTER_POPUP.equals(key)) {
if (myDropdownPopup != null && myDropdownPopup.isVisible()) {
sink.put(key, myDropdownPopup);
}
}
else if (LangDataKeys.PARENT_POPUP.equals(key)) {
if (myTextPopup != null && myTextPopup.isVisible()) {
sink.put(key, myTextPopup);
}
}
}
@Override
protected void processKeyEvent(@NotNull KeyEvent e) {
final KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
if (myCompletionKeyStroke != null && keyStroke.equals(myCompletionKeyStroke)) {
completionKeyStrokeHappened = true;
e.consume();
final String pattern = myTextField.getText();
final String oldText = myTextField.getText();
final int oldPos = myList.getSelectedIndex();
myHistory.add(Pair.create(oldText, oldPos));
final Runnable postRunnable = new Runnable() {
@Override
public void run() {
fillInCommonPrefix(pattern);
}
};
rebuildList(0, 0, ModalityState.current(), postRunnable);
return;
}
if (backStroke != null && keyStroke.equals(backStroke)) {
e.consume();
if (!myHistory.isEmpty()) {
final String oldText = myTextField.getText();
final int oldPos = myList.getSelectedIndex();
final Pair<String, Integer> last = myHistory.remove(myHistory.size() - 1);
myTextField.setText(last.first);
myFuture.add(Pair.create(oldText, oldPos));
rebuildList(0, 0, ModalityState.current(), null);
}
return;
}
if (forwardStroke != null && keyStroke.equals(forwardStroke)) {
e.consume();
if (!myFuture.isEmpty()) {
final String oldText = myTextField.getText();
final int oldPos = myList.getSelectedIndex();
final Pair<String, Integer> next = myFuture.remove(myFuture.size() - 1);
myTextField.setText(next.first);
myHistory.add(Pair.create(oldText, oldPos));
rebuildList(0, 0, ModalityState.current(), null);
}
return;
}
int position = myTextField.getCaretPosition();
int code = keyStroke.getKeyCode();
int modifiers = keyStroke.getModifiers();
try {
super.processKeyEvent(e);
}
catch (NullPointerException e1) {
if (!Patches.SUN_BUG_ID_6322854) {
throw e1;
}
}
finally {
if ((code == KeyEvent.VK_UP || code == KeyEvent.VK_DOWN) && modifiers == 0) {
myTextField.setCaretPosition(position);
}
}
}
private void fillInCommonPrefix(@NotNull final String pattern) {
if (StringUtil.isEmpty(pattern) && !canShowListForEmptyPattern()) {
return;
}
final List<String> list = myProvider.filterNames(ChooseByNameBase.this, getNames(myCheckBox.isSelected()), pattern);
if (isComplexPattern(pattern)) return; //TODO: support '*'
final String oldText = myTextField.getText();
final int oldPos = myList.getSelectedIndex();
String commonPrefix = null;
if (!list.isEmpty()) {
for (String name : list) {
final String string = name.toLowerCase();
if (commonPrefix == null) {
commonPrefix = string;
}
else {
while (!commonPrefix.isEmpty()) {
if (string.startsWith(commonPrefix)) {
break;
}
commonPrefix = commonPrefix.substring(0, commonPrefix.length() - 1);
}
if (commonPrefix.isEmpty()) break;
}
}
commonPrefix = list.get(0).substring(0, commonPrefix.length());
for (int i = 1; i < list.size(); i++) {
final String string = list.get(i).substring(0, commonPrefix.length());
if (!string.equals(commonPrefix)) {
commonPrefix = commonPrefix.toLowerCase();
break;
}
}
}
if (commonPrefix == null) commonPrefix = "";
if (!StringUtil.startsWithIgnoreCase(commonPrefix, pattern)) {
commonPrefix = pattern;
}
final String newPattern = commonPrefix;
myHistory.add(Pair.create(oldText, oldPos));
myTextField.setText(newPattern);
myTextField.setCaretPosition(newPattern.length());
rebuildList(false);
}
private boolean isComplexPattern(@NotNull final String pattern) {
if (pattern.indexOf('*') >= 0) return true;
for (String s : myModel.getSeparators()) {
if (pattern.contains(s)) return true;
}
return false;
}
@Override
@Nullable
public Point getBestPopupPosition() {
return new Point(myTextFieldPanel.getWidth(), getHeight());
}
@Override
protected void paintComponent(@NotNull final Graphics g) {
GraphicsUtil.setupAntialiasing(g);
super.paintComponent(g);
}
public boolean isCompletionKeyStroke() {
return completionKeyStrokeHappened;
}
}
public ChooseByNameItemProvider getProvider() {
return myProvider;
}
protected void handlePaste(String str) {
if (!myInitIsDone) return;
if (myModel instanceof GotoClassModel2 && isFileName(str)) {
//noinspection SSBasedInspection
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
final GotoFileAction gotoFile = new GotoFileAction();
AnActionEvent event = new AnActionEvent(null,
DataManager.getInstance().getDataContext(myTextField),
ActionPlaces.UNKNOWN,
gotoFile.getTemplatePresentation(),
ActionManager.getInstance(),
0);
event.setInjectedContext(gotoFile.isInInjectedContext());
gotoFile.actionPerformed(event);
}
});
}
}
private static boolean isFileName(String name) {
final int index = name.lastIndexOf('.');
if (index > 0) {
String ext = name.substring(index + 1);
if (ext.contains(":")) {
ext = ext.substring(0, ext.indexOf(':'));
}
if (FileTypeManagerEx.getInstanceEx().getFileTypeByExtension(ext) != UnknownFileType.INSTANCE) {
return true;
}
}
return false;
}
public static final String EXTRA_ELEM = "...";
public static final String NON_PREFIX_SEPARATOR = "non-prefix matches:";
public static Component renderNonPrefixSeparatorComponent(Color backgroundColor) {
final JPanel panel = new JPanel(new BorderLayout());
final JSeparator separator = new JSeparator(SwingConstants.HORIZONTAL);
panel.add(separator, BorderLayout.CENTER);
if (!UIUtil.isUnderAquaBasedLookAndFeel()) {
panel.setBorder(new EmptyBorder(3, 0, 2, 0));
}
panel.setBackground(backgroundColor);
return panel;
}
private class CalcElementsThread implements Runnable {
private final String myPattern;
private volatile boolean myCheckboxState;
private volatile boolean myScopeExpanded;
private final Consumer<Set<?>> myCallback;
private final ModalityState myModalityState;
private final ProgressIndicator myCancelled = new ProgressIndicatorBase();
CalcElementsThread(String pattern,
boolean checkboxState,
Consumer<Set<?>> callback,
@NotNull ModalityState modalityState,
boolean scopeExpanded) {
myPattern = pattern;
myCheckboxState = checkboxState;
myCallback = callback;
myModalityState = modalityState;
myScopeExpanded = scopeExpanded;
}
private final Alarm myShowCardAlarm = new Alarm();
private void scheduleRestart() {
scheduleCalcElements(new CalcElementsThread(myPattern, myCheckboxState, myCallback, myModalityState, myScopeExpanded));
}
@Override
public void run() {
showCard(SEARCHING_CARD, 200);
ProgressManager.getInstance().runProcess(new Runnable() {
@Override
public void run() {
final Set<Object> elements = new LinkedHashSet<Object>();
Runnable calculation = new Runnable() {
public void run() {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
ApplicationAdapter listener = new ApplicationAdapter() {
@Override
public void beforeWriteActionStart(Object action) {
cancel();
scheduleRestart();
ApplicationManager.getApplication().removeApplicationListener(this);
}
};
ApplicationManager.getApplication().addApplicationListener(listener);
try {
boolean everywhere = myCheckboxState;
if (!ourLoadNamesEachTime) ensureNamesLoaded(everywhere);
addElementsByPattern(myPattern, elements, myCancelled, everywhere);
}
catch (ProcessCanceledException e) {
//OK
}
finally {
ApplicationManager.getApplication().removeApplicationListener(listener);
}
}
});
}
};
calculation.run();
if (myCancelled.isCanceled()) {
myShowCardAlarm.cancelAllRequests();
return;
}
if (elements.isEmpty() && !myCheckboxState) {
myScopeExpanded = true;
myCheckboxState = true;
calculation.run();
}
final String cardToShow = elements.isEmpty() ? NOT_FOUND_CARD : myScopeExpanded ? NOT_FOUND_IN_PROJECT_CARD : CHECK_BOX_CARD;
showCard(cardToShow, 0);
final boolean edt = myModel instanceof EdtSortingModel;
final Set<Object> filtered = !edt ? filter(elements) : Collections.emptySet();
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
if (!myCancelled.isCanceled()) {
LOG.assertTrue(myCalcElementsThread == CalcElementsThread.this);
myCallback.consume(edt ? filter(elements) : filtered);
}
}
}, myModalityState);
}
}, myCancelled);
}
public void addElementsByPattern(@NotNull String pattern,
@NotNull final Set<Object> elements,
@NotNull final ProgressIndicator cancelled,
boolean everywhere) {
long start = System.currentTimeMillis();
myProvider.filterElements(
ChooseByNameBase.this, pattern, everywhere,
cancelled,
new Processor<Object>() {
@Override
public boolean process(Object o) {
if (cancelled.isCanceled()) return false;
elements.add(o);
if (isOverflow(elements)) {
elements.add(EXTRA_ELEM);
return false;
}
return true;
}
}
);
if (myAlwaysHasMore) {
elements.add(EXTRA_ELEM);
}
if (ContributorsBasedGotoByModel.LOG.isDebugEnabled()) {
long end = System.currentTimeMillis();
ContributorsBasedGotoByModel.LOG.debug("addElementsByPattern("+pattern+"): "+(end-start)+"ms; "+elements.size()+" elements");
}
}
private void showCard(final String card, final int delay) {
if (ApplicationManager.getApplication().isUnitTestMode()) return;
myShowCardAlarm.cancelAllRequests();
myShowCardAlarm.addRequest(new Runnable() {
@Override
public void run() {
if (!myCancelled.isCanceled()) {
myCard.show(myCardContainer, card);
}
}
}, delay, myModalityState);
}
protected boolean isOverflow(@NotNull Set<Object> elementsArray) {
return elementsArray.size() >= myMaximumListSizeLimit;
}
private boolean cancel() {
if (myCancelled.isCanceled()) {
return false;
}
myCancelled.cancel();
return true;
}
}
public boolean canShowListForEmptyPattern() {
return isShowListForEmptyPattern() || isShowListAfterCompletionKeyStroke() && lastKeyStrokeIsCompletion();
}
protected boolean lastKeyStrokeIsCompletion() {
return myTextField.isCompletionKeyStroke();
}
private static Matcher buildPatternMatcher(@NotNull String pattern) {
return NameUtil.buildMatcher(pattern, 0, true, true, pattern.toLowerCase().equals(pattern));
}
private static class HintLabel extends JLabel {
private HintLabel(String text) {
super(text, RIGHT);
setForeground(Color.darkGray);
}
}
public int getMaximumListSizeLimit() {
return myMaximumListSizeLimit;
}
public void setMaximumListSizeLimit(final int maximumListSizeLimit) {
myMaximumListSizeLimit = maximumListSizeLimit;
}
public void setListSizeIncreasing(final int listSizeIncreasing) {
myListSizeIncreasing = listSizeIncreasing;
}
public boolean isAlwaysHasMore() {
return myAlwaysHasMore;
}
/**
* Display <tt>...</tt> item at the end of the list regardless of whether it was filled up or not.
* This option can be useful in cases, when it can't be said beforehand, that the next call to {@link ChooseByNameItemProvider}
* won't give new items.
*/
public void setAlwaysHasMore(boolean enabled) {
myAlwaysHasMore = enabled;
}
private static final String ACTION_NAME = "Show All in View";
private abstract class ShowFindUsagesAction extends AnAction {
public ShowFindUsagesAction() {
super(ACTION_NAME, ACTION_NAME, AllIcons.General.AutohideOff);
}
@Override
public void actionPerformed(final AnActionEvent e) {
cancelListUpdater();
final UsageViewPresentation presentation = new UsageViewPresentation();
final String prefixPattern = myFindUsagesTitle + " \'" + myTextField.getText().trim() + "\'";
final String nonPrefixPattern = myFindUsagesTitle + " \'*" + myTextField.getText().trim() + "*\'";
presentation.setCodeUsagesString(prefixPattern);
presentation.setUsagesInGeneratedCodeString(prefixPattern + " in generated code");
presentation.setDynamicUsagesString(nonPrefixPattern);
presentation.setTabName(prefixPattern);
presentation.setTabText(prefixPattern);
presentation.setTargetsNodeText("Unsorted " + StringUtil.toLowerCase(prefixPattern.toLowerCase()));
final Object[][] elements = getElements();
final List<PsiElement> targets = new ArrayList<PsiElement>();
final List<Usage> usages = new ArrayList<Usage>();
fillUsages(Arrays.asList(elements[0]), usages, targets, false);
fillUsages(Arrays.asList(elements[1]), usages, targets, true);
if (myListModel.contains(EXTRA_ELEM)) { //start searching for the rest
final String text = myTextField.getText();
final boolean everywhere = myCheckBox.isSelected();
final LinkedHashSet<Object> prefixMatchElementsArray = new LinkedHashSet<Object>();
final LinkedHashSet<Object> nonPrefixMatchElementsArray = new LinkedHashSet<Object>();
hideHint();
ProgressManager.getInstance().run(new Task.Modal(myProject, prefixPattern, true) {
private ChooseByNameBase.CalcElementsThread myCalcUsagesThread;
@Override
public void run(@NotNull final ProgressIndicator indicator) {
ensureNamesLoaded(everywhere);
indicator.setIndeterminate(true);
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
final boolean[] overFlow = {false};
myCalcUsagesThread = new CalcElementsThread(text, everywhere, null, ModalityState.NON_MODAL, false) {
private final AtomicBoolean userAskedToAbort = new AtomicBoolean();
@Override
protected boolean isOverflow(@NotNull Set<Object> elementsArray) {
if (elementsArray.size() > UsageLimitUtil.USAGES_LIMIT - myMaximumListSizeLimit && !userAskedToAbort.getAndSet(true)) {
final UsageLimitUtil.Result ret = UsageLimitUtil.showTooManyUsagesWarning(myProject, UsageViewBundle
.message("find.excessive.usage.count.prompt", elementsArray.size() + myMaximumListSizeLimit, StringUtil.pluralize(presentation.getUsagesWord())), presentation);
if (ret == UsageLimitUtil.Result.ABORT) {
overFlow[0] = true;
return true;
}
}
return false;
}
};
boolean anyPlace = isSearchInAnyPlace();
setSearchInAnyPlace(false);
myCalcUsagesThread.addElementsByPattern(text, prefixMatchElementsArray, indicator, everywhere);
setSearchInAnyPlace(anyPlace);
if (anyPlace && !overFlow[0]) {
myCalcUsagesThread.addElementsByPattern(text, nonPrefixMatchElementsArray, indicator, everywhere);
nonPrefixMatchElementsArray.removeAll(prefixMatchElementsArray);
}
indicator.setText("Prepare...");
fillUsages(prefixMatchElementsArray, usages, targets, false);
fillUsages(nonPrefixMatchElementsArray, usages, targets, true);
}
});
}
@Override
public void onSuccess() {
showUsageView(targets, usages, presentation);
}
@Override
public void onCancel() {
myCalcUsagesThread.cancel();
}
});
}
else {
hideHint();
showUsageView(targets, usages, presentation);
}
}
private void fillUsages(Collection<Object> matchElementsArray,
List<Usage> usages,
List<PsiElement> targets,
final boolean separateGroup) {
for (Object o : matchElementsArray) {
if (o instanceof PsiElement) {
PsiElement element = (PsiElement)o;
if (element.getTextRange() != null) {
usages.add(new UsageInfo2UsageAdapter(new UsageInfo(element) {
@Override
public boolean isDynamicUsage() {
return separateGroup || super.isDynamicUsage();
}
}));
}
else {
targets.add(element);
}
}
}
}
private void showUsageView(@NotNull List<PsiElement> targets,
@NotNull List<Usage> usages,
@NotNull UsageViewPresentation presentation) {
UsageTarget[] usageTargets = targets.isEmpty() ? UsageTarget.EMPTY_ARRAY :
PsiElement2UsageTargetAdapter.convert(PsiUtilCore.toPsiElementArray(targets));
UsageViewManager.getInstance(myProject).showUsages(usageTargets, usages.toArray(new Usage[usages.size()]), presentation);
}
@Override
public void update(@NotNull AnActionEvent e) {
if (myFindUsagesTitle == null || myProject == null) {
e.getPresentation().setVisible(false);
return;
}
final Object[][] elements = getElements();
e.getPresentation().setEnabled(elements != null && elements[0].length + elements[1].length > 0);
}
public abstract Object[][] getElements();
}
public JTextField getTextField() {
return myTextField;
}
}