PageRenderTime 40ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/platform/lang-impl/src/com/intellij/codeInsight/navigation/GotoTargetHandler.java

http://github.com/JetBrains/intellij-community
Java | 376 lines | 322 code | 50 blank | 4 comment | 55 complexity | e1dd3fecb8c83f80b507bee1764dc2e3 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0
  1. // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
  2. package com.intellij.codeInsight.navigation;
  3. import com.intellij.codeInsight.CodeInsightActionHandler;
  4. import com.intellij.codeInsight.CodeInsightBundle;
  5. import com.intellij.codeInsight.hint.HintManager;
  6. import com.intellij.featureStatistics.FeatureUsageTracker;
  7. import com.intellij.find.FindUtil;
  8. import com.intellij.ide.util.EditSourceUtil;
  9. import com.intellij.ide.util.PsiElementListCellRenderer;
  10. import com.intellij.navigation.ItemPresentation;
  11. import com.intellij.navigation.NavigationItem;
  12. import com.intellij.openapi.application.ReadAction;
  13. import com.intellij.openapi.diagnostic.Logger;
  14. import com.intellij.openapi.editor.Editor;
  15. import com.intellij.openapi.editor.ex.util.EditorUtil;
  16. import com.intellij.openapi.progress.ProgressManager;
  17. import com.intellij.openapi.project.DumbService;
  18. import com.intellij.openapi.project.IndexNotReadyException;
  19. import com.intellij.openapi.project.Project;
  20. import com.intellij.openapi.ui.popup.IPopupChooserBuilder;
  21. import com.intellij.openapi.ui.popup.JBPopup;
  22. import com.intellij.openapi.ui.popup.JBPopupFactory;
  23. import com.intellij.openapi.ui.popup.PopupChooserBuilder;
  24. import com.intellij.openapi.util.Ref;
  25. import com.intellij.pom.Navigatable;
  26. import com.intellij.psi.PsiElement;
  27. import com.intellij.psi.PsiFile;
  28. import com.intellij.psi.PsiNamedElement;
  29. import com.intellij.psi.util.PsiUtilCore;
  30. import com.intellij.usages.UsageView;
  31. import com.intellij.util.Alarm;
  32. import com.intellij.util.ArrayUtil;
  33. import com.intellij.util.Consumer;
  34. import org.jetbrains.annotations.NonNls;
  35. import org.jetbrains.annotations.NotNull;
  36. import org.jetbrains.annotations.Nullable;
  37. import javax.swing.*;
  38. import java.awt.*;
  39. import java.util.List;
  40. import java.util.*;
  41. public abstract class GotoTargetHandler implements CodeInsightActionHandler {
  42. private static final Logger LOG = Logger.getInstance(GotoTargetHandler.class);
  43. private final PsiElementListCellRenderer myDefaultTargetElementRenderer = new DefaultPsiElementListCellRenderer();
  44. private final DefaultListCellRenderer myActionElementRenderer = new ActionCellRenderer();
  45. @Override
  46. public boolean startInWriteAction() {
  47. return false;
  48. }
  49. @Override
  50. public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
  51. FeatureUsageTracker.getInstance().triggerFeatureUsed(getFeatureUsedKey());
  52. try {
  53. GotoData gotoData = getSourceAndTargetElements(editor, file);
  54. if (gotoData != null) {
  55. show(project, editor, file, gotoData);
  56. }
  57. else {
  58. chooseFromAmbiguousSources(editor, file, data -> show(project, editor, file, data));
  59. }
  60. }
  61. catch (IndexNotReadyException e) {
  62. DumbService.getInstance(project).showDumbModeNotification(
  63. CodeInsightBundle.message("message.navigation.is.not.available.here.during.index.update"));
  64. }
  65. }
  66. protected void chooseFromAmbiguousSources(Editor editor, PsiFile file, Consumer<? super GotoData> successCallback) { }
  67. @NonNls
  68. protected abstract String getFeatureUsedKey();
  69. protected boolean useEditorFont() {
  70. return true;
  71. }
  72. @Nullable
  73. protected abstract GotoData getSourceAndTargetElements(Editor editor, PsiFile file);
  74. private void show(@NotNull Project project,
  75. @NotNull Editor editor,
  76. @NotNull PsiFile file,
  77. @NotNull GotoData gotoData) {
  78. PsiElement[] targets = gotoData.targets;
  79. List<AdditionalAction> additionalActions = gotoData.additionalActions;
  80. if (targets.length == 0 && additionalActions.isEmpty()) {
  81. HintManager.getInstance().showErrorHint(editor, getNotFoundMessage(project, editor, file));
  82. return;
  83. }
  84. boolean finished = gotoData.listUpdaterTask == null || gotoData.listUpdaterTask.isFinished();
  85. if (targets.length == 1 && additionalActions.isEmpty() && finished) {
  86. navigateToElement(targets[0]);
  87. return;
  88. }
  89. for (PsiElement eachTarget : targets) {
  90. gotoData.renderers.put(eachTarget, createRenderer(gotoData, eachTarget));
  91. }
  92. final String name = ((NavigationItem)gotoData.source).getName();
  93. final String title = getChooserTitle(gotoData.source, name, targets.length, finished);
  94. if (shouldSortTargets()) {
  95. Arrays.sort(targets, createComparator(gotoData));
  96. }
  97. List<Object> allElements = new ArrayList<>(targets.length + additionalActions.size());
  98. Collections.addAll(allElements, targets);
  99. allElements.addAll(additionalActions);
  100. final IPopupChooserBuilder<Object> builder = JBPopupFactory.getInstance().createPopupChooserBuilder(allElements);
  101. final Ref<UsageView> usageView = new Ref<>();
  102. builder.setNamerForFiltering(o -> {
  103. if (o instanceof AdditionalAction) {
  104. return ((AdditionalAction)o).getText();
  105. }
  106. return getRenderer(o, gotoData).getElementText((PsiElement)o);
  107. }).setTitle(title);
  108. if (useEditorFont()) {
  109. builder.setFont(EditorUtil.getEditorFont());
  110. }
  111. builder.setRenderer(new DefaultListCellRenderer() {
  112. @Override
  113. public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
  114. if (value == null) return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
  115. if (value instanceof AdditionalAction) {
  116. return myActionElementRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
  117. }
  118. PsiElementListCellRenderer renderer = getRenderer(value, gotoData);
  119. return renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
  120. }
  121. }).
  122. setItemsChosenCallback(selectedElements -> {
  123. for (Object element : selectedElements) {
  124. if (element instanceof AdditionalAction) {
  125. ((AdditionalAction)element).execute();
  126. }
  127. else {
  128. Navigatable nav = element instanceof Navigatable ? (Navigatable)element : EditSourceUtil.getDescriptor((PsiElement)element);
  129. try {
  130. if (nav != null && nav.canNavigate()) {
  131. navigateToElement(nav);
  132. }
  133. }
  134. catch (IndexNotReadyException e) {
  135. DumbService.getInstance(project).showDumbModeNotification(
  136. CodeInsightBundle.message("notification.navigation.is.not.available.while.indexing"));
  137. }
  138. }
  139. }
  140. }).
  141. withHintUpdateSupply().
  142. setMovable(true).
  143. setCancelCallback(() -> {
  144. final BackgroundUpdaterTask task = gotoData.listUpdaterTask;
  145. if (task != null) {
  146. task.cancelTask();
  147. }
  148. return true;
  149. }).
  150. setCouldPin(popup1 -> {
  151. usageView.set(FindUtil.showInUsageView(gotoData.source, gotoData.targets,
  152. getFindUsagesTitle(gotoData.source, name, gotoData.targets.length),
  153. gotoData.source.getProject()));
  154. popup1.cancel();
  155. return false;
  156. }).
  157. setAdText(getAdText(gotoData.source, targets.length));
  158. final JBPopup popup = builder.createPopup();
  159. JScrollPane pane = builder instanceof PopupChooserBuilder ? ((PopupChooserBuilder)builder).getScrollPane() : null;
  160. if (pane != null) {
  161. pane.setBorder(null);
  162. pane.setViewportBorder(null);
  163. }
  164. if (gotoData.listUpdaterTask != null) {
  165. Alarm alarm = new Alarm(popup);
  166. alarm.addRequest(() -> {
  167. if (!editor.isDisposed()) {
  168. popup.showInBestPositionFor(editor);
  169. }
  170. }, 300);
  171. gotoData.listUpdaterTask.init(popup, builder.getBackgroundUpdater(), usageView);
  172. ProgressManager.getInstance().run(gotoData.listUpdaterTask);
  173. }
  174. else {
  175. popup.showInBestPositionFor(editor);
  176. }
  177. }
  178. @NotNull
  179. protected PsiElementListCellRenderer getRenderer(Object value, @NotNull GotoData gotoData) {
  180. PsiElementListCellRenderer renderer = gotoData.getRenderer(value);
  181. return renderer != null ? renderer : myDefaultTargetElementRenderer;
  182. }
  183. @NotNull
  184. protected Comparator<PsiElement> createComparator(@NotNull GotoData gotoData) {
  185. return new Comparator<PsiElement>() {
  186. @Override
  187. public int compare(PsiElement o1, PsiElement o2) {
  188. return getComparingObject(o1).compareTo(getComparingObject(o2));
  189. }
  190. private Comparable getComparingObject(PsiElement o1) {
  191. return getRenderer(o1, gotoData).getComparingObject(o1);
  192. }
  193. };
  194. }
  195. public static PsiElementListCellRenderer createRenderer(@NotNull GotoData gotoData, @NotNull PsiElement eachTarget) {
  196. for (GotoTargetRendererProvider eachProvider : GotoTargetRendererProvider.EP_NAME.getExtensionList()) {
  197. PsiElementListCellRenderer renderer = eachProvider.getRenderer(eachTarget, gotoData);
  198. if (renderer != null) return renderer;
  199. }
  200. return null;
  201. }
  202. protected boolean navigateToElement(PsiElement target) {
  203. Navigatable descriptor = target instanceof Navigatable ? (Navigatable)target : EditSourceUtil.getDescriptor(target);
  204. if (descriptor != null && descriptor.canNavigate()) {
  205. navigateToElement(descriptor);
  206. return true;
  207. }
  208. return false;
  209. }
  210. protected void navigateToElement(@NotNull Navigatable descriptor) {
  211. descriptor.navigate(true);
  212. }
  213. protected boolean shouldSortTargets() {
  214. return true;
  215. }
  216. /**
  217. * @deprecated, use getChooserTitle(PsiElement, String, int, boolean) instead
  218. */
  219. @Deprecated
  220. @NotNull
  221. protected String getChooserTitle(PsiElement sourceElement, String name, int length) {
  222. LOG.warn("Please override getChooserTitle(PsiElement, String, int, boolean) instead");
  223. return "";
  224. }
  225. @NotNull
  226. protected String getChooserTitle(@NotNull PsiElement sourceElement, @Nullable String name, int length, boolean finished) {
  227. return getChooserTitle(sourceElement, name, length);
  228. }
  229. @NotNull
  230. protected String getFindUsagesTitle(@NotNull PsiElement sourceElement, String name, int length) {
  231. return getChooserTitle(sourceElement, name, length, true);
  232. }
  233. @NotNull
  234. protected abstract String getNotFoundMessage(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file);
  235. @Nullable
  236. protected String getAdText(PsiElement source, int length) {
  237. return null;
  238. }
  239. public interface AdditionalAction {
  240. @NotNull
  241. String getText();
  242. Icon getIcon();
  243. void execute();
  244. }
  245. public static class GotoData {
  246. @NotNull public final PsiElement source;
  247. public PsiElement[] targets;
  248. public final List<AdditionalAction> additionalActions;
  249. private boolean hasDifferentNames;
  250. public BackgroundUpdaterTask listUpdaterTask;
  251. protected final Set<String> myNames;
  252. public Map<Object, PsiElementListCellRenderer> renderers = new HashMap<>();
  253. public GotoData(@NotNull PsiElement source, PsiElement @NotNull [] targets, @NotNull List<AdditionalAction> additionalActions) {
  254. this.source = source;
  255. this.targets = targets;
  256. this.additionalActions = additionalActions;
  257. myNames = new HashSet<>();
  258. for (PsiElement target : targets) {
  259. if (target instanceof PsiNamedElement) {
  260. myNames.add(((PsiNamedElement)target).getName());
  261. if (myNames.size() > 1) break;
  262. }
  263. }
  264. hasDifferentNames = myNames.size() > 1;
  265. }
  266. public boolean hasDifferentNames() {
  267. return hasDifferentNames;
  268. }
  269. public boolean addTarget(final PsiElement element) {
  270. if (ArrayUtil.find(targets, element) > -1) return false;
  271. targets = ArrayUtil.append(targets, element);
  272. renderers.put(element, createRenderer(this, element));
  273. if (!hasDifferentNames && element instanceof PsiNamedElement) {
  274. final String name = ReadAction.compute(() -> ((PsiNamedElement)element).getName());
  275. myNames.add(name);
  276. hasDifferentNames = myNames.size() > 1;
  277. }
  278. return true;
  279. }
  280. public PsiElementListCellRenderer getRenderer(Object value) {
  281. return renderers.get(value);
  282. }
  283. }
  284. private static class DefaultPsiElementListCellRenderer extends PsiElementListCellRenderer {
  285. @Override
  286. public String getElementText(final PsiElement element) {
  287. if (element instanceof PsiNamedElement) {
  288. String name = ((PsiNamedElement)element).getName();
  289. if (name != null) {
  290. return name;
  291. }
  292. }
  293. PsiFile file = element.getContainingFile();
  294. if (file == null) {
  295. PsiUtilCore.ensureValid(element);
  296. LOG.error("No file for " + element.getClass());
  297. return element.toString();
  298. }
  299. return file.getName();
  300. }
  301. @Override
  302. protected String getContainerText(final PsiElement element, final String name) {
  303. if (element instanceof NavigationItem) {
  304. final ItemPresentation presentation = ((NavigationItem)element).getPresentation();
  305. return presentation != null ? presentation.getLocationString():null;
  306. }
  307. return null;
  308. }
  309. @Override
  310. protected int getIconFlags() {
  311. return 0;
  312. }
  313. }
  314. private static class ActionCellRenderer extends DefaultListCellRenderer {
  315. @Override
  316. public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
  317. Component result = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
  318. if (value != null) {
  319. AdditionalAction action = (AdditionalAction)value;
  320. setText(action.getText());
  321. setIcon(action.getIcon());
  322. }
  323. return result;
  324. }
  325. }
  326. }