Cleanup (formatting; unneeded assertions)
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / navigation / NavigationUtil.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.intellij.codeInsight.navigation;
18
19 import com.intellij.ide.util.DefaultPsiElementCellRenderer;
20 import com.intellij.ide.util.EditSourceUtil;
21 import com.intellij.ide.util.PsiElementListCellRenderer;
22 import com.intellij.navigation.GotoRelatedItem;
23 import com.intellij.navigation.GotoRelatedProvider;
24 import com.intellij.navigation.NavigationItem;
25 import com.intellij.openapi.actionSystem.DataContext;
26 import com.intellij.openapi.editor.Editor;
27 import com.intellij.openapi.editor.ex.MarkupModelEx;
28 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
29 import com.intellij.openapi.editor.impl.DocumentMarkupModel;
30 import com.intellij.openapi.editor.markup.HighlighterTargetArea;
31 import com.intellij.openapi.editor.markup.MarkupModel;
32 import com.intellij.openapi.editor.markup.TextAttributes;
33 import com.intellij.openapi.extensions.Extensions;
34 import com.intellij.openapi.fileEditor.FileEditor;
35 import com.intellij.openapi.fileEditor.FileEditorManager;
36 import com.intellij.openapi.fileEditor.TextEditor;
37 import com.intellij.openapi.fileEditor.impl.EditorHistoryManager;
38 import com.intellij.openapi.ui.popup.JBPopup;
39 import com.intellij.openapi.ui.popup.PopupChooserBuilder;
40 import com.intellij.openapi.ui.popup.PopupStep;
41 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
42 import com.intellij.openapi.util.Ref;
43 import com.intellij.openapi.util.TextRange;
44 import com.intellij.openapi.util.text.StringUtil;
45 import com.intellij.openapi.vfs.VirtualFile;
46 import com.intellij.pom.Navigatable;
47 import com.intellij.psi.PsiElement;
48 import com.intellij.psi.PsiFile;
49 import com.intellij.psi.impl.ElementBase;
50 import com.intellij.psi.search.PsiElementProcessor;
51 import com.intellij.ui.*;
52 import com.intellij.ui.popup.list.ListPopupImpl;
53 import com.intellij.ui.popup.list.PopupListElementRenderer;
54 import com.intellij.util.Processor;
55 import com.intellij.util.containers.ContainerUtil;
56 import com.intellij.util.ui.UIUtil;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
59
60 import javax.swing.*;
61 import java.awt.*;
62 import java.awt.event.ActionEvent;
63 import java.util.*;
64 import java.util.List;
65
66 /**
67  * @author ven
68  */
69 public final class NavigationUtil {
70
71   private NavigationUtil() {
72   }
73
74   @NotNull
75   public static JBPopup getPsiElementPopup(@NotNull PsiElement[] elements, String title) {
76     return getPsiElementPopup(elements, new DefaultPsiElementCellRenderer(), title);
77   }
78
79   @NotNull
80   public static JBPopup getPsiElementPopup(@NotNull PsiElement[] elements,
81                                            @NotNull final PsiElementListCellRenderer<PsiElement> renderer,
82                                            final String title) {
83     return getPsiElementPopup(elements, renderer, title, new PsiElementProcessor<PsiElement>() {
84       @Override
85       public boolean execute(@NotNull final PsiElement element) {
86         Navigatable descriptor = EditSourceUtil.getDescriptor(element);
87         if (descriptor != null && descriptor.canNavigate()) {
88           descriptor.navigate(true);
89         }
90         return true;
91       }
92     });
93   }
94
95   @NotNull
96   public static <T extends PsiElement> JBPopup getPsiElementPopup(@NotNull T[] elements,
97                                                                   @NotNull final PsiElementListCellRenderer<T> renderer,
98                                                                   final String title,
99                                                                   @NotNull final PsiElementProcessor<T> processor) {
100     return getPsiElementPopup(elements, renderer, title, processor, null);
101   }
102
103   @NotNull
104   public static <T extends PsiElement> JBPopup getPsiElementPopup(@NotNull T[] elements,
105                                                                   @NotNull final PsiElementListCellRenderer<T> renderer,
106                                                                   @Nullable final String title,
107                                                                   @NotNull final PsiElementProcessor<T> processor,
108                                                                   @Nullable final T selection) {
109     final JList list = new JBListWithHintProvider(elements) {
110       @Nullable
111       @Override
112       protected PsiElement getPsiElementForHint(Object selectedValue) {
113         return (PsiElement)selectedValue;
114       }
115     };
116     list.setCellRenderer(renderer);
117     if (selection != null) {
118       list.setSelectedValue(selection, true);
119     }
120
121     final Runnable runnable = new Runnable() {
122       @Override
123       public void run() {
124         int[] ids = list.getSelectedIndices();
125         if (ids == null || ids.length == 0) return;
126         for (Object element : list.getSelectedValues()) {
127           if (element != null) {
128             processor.execute((T)element);
129           }
130         }
131       }
132     };
133
134     PopupChooserBuilder builder = new PopupChooserBuilder(list);
135     if (title != null) {
136       builder.setTitle(title);
137     }
138     renderer.installSpeedSearch(builder, true);
139
140     return builder.setItemChoosenCallback(runnable).createPopup();
141   }
142
143   public static boolean activateFileWithPsiElement(@NotNull PsiElement elt) {
144     return activateFileWithPsiElement(elt, true);
145   }
146
147   public static boolean activateFileWithPsiElement(@NotNull PsiElement elt, boolean searchForOpen) {
148     return openFileWithPsiElement(elt, searchForOpen, true);
149   }
150
151   public static boolean openFileWithPsiElement(PsiElement element, boolean searchForOpen, boolean requestFocus) {
152     boolean openAsNative = false;
153     if (element instanceof PsiFile) {
154       VirtualFile virtualFile = ((PsiFile)element).getVirtualFile();
155       if (virtualFile != null) {
156         openAsNative = ElementBase.isNativeFileType(virtualFile.getFileType());
157       }
158     }
159
160     if (searchForOpen) {
161       element.putUserData(FileEditorManager.USE_CURRENT_WINDOW, null);
162     }
163     else {
164       element.putUserData(FileEditorManager.USE_CURRENT_WINDOW, true);
165     }
166
167     if (openAsNative || !activatePsiElementIfOpen(element, searchForOpen, requestFocus)) {
168       final NavigationItem navigationItem = (NavigationItem)element;
169       if (!navigationItem.canNavigate()) return false;
170       navigationItem.navigate(requestFocus);
171       return true;
172     }
173
174     element.putUserData(FileEditorManager.USE_CURRENT_WINDOW, null);
175     return false;
176   }
177
178   private static boolean activatePsiElementIfOpen(@NotNull PsiElement elt, boolean searchForOpen, boolean requestFocus) {
179     if (!elt.isValid()) return false;
180     elt = elt.getNavigationElement();
181     final PsiFile file = elt.getContainingFile();
182     if (file == null || !file.isValid()) return false;
183
184     VirtualFile vFile = file.getVirtualFile();
185     if (vFile == null) return false;
186
187     if (!EditorHistoryManager.getInstance(elt.getProject()).hasBeenOpen(vFile)) return false;
188
189     final FileEditorManager fem = FileEditorManager.getInstance(elt.getProject());
190     if (!fem.isFileOpen(vFile)) {
191       fem.openFile(vFile, requestFocus, searchForOpen);
192     }
193
194     final TextRange range = elt.getTextRange();
195     if (range == null) return false;
196
197     final FileEditor[] editors = fem.getEditors(vFile);
198     for (FileEditor editor : editors) {
199       if (editor instanceof TextEditor) {
200         final Editor text = ((TextEditor)editor).getEditor();
201         final int offset = text.getCaretModel().getOffset();
202
203         if (range.containsOffset(offset)) {
204           // select the file
205           fem.openFile(vFile, requestFocus, searchForOpen);
206           return true;
207         }
208       }
209     }
210
211     return false;
212   }
213
214   /**
215    * Patches attributes to be visible under debugger active line
216    */
217   @SuppressWarnings("UseJBColor")
218   public static TextAttributes patchAttributesColor(TextAttributes attributes, @NotNull TextRange range, @NotNull Editor editor) {
219     MarkupModel model = DocumentMarkupModel.forDocument(editor.getDocument(), editor.getProject(), false);
220     if (model != null) {
221       if (!((MarkupModelEx)model).processRangeHighlightersOverlappingWith(range.getStartOffset(), range.getEndOffset(),
222            new Processor<RangeHighlighterEx>() {
223              @Override
224              public boolean process(RangeHighlighterEx highlighter) {
225                if (highlighter.isValid() && highlighter.getTargetArea() == HighlighterTargetArea.LINES_IN_RANGE) {
226                  TextAttributes textAttributes = highlighter.getTextAttributes();
227                  if (textAttributes != null) {
228                    Color color = textAttributes.getBackgroundColor();
229                    return !(color != null && color.getBlue() > 128 && color.getRed() < 128 && color.getGreen() < 128);
230                  }
231                }
232                return true;
233              }
234            })) {
235         TextAttributes clone = attributes.clone();
236         clone.setForegroundColor(Color.orange);
237         clone.setEffectColor(Color.orange);
238         return clone;
239       }
240     }
241     return attributes;
242   }
243
244   @NotNull
245   public static JBPopup getRelatedItemsPopup(final List<? extends GotoRelatedItem> items, String title) {
246     Object[] elements = new Object[items.size()];
247     //todo[nik] move presentation logic to GotoRelatedItem class
248     final Map<PsiElement, GotoRelatedItem> itemsMap = new HashMap<PsiElement, GotoRelatedItem>();
249     for (int i = 0; i < items.size(); i++) {
250       GotoRelatedItem item = items.get(i);
251       elements[i] = item.getElement() != null ? item.getElement() : item;
252       itemsMap.put(item.getElement(), item);
253     }
254
255     return getPsiElementPopup(elements, itemsMap, title, new Processor<Object>() {
256       @Override
257       public boolean process(Object element) {
258         if (element instanceof PsiElement) {
259           //noinspection SuspiciousMethodCalls
260           itemsMap.get(element).navigate();
261         }
262         else {
263           ((GotoRelatedItem)element).navigate();
264         }
265         return true;
266       }
267     }
268     );
269   }
270
271   private static JBPopup getPsiElementPopup(final Object[] elements, final Map<PsiElement, GotoRelatedItem> itemsMap,
272                                            final String title, final Processor<Object> processor) {
273
274     final Ref<Boolean> hasMnemonic = Ref.create(false);
275     final DefaultPsiElementCellRenderer renderer = new DefaultPsiElementCellRenderer() {
276       {
277         setFocusBorderEnabled(false);
278       }
279
280       @Override
281       public String getElementText(PsiElement element) {
282         String customName = itemsMap.get(element).getCustomName();
283         return (customName != null ? customName : super.getElementText(element));
284       }
285
286       @Override
287       protected Icon getIcon(PsiElement element) {
288         Icon customIcon = itemsMap.get(element).getCustomIcon();
289         return customIcon != null ? customIcon : super.getIcon(element);
290       }
291
292       @Override
293       public String getContainerText(PsiElement element, String name) {
294         String customContainerName = itemsMap.get(element).getCustomContainerName();
295
296         if (customContainerName != null) {
297           return customContainerName;
298         }
299         PsiFile file = element.getContainingFile();
300         return file != null && !getElementText(element).equals(file.getName())
301                ? "(" + file.getName() + ")"
302                : null;
303       }
304
305       @Override
306       protected DefaultListCellRenderer getRightCellRenderer(Object value) {
307         return null;
308       }
309
310       @Override
311       protected boolean customizeNonPsiElementLeftRenderer(ColoredListCellRenderer renderer,
312                                                            JList list,
313                                                            Object value,
314                                                            int index,
315                                                            boolean selected,
316                                                            boolean hasFocus) {
317         final GotoRelatedItem item = (GotoRelatedItem)value;
318         Color color = list.getForeground();
319         final SimpleTextAttributes nameAttributes = new SimpleTextAttributes(Font.PLAIN, color);
320         final String name = item.getCustomName();
321         if (name == null) return false;
322         renderer.append(name, nameAttributes);
323         renderer.setIcon(item.getCustomIcon());
324         return true;
325       }
326
327       @Override
328       public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
329         final JPanel component = (JPanel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
330         if (!hasMnemonic.get()) return component;
331
332         final JPanel panelWithMnemonic = new JPanel(new BorderLayout());
333         final int mnemonic = getMnemonic(value, itemsMap);
334         final JLabel label = new JLabel("");
335         if (mnemonic != -1) {
336           label.setText(mnemonic + ".");
337           label.setDisplayedMnemonicIndex(0);
338         }
339         label.setPreferredSize(new JLabel("8.").getPreferredSize());
340
341         final JComponent leftRenderer = (JComponent)component.getComponents()[0];
342         component.remove(leftRenderer);
343         panelWithMnemonic.setBorder(BorderFactory.createEmptyBorder(0, 7, 0, 0));
344         panelWithMnemonic.setBackground(leftRenderer.getBackground());
345         label.setBackground(leftRenderer.getBackground());
346         panelWithMnemonic.add(label, BorderLayout.WEST);
347         panelWithMnemonic.add(leftRenderer, BorderLayout.CENTER);
348         component.add(panelWithMnemonic);
349         return component;
350       }
351     };
352     final ListPopupImpl popup = new ListPopupImpl(new BaseListPopupStep<Object>(title, Arrays.asList(elements)) {
353       @Override
354       public boolean isSpeedSearchEnabled() {
355         return true;
356       }
357
358       @Override
359       public String getIndexedString(Object value) {
360         if (value instanceof GotoRelatedItem) {
361           //noinspection ConstantConditions
362           return ((GotoRelatedItem)value).getCustomName();
363         }
364         final PsiElement element = (PsiElement)value;
365         return renderer.getElementText(element) + " " + renderer.getContainerText(element, null);
366       }
367
368       @Override
369       public PopupStep onChosen(Object selectedValue, boolean finalChoice) {
370         processor.process(selectedValue);
371         return super.onChosen(selectedValue, finalChoice);
372       }
373     }) {
374     };
375     popup.getList().setCellRenderer(new PopupListElementRenderer(popup) {
376       Map<Object, String> separators = new HashMap<Object, String>();
377       {
378         final ListModel model = popup.getList().getModel();
379         String current = null;
380         boolean hasTitle = false;
381         for (int i = 0; i < model.getSize(); i++) {
382           final Object element = model.getElementAt(i);
383           final GotoRelatedItem item = itemsMap.get(element);
384           if (item != null && !StringUtil.equals(current, item.getGroup())) {
385             current = item.getGroup();
386             separators.put(element, current);
387             if (!hasTitle && !StringUtil.isEmpty(current)) {
388               hasTitle = true;
389             }
390           }
391         }
392
393         if (!hasTitle) {
394           separators.remove(model.getElementAt(0));
395         }
396       }
397       @Override
398       public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
399         final Component component = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
400         final String separator = separators.get(value);
401
402         if (separator != null) {
403           JPanel panel = new JPanel(new BorderLayout());
404           panel.add(component, BorderLayout.CENTER);
405           final SeparatorWithText sep = new SeparatorWithText() {
406             @Override
407             protected void paintComponent(Graphics g) {
408               g.setColor(new JBColor(Color.WHITE, UIUtil.getSeparatorColor()));
409               g.fillRect(0,0,getWidth(), getHeight());
410               super.paintComponent(g);
411             }
412           };
413           sep.setCaption(separator);
414           panel.add(sep, BorderLayout.NORTH);
415           return panel;
416         }
417         return component;
418       }
419     });
420
421     popup.setMinimumSize(new Dimension(200, -1));
422
423     for (Object item : elements) {
424       final int mnemonic = getMnemonic(item, itemsMap);
425       if (mnemonic != -1) {
426         final Action action = createNumberAction(mnemonic, popup, itemsMap, processor);
427         popup.registerAction(mnemonic + "Action", KeyStroke.getKeyStroke(String.valueOf(mnemonic)), action);
428         popup.registerAction(mnemonic + "Action", KeyStroke.getKeyStroke("NUMPAD" + String.valueOf(mnemonic)), action);
429         hasMnemonic.set(true);
430       }
431     }
432     return popup;
433   }
434
435   private static Action createNumberAction(final int mnemonic,
436                                            final ListPopupImpl listPopup,
437                                            final Map<PsiElement, GotoRelatedItem> itemsMap,
438                                            final Processor<Object> processor) {
439       return new AbstractAction() {
440         @Override
441         public void actionPerformed(ActionEvent e) {
442           for (final Object item : listPopup.getListStep().getValues()) {
443             if (getMnemonic(item, itemsMap) == mnemonic) {
444               listPopup.setFinalRunnable(new Runnable() {
445                 @Override
446                 public void run() {
447                   processor.process(item);
448                 }
449               });
450               listPopup.closeOk(null);
451             }
452           }
453         }
454       };
455     }
456
457   private static int getMnemonic(Object item, Map<PsiElement, GotoRelatedItem> itemsMap) {
458     return (item instanceof GotoRelatedItem ? (GotoRelatedItem)item : itemsMap.get((PsiElement)item)).getMnemonic();
459   }
460
461   @NotNull
462   public static List<GotoRelatedItem> collectRelatedItems(@NotNull PsiElement contextElement, @Nullable DataContext dataContext) {
463     Set<GotoRelatedItem> items = ContainerUtil.newLinkedHashSet();
464     for (GotoRelatedProvider provider : Extensions.getExtensions(GotoRelatedProvider.EP_NAME)) {
465       items.addAll(provider.getItems(contextElement));
466       if (dataContext != null) {
467         items.addAll(provider.getItems(dataContext));
468       }
469     }
470     GotoRelatedItem[] result = items.toArray(new GotoRelatedItem[items.size()]);
471     Arrays.sort(result, new Comparator<GotoRelatedItem>() {
472       @Override
473       public int compare(GotoRelatedItem i1, GotoRelatedItem i2) {
474         String o1 = i1.getGroup();
475         String o2 = i2.getGroup();
476         return StringUtil.isEmpty(o1) ? 1 : StringUtil.isEmpty(o2) ? -1 : o1.compareTo(o2);
477       }
478     });
479     return Arrays.asList(result);
480   }
481 }