2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.intellij.find.actions;
19 import com.intellij.codeInsight.hint.HintManager;
20 import com.intellij.codeInsight.hint.HintUtil;
21 import com.intellij.featureStatistics.FeatureUsageTracker;
22 import com.intellij.find.FindManager;
23 import com.intellij.find.FindSettings;
24 import com.intellij.find.UsagesPreviewPanelProvider;
25 import com.intellij.find.findUsages.*;
26 import com.intellij.find.impl.FindManagerImpl;
27 import com.intellij.icons.AllIcons;
28 import com.intellij.ide.DataManager;
29 import com.intellij.ide.util.gotoByName.ModelDiff;
30 import com.intellij.openapi.Disposable;
31 import com.intellij.openapi.actionSystem.*;
32 import com.intellij.openapi.application.ApplicationManager;
33 import com.intellij.openapi.editor.Editor;
34 import com.intellij.openapi.fileEditor.FileEditor;
35 import com.intellij.openapi.fileEditor.FileEditorLocation;
36 import com.intellij.openapi.fileEditor.TextEditor;
37 import com.intellij.openapi.keymap.KeymapUtil;
38 import com.intellij.openapi.preview.PreviewManager;
39 import com.intellij.openapi.progress.ProgressIndicator;
40 import com.intellij.openapi.project.DumbAwareAction;
41 import com.intellij.openapi.project.Project;
42 import com.intellij.openapi.ui.MessageType;
43 import com.intellij.openapi.ui.popup.JBPopup;
44 import com.intellij.openapi.ui.popup.JBPopupFactory;
45 import com.intellij.openapi.ui.popup.PopupChooserBuilder;
46 import com.intellij.openapi.util.*;
47 import com.intellij.openapi.util.text.StringUtil;
48 import com.intellij.openapi.vfs.VirtualFile;
49 import com.intellij.openapi.wm.IdeFocusManager;
50 import com.intellij.pom.Navigatable;
51 import com.intellij.psi.PsiDocumentManager;
52 import com.intellij.psi.PsiElement;
53 import com.intellij.psi.search.GlobalSearchScope;
54 import com.intellij.psi.search.PsiElementProcessor;
55 import com.intellij.psi.search.SearchScope;
56 import com.intellij.ui.*;
57 import com.intellij.ui.awt.RelativePoint;
58 import com.intellij.ui.popup.AbstractPopup;
59 import com.intellij.usageView.UsageInfo;
60 import com.intellij.usageView.UsageViewBundle;
61 import com.intellij.usageView.UsageViewUtil;
62 import com.intellij.usages.*;
63 import com.intellij.usages.impl.*;
64 import com.intellij.usages.rules.UsageFilteringRuleProvider;
65 import com.intellij.util.Alarm;
66 import com.intellij.util.PlatformIcons;
67 import com.intellij.util.Processor;
68 import com.intellij.util.messages.MessageBusConnection;
69 import com.intellij.util.ui.AsyncProcessIcon;
70 import com.intellij.util.ui.ColumnInfo;
71 import com.intellij.util.ui.ListTableModel;
72 import com.intellij.util.ui.UIUtil;
73 import com.intellij.xml.util.XmlStringUtil;
74 import org.jetbrains.annotations.NonNls;
75 import org.jetbrains.annotations.NotNull;
76 import org.jetbrains.annotations.Nullable;
79 import javax.swing.event.ListSelectionEvent;
80 import javax.swing.event.ListSelectionListener;
81 import javax.swing.table.TableColumn;
83 import java.awt.event.*;
85 import java.util.List;
86 import java.util.concurrent.atomic.AtomicBoolean;
87 import java.util.concurrent.atomic.AtomicInteger;
88 import java.util.concurrent.atomic.AtomicReference;
90 public class ShowUsagesAction extends AnAction implements PopupAction {
91 public static final String ID = "ShowUsages";
92 private final boolean showSettingsDialogBefore;
93 public static final int USAGES_PAGE_SIZE = 100;
95 static final Usage MORE_USAGES_SEPARATOR = NullUsage.INSTANCE;
96 private static final UsageNode MORE_USAGES_SEPARATOR_NODE = UsageViewImpl.NULL_NODE;
97 static final Usage USAGES_OUTSIDE_SCOPE_SEPARATOR = new UsageAdapter();
98 private static final UsageNode USAGES_OUTSIDE_SCOPE_NODE = new UsageNode(USAGES_OUTSIDE_SCOPE_SEPARATOR, new UsageViewTreeModelBuilder(new UsageViewPresentation(), UsageTarget.EMPTY_ARRAY));
100 private static final Comparator<UsageNode> USAGE_NODE_COMPARATOR = new Comparator<UsageNode>() {
102 public int compare(UsageNode c1, UsageNode c2) {
103 if (c1 instanceof StringNode) return 1;
104 if (c2 instanceof StringNode) return -1;
105 Usage o1 = c1.getUsage();
106 Usage o2 = c2.getUsage();
107 int weight1 = o1 == USAGES_OUTSIDE_SCOPE_SEPARATOR ? 2 : o1 == MORE_USAGES_SEPARATOR ? 1 : 0;
108 int weight2 = o2 == USAGES_OUTSIDE_SCOPE_SEPARATOR ? 2 : o2 == MORE_USAGES_SEPARATOR ? 1 : 0;
109 if (weight1 != weight2) return weight1 - weight2;
111 VirtualFile v1 = UsageListCellRenderer.getVirtualFile(o1);
112 VirtualFile v2 = UsageListCellRenderer.getVirtualFile(o2);
113 String name1 = v1 == null ? null : v1.getName();
114 String name2 = v2 == null ? null : v2.getName();
115 int i = Comparing.compare(name1, name2);
116 if (i != 0) return i;
118 if (o1 instanceof Comparable && o2 instanceof Comparable) {
119 return ((Comparable)o1).compareTo(o2);
122 FileEditorLocation loc1 = o1.getLocation();
123 FileEditorLocation loc2 = o2.getLocation();
124 return Comparing.compare(loc1, loc2);
127 private static final Runnable HIDE_HINTS_ACTION = new Runnable() {
133 @NotNull private final UsageViewSettings myUsageViewSettings;
134 @Nullable private Runnable mySearchEverywhereRunnable;
136 // used from plugin.xml
137 @SuppressWarnings({"UnusedDeclaration"})
138 public ShowUsagesAction() {
142 private ShowUsagesAction(boolean showDialogBefore) {
143 setInjectedContext(true);
144 showSettingsDialogBefore = showDialogBefore;
146 final UsageViewSettings usageViewSettings = UsageViewSettings.getInstance();
147 myUsageViewSettings = new UsageViewSettings();
148 myUsageViewSettings.loadState(usageViewSettings);
149 myUsageViewSettings.GROUP_BY_FILE_STRUCTURE = false;
150 myUsageViewSettings.GROUP_BY_MODULE = false;
151 myUsageViewSettings.GROUP_BY_PACKAGE = false;
152 myUsageViewSettings.GROUP_BY_USAGE_TYPE = false;
153 myUsageViewSettings.GROUP_BY_SCOPE = false;
158 public void actionPerformed(@NotNull AnActionEvent e) {
159 final Project project = e.getData(CommonDataKeys.PROJECT);
160 if (project == null) return;
162 Runnable searchEverywhere = mySearchEverywhereRunnable;
163 mySearchEverywhereRunnable = null;
166 if (searchEverywhere != null) {
167 searchEverywhere.run();
171 final RelativePoint popupPosition = JBPopupFactory.getInstance().guessBestPopupLocation(e.getDataContext());
172 PsiDocumentManager.getInstance(project).commitAllDocuments();
173 FeatureUsageTracker.getInstance().triggerFeatureUsed("navigation.goto.usages");
175 UsageTarget[] usageTargets = e.getData(UsageView.USAGE_TARGETS_KEY);
176 final Editor editor = e.getData(CommonDataKeys.EDITOR);
177 if (usageTargets == null) {
178 FindUsagesAction.chooseAmbiguousTargetAndPerform(project, editor, new PsiElementProcessor<PsiElement>() {
180 public boolean execute(@NotNull final PsiElement element) {
181 startFindUsages(element, popupPosition, editor, USAGES_PAGE_SIZE);
187 PsiElement element = ((PsiElementUsageTarget)usageTargets[0]).getElement();
188 if (element != null) {
189 startFindUsages(element, popupPosition, editor, USAGES_PAGE_SIZE);
194 private static void hideHints() {
195 HintManager.getInstance().hideHints(HintManager.HIDE_BY_ANY_KEY, false, false);
198 public void startFindUsages(@NotNull PsiElement element, @NotNull RelativePoint popupPosition, Editor editor, int maxUsages) {
199 Project project = element.getProject();
200 FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(project)).getFindUsagesManager();
201 FindUsagesHandler handler = findUsagesManager.getFindUsagesHandler(element, false);
202 if (handler == null) return;
203 if (showSettingsDialogBefore) {
204 showDialogAndFindUsages(handler, popupPosition, editor, maxUsages);
207 showElementUsages(editor, popupPosition, handler, maxUsages, handler.getFindUsagesOptions(DataManager.getInstance().getDataContext()));
210 private void showElementUsages(final Editor editor,
211 @NotNull final RelativePoint popupPosition,
212 @NotNull final FindUsagesHandler handler,
214 @NotNull final FindUsagesOptions options) {
215 ApplicationManager.getApplication().assertIsDispatchThread();
216 final UsageViewSettings usageViewSettings = UsageViewSettings.getInstance();
217 final UsageViewSettings savedGlobalSettings = new UsageViewSettings();
219 savedGlobalSettings.loadState(usageViewSettings);
220 usageViewSettings.loadState(myUsageViewSettings);
222 final Project project = handler.getProject();
223 UsageViewManager manager = UsageViewManager.getInstance(project);
224 FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(project)).getFindUsagesManager();
225 final UsageViewPresentation presentation = findUsagesManager.createPresentation(handler, options);
226 presentation.setDetachedMode(true);
227 final UsageViewImpl usageView = (UsageViewImpl)manager.createUsageView(UsageTarget.EMPTY_ARRAY, Usage.EMPTY_ARRAY, presentation, null);
229 Disposer.register(usageView, new Disposable() {
231 public void dispose() {
232 myUsageViewSettings.loadState(usageViewSettings);
233 usageViewSettings.loadState(savedGlobalSettings);
236 final AtomicInteger outOfScopeUsages = new AtomicInteger();
238 final List<Usage> usages = new ArrayList<Usage>();
239 final Set<UsageNode> visibleNodes = new LinkedHashSet<UsageNode>();
241 final MyTable table = new MyTable();
242 final AsyncProcessIcon processIcon = new AsyncProcessIcon("xxx");
244 addUsageNodes(usageView.getRoot(), usageView, new ArrayList<UsageNode>());
246 TableScrollingUtil.installActions(table);
248 final List<UsageNode> data = collectData(usages, visibleNodes, usageView, presentation);
249 setTableModel(table, usageView, data, outOfScopeUsages, options.searchScope);
252 boolean isPreviewMode = (Boolean.TRUE == PreviewManager.SERVICE.preview(handler.getProject(), UsagesPreviewPanelProvider.ID, Pair.create(usageView, table), false) );
253 Runnable itemChosenCallback = prepareTable(table, editor, popupPosition, handler, maxUsages, options, isPreviewMode);
255 @Nullable final JBPopup popup = isPreviewMode ? null : createUsagePopup(usages, visibleNodes, handler, editor, popupPosition,
256 maxUsages, usageView, options, table, itemChosenCallback, presentation, processIcon);
258 Disposer.register(popup, usageView);
260 // show popup only if find usages takes more than 300ms, otherwise it would flicker needlessly
261 Alarm alarm = new Alarm(usageView);
262 alarm.addRequest(new Runnable() {
265 showPopupIfNeedTo(popup, popupPosition);
270 final PingEDT pingEDT = new PingEDT("Rebuild popup in EDT", new Condition<Object>() {
272 public boolean value(Object o) {
273 return popup != null && popup.isDisposed();
275 }, 100, new Runnable() {
278 if (popup != null && popup.isDisposed()) return;
280 final List<UsageNode> nodes = new ArrayList<UsageNode>();
282 synchronized (usages) {
283 // open up popup as soon as several usages 've been found
284 if (popup != null && (!popup.isVisible() && (usages.size() <= 1 || !showPopupIfNeedTo(popup, popupPosition)))) {
287 addUsageNodes(usageView.getRoot(), usageView, nodes);
288 copy = new ArrayList<Usage>(usages);
291 rebuildTable(usageView, copy, nodes, table, popup, presentation, popupPosition, !processIcon.isDisposed(), outOfScopeUsages,
292 options.searchScope);
296 final MessageBusConnection messageBusConnection = project.getMessageBus().connect(usageView);
297 messageBusConnection.subscribe(UsageFilteringRuleProvider.RULES_CHANGED, new Runnable() {
304 final UsageTarget[] myUsageTarget = {new PsiElement2UsageTargetAdapter(handler.getPsiElement())};
305 Processor<Usage> collect = new Processor<Usage>() {
307 public boolean process(@NotNull final Usage usage) {
308 if (!UsageViewManagerImpl.isInScope(usage, options.searchScope)) {
309 if (outOfScopeUsages.getAndIncrement() == 0) {
310 visibleNodes.add(USAGES_OUTSIDE_SCOPE_NODE);
311 usages.add(USAGES_OUTSIDE_SCOPE_SEPARATOR);
315 synchronized (usages) {
316 if (visibleNodes.size() >= maxUsages) return false;
317 if(UsageViewManager.isSelfUsage(usage, myUsageTarget)) return true;
318 UsageNode node = ApplicationManager.getApplication().runReadAction(new Computable<UsageNode>() {
320 public UsageNode compute() {
321 return usageView.doAppendUsage(usage);
326 visibleNodes.add(node);
327 boolean continueSearch = true;
328 if (visibleNodes.size() == maxUsages) {
329 visibleNodes.add(MORE_USAGES_SEPARATOR_NODE);
330 usages.add(MORE_USAGES_SEPARATOR);
331 continueSearch = false;
335 return continueSearch;
343 final ProgressIndicator indicator = FindUsagesManager.startProcessUsages(handler, handler.getPrimaryElements(), handler.getSecondaryElements(), collect, options, new Runnable() {
346 ApplicationManager.getApplication().invokeLater(new Runnable() {
349 Disposer.dispose(processIcon);
350 Container parent = processIcon.getParent();
351 if (parent != null) {
352 parent.remove(processIcon);
355 pingEDT.ping(); // repaint title
356 synchronized (usages) {
357 if (visibleNodes.isEmpty()) {
358 if (usages.isEmpty()) {
359 String text = UsageViewBundle.message("no.usages.found.in", searchScopePresentableName(options));
360 hint(editor, text, handler, popupPosition, maxUsages, options, false);
364 // all usages filtered out
367 else if (visibleNodes.size() == 1) {
368 if (usages.size() == 1) {
370 Usage usage = visibleNodes.iterator().next().getUsage();
371 if (usage == USAGES_OUTSIDE_SCOPE_SEPARATOR) {
372 hint(editor, UsageViewManagerImpl.outOfScopeMessage(outOfScopeUsages.get(), options.searchScope), handler, popupPosition, maxUsages, options, true);
375 String message = UsageViewBundle.message("show.usages.only.usage", searchScopePresentableName(options));
376 navigateAndHint(usage, message, handler, popupPosition, maxUsages, options);
381 assert usages.size() > 1 : usages;
382 // usage view can filter usages down to one
383 Usage visibleUsage = visibleNodes.iterator().next().getUsage();
384 if (areAllUsagesInOneLine(visibleUsage, usages)) {
385 String hint = UsageViewBundle.message("all.usages.are.in.this.line", usages.size(), searchScopePresentableName(options));
386 navigateAndHint(visibleUsage, hint, handler, popupPosition, maxUsages, options);
393 String title = presentation.getTabText();
394 boolean shouldShowMoreSeparator = visibleNodes.contains(MORE_USAGES_SEPARATOR_NODE);
396 getFullTitle(usages, title, shouldShowMoreSeparator, visibleNodes.size() - (shouldShowMoreSeparator ? 1 : 0), false);
397 ((AbstractPopup)popup).setCaption(fullTitle);
402 }, project.getDisposed());
406 Disposer.register(popup, new Disposable() {
408 public void dispose() {
416 private static UsageNode createStringNode(@NotNull final Object string) {
417 return new StringNode(string);
420 private static class MyModel extends ListTableModel<UsageNode> implements ModelDiff.Model<Object> {
421 private MyModel(@NotNull List<UsageNode> data, int cols) {
422 super(cols(cols), data, 0);
426 private static ColumnInfo[] cols(int cols) {
427 ColumnInfo<UsageNode, UsageNode> o = new ColumnInfo<UsageNode, UsageNode>("") {
430 public UsageNode valueOf(UsageNode node) {
434 List<ColumnInfo<UsageNode, UsageNode>> list = Collections.nCopies(cols, o);
435 return list.toArray(new ColumnInfo[list.size()]);
439 public void addToModel(int idx, Object element) {
440 UsageNode node = element instanceof UsageNode ? (UsageNode)element : createStringNode(element);
442 if (idx < getRowCount()) {
443 insertRow(idx, node);
451 public void removeRangeFromModel(int start, int end) {
452 for (int i=end; i>=start; i--) {
458 private static boolean showPopupIfNeedTo(@NotNull JBPopup popup, @NotNull RelativePoint popupPosition) {
459 if (!popup.isDisposed() && !popup.isVisible()) {
460 popup.show(popupPosition);
470 private JComponent createHintComponent(@NotNull String text,
471 @NotNull final FindUsagesHandler handler,
472 @NotNull final RelativePoint popupPosition,
474 @NotNull final Runnable cancelAction,
476 @NotNull final FindUsagesOptions options,
478 JComponent label = HintUtil.createInformationLabel(suggestSecondInvocation(options, handler, text + " "));
480 label.setBackground(MessageType.WARNING.getPopupBackground());
482 InplaceButton button = createSettingsButton(handler, popupPosition, editor, maxUsages, cancelAction);
484 JPanel panel = new JPanel(new BorderLayout()) {
486 public void addNotify() {
487 mySearchEverywhereRunnable = new Runnable() {
490 searchEverywhere(options, handler, editor, popupPosition, maxUsages);
497 public void removeNotify() {
498 mySearchEverywhereRunnable = null;
499 super.removeNotify();
502 button.setBackground(label.getBackground());
503 panel.setBackground(label.getBackground());
504 label.setOpaque(false);
505 label.setBorder(null);
506 panel.setBorder(HintUtil.createHintBorder());
507 panel.add(label, BorderLayout.CENTER);
508 panel.add(button, BorderLayout.EAST);
513 private InplaceButton createSettingsButton(@NotNull final FindUsagesHandler handler,
514 @NotNull final RelativePoint popupPosition,
517 @NotNull final Runnable cancelAction) {
518 String shortcutText = "";
519 KeyboardShortcut shortcut = UsageViewImpl.getShowUsagesWithSettingsShortcut();
520 if (shortcut != null) {
521 shortcutText = "(" + KeymapUtil.getShortcutText(shortcut) + ")";
523 return new InplaceButton("Settings..." + shortcutText, AllIcons.General.Settings, new ActionListener() {
525 public void actionPerformed(ActionEvent e) {
526 SwingUtilities.invokeLater(new Runnable() {
529 showDialogAndFindUsages(handler, popupPosition, editor, maxUsages);
537 private void showDialogAndFindUsages(@NotNull FindUsagesHandler handler,
538 @NotNull RelativePoint popupPosition,
541 AbstractFindUsagesDialog dialog = handler.getFindUsagesDialog(false, false, false);
542 if (dialog.showAndGet()) {
543 dialog.calcFindUsagesOptions();
544 FindUsagesOptions options = handler.getFindUsagesOptions(DataManager.getInstance().getDataContext());
545 showElementUsages(editor, popupPosition, handler, maxUsages, options);
550 private static String searchScopePresentableName(@NotNull FindUsagesOptions options) {
551 return options.searchScope.getDisplayName();
555 private Runnable prepareTable(final MyTable table,
557 final RelativePoint popupPosition,
558 final FindUsagesHandler handler,
560 @NotNull final FindUsagesOptions options,
561 final boolean previewMode) {
563 SpeedSearchBase<JTable> speedSearch = new MySpeedSearch(table);
564 speedSearch.setComparator(new SpeedSearchComparator(false));
566 table.setRowHeight(PlatformIcons.CLASS_ICON.getIconHeight()+2);
567 table.setShowGrid(false);
568 table.setShowVerticalLines(false);
569 table.setShowHorizontalLines(false);
570 table.setTableHeader(null);
571 table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
572 table.setIntercellSpacing(new Dimension(0, 0));
573 final AtomicReference<Object> selectedUsage = new AtomicReference<Object>();
574 final AtomicBoolean moreUsagesSelected = new AtomicBoolean();
575 final AtomicBoolean outsideScopeUsagesSelected = new AtomicBoolean();
576 final Runnable itemChosenCallback = new Runnable() {
579 if (moreUsagesSelected.get()) {
580 appendMoreUsages(editor, popupPosition, handler, maxUsages, options);
583 if (outsideScopeUsagesSelected.get()) {
584 options.searchScope = GlobalSearchScope.projectScope(handler.getProject());
585 showElementUsages(editor, popupPosition, handler, maxUsages, options);
588 Object usage = selectedUsage.get();
589 if (usage instanceof UsageInfo) {
590 UsageViewUtil.navigateTo((UsageInfo)usage, true);
592 else if (usage instanceof Navigatable) {
593 ((Navigatable)usage).navigate(true);
597 table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
599 public void valueChanged(ListSelectionEvent e) {
600 selectedUsage.set(null);
601 int[] selected = table.getSelectedRows();
602 for (int i : selected) {
603 Object value = table.getValueAt(i, 0);
604 if (value instanceof UsageNode) {
605 Usage usage = ((UsageNode)value).getUsage();
606 outsideScopeUsagesSelected.set(false);
607 moreUsagesSelected.set(false);
608 if (usage == USAGES_OUTSIDE_SCOPE_SEPARATOR) {
609 outsideScopeUsagesSelected.set(true);
610 selectedUsage.set(null);
612 else if (usage == MORE_USAGES_SEPARATOR) {
613 moreUsagesSelected.set(true);
614 selectedUsage.set(null);
617 selectedUsage.set(usage instanceof UsageInfo2UsageAdapter ? ((UsageInfo2UsageAdapter)usage).getUsageInfo().copy() : usage);
625 table.addMouseListener(new MouseAdapter() {
627 public void mouseReleased(MouseEvent e) {
628 if (UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED) && !UIUtil.isSelectionButtonDown(e) && !e.isConsumed()) {
629 itemChosenCallback.run();
633 table.addKeyListener(new KeyAdapter() {
635 public void keyPressed(KeyEvent e) {
636 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
637 itemChosenCallback.run();
642 return itemChosenCallback;
646 private JBPopup createUsagePopup(@NotNull final List<Usage> usages,
647 @NotNull Set<UsageNode> visibleNodes,
648 @NotNull final FindUsagesHandler handler,
650 @NotNull final RelativePoint popupPosition,
652 @NotNull final UsageViewImpl usageView,
653 @NotNull final FindUsagesOptions options,
654 @NotNull final JTable table,
655 @NotNull final Runnable itemChoseCallback,
656 @NotNull final UsageViewPresentation presentation,
657 @NotNull final AsyncProcessIcon processIcon) {
659 PopupChooserBuilder builder = new PopupChooserBuilder(table);
660 final String title = presentation.getTabText();
662 String result = getFullTitle(usages, title, false, visibleNodes.size() - 1, true);
663 builder.setTitle(result);
664 builder.setAdText(getSecondInvocationTitle(options, handler));
667 builder.setMovable(true).setResizable(true);
668 builder.setMovable(true).setResizable(true);
669 builder.setItemChoosenCallback(itemChoseCallback);
670 final JBPopup[] popup = new JBPopup[1];
672 KeyboardShortcut shortcut = UsageViewImpl.getShowUsagesWithSettingsShortcut();
673 if (shortcut != null) {
674 new DumbAwareAction() {
676 public void actionPerformed(@NotNull AnActionEvent e) {
678 showDialogAndFindUsages(handler, popupPosition, editor, maxUsages);
680 }.registerCustomShortcutSet(new CustomShortcutSet(shortcut.getFirstKeyStroke()), table);
682 shortcut = getShowUsagesShortcut();
683 if (shortcut != null) {
684 new DumbAwareAction() {
686 public void actionPerformed(@NotNull AnActionEvent e) {
688 searchEverywhere(options, handler, editor, popupPosition, maxUsages);
690 }.registerCustomShortcutSet(new CustomShortcutSet(shortcut.getFirstKeyStroke()), table);
693 InplaceButton settingsButton = createSettingsButton(handler, popupPosition, editor, maxUsages, new Runnable() {
700 ActiveComponent spinningProgress = new ActiveComponent() {
702 public void setActive(boolean active) {
706 public JComponent getComponent() {
710 final DefaultActionGroup pinGroup = new DefaultActionGroup();
711 final ActiveComponent pin = createPinButton(handler, usageView, options, popup, pinGroup);
712 builder.setCommandButton(new CompositeActiveComponent(spinningProgress, settingsButton, pin));
714 DefaultActionGroup toolbar = new DefaultActionGroup();
715 usageView.addFilteringActions(toolbar);
717 toolbar.add(UsageGroupingRuleProviderImpl.createGroupByFileStructureAction(usageView));
718 ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, toolbar, true);
719 actionToolbar.setReservePlaceAutoPopupIcon(false);
720 final JComponent toolBar = actionToolbar.getComponent();
721 toolBar.setOpaque(false);
722 builder.setSettingButton(toolBar);
724 popup[0] = builder.createPopup();
725 JComponent content = popup[0].getContent();
727 myWidth = (int)(toolBar.getPreferredSize().getWidth()
728 + new JLabel(getFullTitle(usages, title, false, visibleNodes.size() - 1, true)).getPreferredSize().getWidth()
729 + settingsButton.getPreferredSize().getWidth());
731 for (AnAction action : toolbar.getChildren(null)) {
732 action.unregisterCustomShortcutSet(usageView.getComponent());
733 action.registerCustomShortcutSet(action.getShortcutSet(), content);
736 for (AnAction action : pinGroup.getChildren(null)) {
737 action.unregisterCustomShortcutSet(usageView.getComponent());
738 action.registerCustomShortcutSet(action.getShortcutSet(), content);
744 private ActiveComponent createPinButton(@NotNull final FindUsagesHandler handler,
745 @NotNull final UsageViewImpl usageView,
746 @NotNull final FindUsagesOptions options,
747 @NotNull final JBPopup[] popup,
748 @NotNull DefaultActionGroup pinGroup) {
749 final AnAction pinAction =
750 new AnAction("Open Find Usages Toolwindow", "Show all usages in a separate toolwindow", AllIcons.General.AutohideOff) {
752 AnAction action = ActionManager.getInstance().getAction(IdeActions.ACTION_FIND_USAGES);
753 setShortcutSet(action.getShortcutSet());
757 public void actionPerformed(@NotNull AnActionEvent e) {
760 FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(usageView.getProject())).getFindUsagesManager();
761 findUsagesManager.findUsages(handler.getPrimaryElements(), handler.getSecondaryElements(), handler, options,
762 FindSettings.getInstance().isSkipResultsWithOneUsage());
765 pinGroup.add(pinAction);
766 final ActionToolbar pinToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, pinGroup, true);
767 pinToolbar.setReservePlaceAutoPopupIcon(false);
768 final JComponent pinToolBar = pinToolbar.getComponent();
769 pinToolBar.setBorder(null);
770 pinToolBar.setOpaque(false);
772 return new ActiveComponent() {
774 public void setActive(boolean active) {
778 public JComponent getComponent() {
784 private static void cancel(@Nullable JBPopup... popup) {
785 if (popup != null && popup.length>0 && popup[0] != null) {
791 private static String getFullTitle(@NotNull List<Usage> usages,
792 @NotNull String title,
793 boolean hadMoreSeparator,
794 int visibleNodesCount,
795 boolean findUsagesInProgress) {
797 if (hadMoreSeparator) {
798 s = "<b>Some</b> " + title + " " + "<b>(Only " + visibleNodesCount + " usages shown" +(findUsagesInProgress ? " so far" : "")+")</b>";
801 s = title + " (" + UsageViewBundle.message("usages.n", usages.size()) + (findUsagesInProgress ? " so far" : "") + ")";
803 return "<html><nobr>" + s + "</nobr></html>";
807 private static String suggestSecondInvocation(@NotNull FindUsagesOptions options, @NotNull FindUsagesHandler handler, @NotNull String text) {
808 final String title = getSecondInvocationTitle(options, handler);
811 text += "<br><small> " + title + "</small>";
813 return XmlStringUtil.wrapInHtml(text);
817 private static String getSecondInvocationTitle(@NotNull FindUsagesOptions options, @NotNull FindUsagesHandler handler) {
818 if (getShowUsagesShortcut() != null) {
819 GlobalSearchScope maximalScope = FindUsagesManager.getMaximalScope(handler);
820 if (!options.searchScope.equals(maximalScope)) {
821 return "Press " + KeymapUtil.getShortcutText(getShowUsagesShortcut()) + " again to search in " + maximalScope.getDisplayName();
827 private void searchEverywhere(@NotNull FindUsagesOptions options,
828 @NotNull FindUsagesHandler handler,
830 @NotNull RelativePoint popupPosition,
832 FindUsagesOptions cloned = options.clone();
833 cloned.searchScope = FindUsagesManager.getMaximalScope(handler);
834 showElementUsages(editor, popupPosition, handler, maxUsages, cloned);
838 private static KeyboardShortcut getShowUsagesShortcut() {
839 return ActionManager.getInstance().getKeyboardShortcut(ID);
842 private static int filtered(@NotNull List<Usage> usages, @NotNull UsageViewImpl usageView) {
844 for (Usage usage : usages) {
845 if (!usageView.isVisible(usage)) count++;
850 private static int getUsageOffset(@NotNull Usage usage) {
851 if (!(usage instanceof UsageInfo2UsageAdapter)) return -1;
852 PsiElement element = ((UsageInfo2UsageAdapter)usage).getElement();
853 if (element == null) return -1;
854 return element.getTextRange().getStartOffset();
857 private static boolean areAllUsagesInOneLine(@NotNull Usage visibleUsage, @NotNull List<Usage> usages) {
858 Editor editor = getEditorFor(visibleUsage);
859 if (editor == null) return false;
860 int offset = getUsageOffset(visibleUsage);
861 if (offset == -1) return false;
862 int lineNumber = editor.getDocument().getLineNumber(offset);
863 for (Usage other : usages) {
864 Editor otherEditor = getEditorFor(other);
865 if (otherEditor != editor) return false;
866 int otherOffset = getUsageOffset(other);
867 if (otherOffset == -1) return false;
868 int otherLine = otherEditor.getDocument().getLineNumber(otherOffset);
869 if (otherLine != lineNumber) return false;
875 private static MyModel setTableModel(@NotNull JTable table,
876 @NotNull UsageViewImpl usageView,
877 @NotNull final List<UsageNode> data,
878 @NotNull AtomicInteger outOfScopeUsages,
879 @NotNull SearchScope searchScope) {
880 ApplicationManager.getApplication().assertIsDispatchThread();
881 final int columnCount = calcColumnCount(data);
882 MyModel model = table.getModel() instanceof MyModel ? (MyModel)table.getModel() : null;
883 if (model == null || model.getColumnCount() != columnCount) {
884 model = new MyModel(data, columnCount);
885 table.setModel(model);
887 ShowUsagesTableCellRenderer renderer = new ShowUsagesTableCellRenderer(usageView, outOfScopeUsages, searchScope);
888 for (int i=0;i<table.getColumnModel().getColumnCount();i++) {
889 TableColumn column = table.getColumnModel().getColumn(i);
890 column.setCellRenderer(renderer);
896 private static int calcColumnCount(@NotNull List<UsageNode> data) {
897 return data.isEmpty() || data.get(0) instanceof StringNode ? 1 : 3;
901 private static List<UsageNode> collectData(@NotNull List<Usage> usages,
902 @NotNull Collection<UsageNode> visibleNodes,
903 @NotNull UsageViewImpl usageView,
904 @NotNull UsageViewPresentation presentation) {
905 @NotNull List<UsageNode> data = new ArrayList<UsageNode>();
906 int filtered = filtered(usages, usageView);
908 data.add(createStringNode(UsageViewBundle.message("usages.were.filtered.out", filtered)));
910 data.addAll(visibleNodes);
911 if (data.isEmpty()) {
912 String progressText = StringUtil.escapeXml(UsageViewManagerImpl.getProgressTitle(presentation));
913 data.add(createStringNode(progressText));
915 Collections.sort(data, USAGE_NODE_COMPARATOR);
919 private static int calcMaxWidth(JTable table) {
920 int colsNum = table.getColumnModel().getColumnCount();
923 for (int col = 0; col < colsNum -1; col++) {
924 TableColumn column = table.getColumnModel().getColumn(col);
925 int preferred = column.getPreferredWidth();
926 int width = Math.max(preferred, columnMaxWidth(table, col));
928 column.setMinWidth(width);
929 column.setMaxWidth(width);
930 column.setWidth(width);
931 column.setPreferredWidth(width);
934 totalWidth += columnMaxWidth(table, colsNum - 1);
939 private static int columnMaxWidth(@NotNull JTable table, int col) {
940 TableColumn column = table.getColumnModel().getColumn(col);
942 for (int row = 0; row < table.getRowCount(); row++) {
943 Component component = table.prepareRenderer(column.getCellRenderer(), row, col);
945 int rendererWidth = component.getPreferredSize().width;
946 width = Math.max(width, rendererWidth + table.getIntercellSpacing().width);
953 private void rebuildTable(@NotNull final UsageViewImpl usageView,
954 @NotNull final List<Usage> usages,
955 @NotNull List<UsageNode> nodes,
956 @NotNull final JTable table,
957 @Nullable final JBPopup popup,
958 @NotNull final UsageViewPresentation presentation,
959 @NotNull final RelativePoint popupPosition,
960 boolean findUsagesInProgress,
961 @NotNull AtomicInteger outOfScopeUsages,
962 @NotNull SearchScope searchScope) {
963 ApplicationManager.getApplication().assertIsDispatchThread();
965 boolean shouldShowMoreSeparator = usages.contains(MORE_USAGES_SEPARATOR);
966 if (shouldShowMoreSeparator) {
967 nodes.add(MORE_USAGES_SEPARATOR_NODE);
969 boolean hasOutsideScopeUsages = usages.contains(USAGES_OUTSIDE_SCOPE_SEPARATOR);
970 if (hasOutsideScopeUsages && !shouldShowMoreSeparator) {
971 nodes.add(USAGES_OUTSIDE_SCOPE_NODE);
974 String title = presentation.getTabText();
975 String fullTitle = getFullTitle(usages, title, shouldShowMoreSeparator || hasOutsideScopeUsages, nodes.size() - (shouldShowMoreSeparator || hasOutsideScopeUsages ? 1 : 0), findUsagesInProgress);
977 ((AbstractPopup)popup).setCaption(fullTitle);
980 List<UsageNode> data = collectData(usages, nodes, usageView, presentation);
981 MyModel tableModel = setTableModel(table, usageView, data, outOfScopeUsages, searchScope);
982 List<UsageNode> existingData = tableModel.getItems();
984 int row = table.getSelectedRow();
986 int newSelection = updateModel(tableModel, existingData, data, row == -1 ? 0 : row);
987 if (newSelection < 0 || newSelection >= tableModel.getRowCount()) {
988 TableScrollingUtil.ensureSelectionExists(table);
989 newSelection = table.getSelectedRow();
992 table.getSelectionModel().setSelectionInterval(newSelection, newSelection);
994 TableScrollingUtil.ensureIndexIsVisible(table, newSelection, 0);
997 setSizeAndDimensions(table, popup, popupPosition, data);
1001 // returns new selection
1002 private static int updateModel(@NotNull MyModel tableModel, @NotNull List<UsageNode> listOld, @NotNull List<UsageNode> listNew, int oldSelection) {
1003 UsageNode[] oa = listOld.toArray(new UsageNode[listOld.size()]);
1004 UsageNode[] na = listNew.toArray(new UsageNode[listNew.size()]);
1005 List<ModelDiff.Cmd> cmds = ModelDiff.createDiffCmds(tableModel, oa, na);
1006 int selection = oldSelection;
1008 for (ModelDiff.Cmd cmd : cmds) {
1009 selection = cmd.translateSelection(selection);
1016 private void setSizeAndDimensions(@NotNull JTable table,
1017 @NotNull JBPopup popup,
1018 @NotNull RelativePoint popupPosition,
1019 @NotNull List<UsageNode> data) {
1020 JComponent content = popup.getContent();
1021 Window window = SwingUtilities.windowForComponent(content);
1022 Dimension d = window.getSize();
1024 int width = calcMaxWidth(table);
1025 width = (int)Math.max(d.getWidth(), width);
1026 Dimension headerSize = ((AbstractPopup)popup).getHeaderPreferredSize();
1027 width = Math.max((int)headerSize.getWidth(), width);
1028 width = Math.max(myWidth, width);
1030 if (myWidth == -1) myWidth = width;
1031 int newWidth = Math.max(width, d.width + width - myWidth);
1035 int rowsToShow = Math.min(30, data.size());
1036 Dimension dimension = new Dimension(newWidth, table.getRowHeight() * rowsToShow);
1037 Rectangle rectangle = fitToScreen(dimension, popupPosition, table);
1038 if (!data.isEmpty()) {
1039 TableScrollingUtil.ensureSelectionExists(table);
1041 table.setSize(rectangle.getSize());
1042 //table.setPreferredSize(dimension);
1043 //table.setMaximumSize(dimension);
1044 //table.setPreferredScrollableViewportSize(dimension);
1047 Dimension footerSize = ((AbstractPopup)popup).getFooterPreferredSize();
1049 rectangle.height += headerSize.height + footerSize.height + 4/* invisible borders, margins etc*/;
1050 ScreenUtil.fitToScreen(rectangle);
1051 Dimension newDim = rectangle.getSize();
1052 window.setBounds(rectangle);
1053 window.setMinimumSize(newDim);
1054 window.setMaximumSize(newDim);
1062 private static Rectangle fitToScreen(@NotNull Dimension newDim, @NotNull RelativePoint popupPosition, JTable table) {
1063 Rectangle rectangle = new Rectangle(popupPosition.getScreenPoint(), newDim);
1064 ScreenUtil.fitToScreen(rectangle);
1065 if (rectangle.getHeight() != newDim.getHeight()) {
1066 int newHeight = (int)rectangle.getHeight();
1067 int roundedHeight = newHeight - newHeight % table.getRowHeight();
1068 rectangle.setSize((int)rectangle.getWidth(), Math.max(roundedHeight, table.getRowHeight()));
1074 private void appendMoreUsages(Editor editor,
1075 @NotNull RelativePoint popupPosition,
1076 @NotNull FindUsagesHandler handler,
1078 @NotNull FindUsagesOptions options) {
1079 showElementUsages(editor, popupPosition, handler, maxUsages+USAGES_PAGE_SIZE, options);
1082 private static void addUsageNodes(@NotNull GroupNode root, @NotNull final UsageViewImpl usageView, @NotNull List<UsageNode> outNodes) {
1083 for (UsageNode node : root.getUsageNodes()) {
1084 Usage usage = node.getUsage();
1085 if (usageView.isVisible(usage)) {
1086 node.setParent(root);
1090 for (GroupNode groupNode : root.getSubGroups()) {
1091 groupNode.setParent(root);
1092 addUsageNodes(groupNode, usageView, outNodes);
1097 public void update(@NotNull AnActionEvent e){
1098 FindUsagesInFileAction.updateFindUsagesAction(e);
1101 private void navigateAndHint(@NotNull Usage usage,
1102 @Nullable final String hint,
1103 @NotNull final FindUsagesHandler handler,
1104 @NotNull final RelativePoint popupPosition,
1105 final int maxUsages,
1106 @NotNull final FindUsagesOptions options) {
1107 usage.navigate(true);
1108 if (hint == null) return;
1109 final Editor newEditor = getEditorFor(usage);
1110 if (newEditor == null) return;
1111 hint(newEditor, hint, handler, popupPosition, maxUsages, options, false);
1114 private void showHint(@Nullable final Editor editor,
1115 @NotNull String hint,
1116 @NotNull FindUsagesHandler handler,
1117 @NotNull final RelativePoint popupPosition,
1119 @NotNull FindUsagesOptions options,
1120 boolean isWarning) {
1121 JComponent label = createHintComponent(hint, handler, popupPosition, editor, HIDE_HINTS_ACTION, maxUsages, options, isWarning);
1122 if (editor == null || editor.isDisposed() || !editor.getComponent().isShowing()) {
1123 HintManager.getInstance().showHint(label, popupPosition, HintManager.HIDE_BY_ANY_KEY |
1124 HintManager.HIDE_BY_TEXT_CHANGE | HintManager.HIDE_BY_SCROLLING, 0);
1127 HintManager.getInstance().showInformationHint(editor, label);
1131 private void hint(@Nullable final Editor editor,
1132 @NotNull final String hint,
1133 @NotNull final FindUsagesHandler handler,
1134 @NotNull final RelativePoint popupPosition,
1135 final int maxUsages,
1136 @NotNull final FindUsagesOptions options,
1137 final boolean isWarning) {
1138 final Project project = handler.getProject();
1139 //opening editor is performing in invokeLater
1140 IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(new Runnable() {
1143 Runnable runnable = new Runnable() {
1146 // after new editor created, some editor resizing events are still bubbling. To prevent hiding hint, invokeLater this
1147 IdeFocusManager.getInstance(project).doWhenFocusSettlesDown(new Runnable() {
1150 showHint(editor, hint, handler, popupPosition, maxUsages, options, isWarning);
1155 if (editor == null) runnable.run();
1156 else editor.getScrollingModel().runActionOnScrollingFinished(runnable);
1162 private static Editor getEditorFor(@NotNull Usage usage) {
1163 FileEditorLocation location = usage.getLocation();
1164 FileEditor newFileEditor = location == null ? null : location.getEditor();
1165 return newFileEditor instanceof TextEditor ? ((TextEditor)newFileEditor).getEditor() : null;
1168 private static class MyTable extends JBTableWithHintProvider implements DataProvider {
1170 public boolean getScrollableTracksViewportWidth() {
1175 public Object getData(@NonNls String dataId) {
1176 if (CommonDataKeys.PSI_ELEMENT.is(dataId)) {
1177 final int[] selected = getSelectedRows();
1178 if (selected.length == 1) {
1179 return getPsiElementForHint(getValueAt(selected[0], 0));
1187 protected PsiElement getPsiElementForHint(Object selectedValue) {
1188 if (selectedValue instanceof UsageNode) {
1189 final Usage usage = ((UsageNode)selectedValue).getUsage();
1190 if (usage instanceof UsageInfo2UsageAdapter) {
1191 final PsiElement element = ((UsageInfo2UsageAdapter)usage).getElement();
1192 if (element != null) {
1193 final PsiElement view = UsageToPsiElementProvider.findAppropriateParentFrom(element);
1194 return view == null ? element : view;
1202 static class StringNode extends UsageNode {
1203 @NotNull private final Object myString;
1205 public StringNode(@NotNull Object string) {
1206 super(NullUsage.INSTANCE, new UsageViewTreeModelBuilder(new UsageViewPresentation(), UsageTarget.EMPTY_ARRAY));
1211 public String toString() {
1212 return myString.toString();
1216 private static class MySpeedSearch extends SpeedSearchBase<JTable> {
1217 public MySpeedSearch(@NotNull MyTable table) {
1222 protected int getSelectedIndex() {
1223 return getTable().getSelectedRow();
1227 protected int convertIndexToModel(int viewIndex) {
1228 return getTable().convertRowIndexToModel(viewIndex);
1233 protected Object[] getAllElements() {
1234 return ((MyModel)getTable().getModel()).getItems().toArray();
1238 protected String getElementText(@NotNull Object element) {
1239 if (!(element instanceof UsageNode)) return element.toString();
1240 UsageNode node = (UsageNode)element;
1241 if (node instanceof StringNode) return "";
1242 Usage usage = node.getUsage();
1243 if (usage == MORE_USAGES_SEPARATOR || usage == USAGES_OUTSIDE_SCOPE_SEPARATOR) return "";
1244 GroupNode group = (GroupNode)node.getParent();
1245 return usage.getPresentation().getPlainText() + group;
1249 protected void selectElement(Object element, String selectedText) {
1250 List<UsageNode> data = ((MyModel)getTable().getModel()).getItems();
1251 int i = data.indexOf(element);
1252 if (i == -1) return;
1253 final int viewRow = getTable().convertRowIndexToView(i);
1254 getTable().getSelectionModel().setSelectionInterval(viewRow, viewRow);
1255 TableUtil.scrollSelectionToVisible(getTable());
1258 private MyTable getTable() {
1259 return (MyTable)myComponent;