blob: 3617c944e8889dfb908a6f66fcb03f0dcc39cbee [file] [log] [blame]
/*
* Copyright 2000-2013 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.codeInsight.navigation;
import com.intellij.ide.util.DefaultPsiElementCellRenderer;
import com.intellij.ide.util.EditSourceUtil;
import com.intellij.ide.util.PsiElementListCellRenderer;
import com.intellij.navigation.GotoRelatedItem;
import com.intellij.navigation.GotoRelatedProvider;
import com.intellij.navigation.NavigationItem;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.fileEditor.TextEditor;
import com.intellij.openapi.fileEditor.impl.EditorHistoryManager;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
import com.intellij.openapi.ui.popup.PopupStep;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.ElementBase;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.ui.*;
import com.intellij.ui.popup.list.ListPopupImpl;
import com.intellij.ui.popup.list.PopupListElementRenderer;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.*;
import java.util.List;
/**
* @author ven
*/
public final class NavigationUtil {
private NavigationUtil() {
}
@NotNull
public static JBPopup getPsiElementPopup(@NotNull PsiElement[] elements, String title) {
return getPsiElementPopup(elements, new DefaultPsiElementCellRenderer(), title);
}
@NotNull
public static JBPopup getPsiElementPopup(@NotNull PsiElement[] elements,
@NotNull final PsiElementListCellRenderer<PsiElement> renderer,
final String title) {
return getPsiElementPopup(elements, renderer, title, new PsiElementProcessor<PsiElement>() {
@Override
public boolean execute(@NotNull final PsiElement element) {
Navigatable descriptor = EditSourceUtil.getDescriptor(element);
if (descriptor != null && descriptor.canNavigate()) {
descriptor.navigate(true);
}
return true;
}
});
}
@NotNull
public static <T extends PsiElement> JBPopup getPsiElementPopup(@NotNull T[] elements,
@NotNull final PsiElementListCellRenderer<T> renderer,
final String title,
@NotNull final PsiElementProcessor<T> processor) {
return getPsiElementPopup(elements, renderer, title, processor, null);
}
@NotNull
public static <T extends PsiElement> JBPopup getPsiElementPopup(@NotNull T[] elements,
@NotNull final PsiElementListCellRenderer<T> renderer,
@Nullable final String title,
@NotNull final PsiElementProcessor<T> processor,
@Nullable final T selection) {
final JList list = new JBListWithHintProvider(elements) {
@Nullable
@Override
protected PsiElement getPsiElementForHint(Object selectedValue) {
return (PsiElement)selectedValue;
}
};
list.setCellRenderer(renderer);
if (selection != null) {
list.setSelectedValue(selection, true);
}
final Runnable runnable = new Runnable() {
@Override
public void run() {
int[] ids = list.getSelectedIndices();
if (ids == null || ids.length == 0) return;
for (Object element : list.getSelectedValues()) {
if (element != null) {
processor.execute((T)element);
}
}
}
};
PopupChooserBuilder builder = new PopupChooserBuilder(list);
if (title != null) {
builder.setTitle(title);
}
renderer.installSpeedSearch(builder, true);
return builder.setItemChoosenCallback(runnable).createPopup();
}
public static boolean activateFileWithPsiElement(@NotNull PsiElement elt) {
return activateFileWithPsiElement(elt, true);
}
public static boolean activateFileWithPsiElement(@NotNull PsiElement elt, boolean searchForOpen) {
return openFileWithPsiElement(elt, searchForOpen, true);
}
public static boolean openFileWithPsiElement(PsiElement element, boolean searchForOpen, boolean requestFocus) {
boolean openAsNative = false;
if (element instanceof PsiFile) {
VirtualFile virtualFile = ((PsiFile)element).getVirtualFile();
if (virtualFile != null) {
openAsNative = ElementBase.isNativeFileType(virtualFile.getFileType());
}
}
if (searchForOpen) {
element.putUserData(FileEditorManager.USE_CURRENT_WINDOW, null);
}
else {
element.putUserData(FileEditorManager.USE_CURRENT_WINDOW, true);
}
if (openAsNative || !activatePsiElementIfOpen(element, searchForOpen, requestFocus)) {
final NavigationItem navigationItem = (NavigationItem)element;
if (!navigationItem.canNavigate()) return false;
navigationItem.navigate(requestFocus);
return true;
}
element.putUserData(FileEditorManager.USE_CURRENT_WINDOW, null);
return false;
}
private static boolean activatePsiElementIfOpen(@NotNull PsiElement elt, boolean searchForOpen, boolean requestFocus) {
if (!elt.isValid()) return false;
elt = elt.getNavigationElement();
final PsiFile file = elt.getContainingFile();
if (file == null || !file.isValid()) return false;
VirtualFile vFile = file.getVirtualFile();
if (vFile == null) return false;
if (!EditorHistoryManager.getInstance(elt.getProject()).hasBeenOpen(vFile)) return false;
final FileEditorManager fem = FileEditorManager.getInstance(elt.getProject());
if (!fem.isFileOpen(vFile)) {
fem.openFile(vFile, requestFocus, searchForOpen);
}
final TextRange range = elt.getTextRange();
if (range == null) return false;
final FileEditor[] editors = fem.getEditors(vFile);
for (FileEditor editor : editors) {
if (editor instanceof TextEditor) {
final Editor text = ((TextEditor)editor).getEditor();
final int offset = text.getCaretModel().getOffset();
if (range.containsOffset(offset)) {
// select the file
fem.openFile(vFile, requestFocus, searchForOpen);
return true;
}
}
}
return false;
}
/**
* Patches attributes to be visible under debugger active line
*/
@SuppressWarnings("UseJBColor")
public static TextAttributes patchAttributesColor(TextAttributes attributes, @NotNull TextRange range, @NotNull Editor editor) {
int lineStart = editor.offsetToLogicalPosition(range.getStartOffset()).line;
int lineEnd = editor.offsetToLogicalPosition(range.getEndOffset()).line;
for (RangeHighlighter highlighter : editor.getMarkupModel().getAllHighlighters()) {
if (!highlighter.isValid()) continue;
if (highlighter.getTargetArea() == HighlighterTargetArea.LINES_IN_RANGE) {
int line = editor.offsetToLogicalPosition(highlighter.getStartOffset()).line;
if (line >= lineStart && line <= lineEnd) {
TextAttributes textAttributes = highlighter.getTextAttributes();
if (textAttributes != null) {
Color color = textAttributes.getBackgroundColor();
if (color != null && color.getBlue() > 128 && color.getRed() < 128 && color.getGreen() < 128) {
TextAttributes clone = attributes.clone();
clone.setForegroundColor(Color.orange);
clone.setEffectColor(Color.orange);
return clone;
}
}
}
}
}
return attributes;
}
@NotNull
public static JBPopup getRelatedItemsPopup(final List<? extends GotoRelatedItem> items, String title) {
Object[] elements = new Object[items.size()];
//todo[nik] move presentation logic to GotoRelatedItem class
final Map<PsiElement, GotoRelatedItem> itemsMap = new HashMap<PsiElement, GotoRelatedItem>();
for (int i = 0; i < items.size(); i++) {
GotoRelatedItem item = items.get(i);
elements[i] = item.getElement() != null ? item.getElement() : item;
itemsMap.put(item.getElement(), item);
}
return getPsiElementPopup(elements, itemsMap, title, new Processor<Object>() {
@Override
public boolean process(Object element) {
if (element instanceof PsiElement) {
//noinspection SuspiciousMethodCalls
itemsMap.get(element).navigate();
}
else {
((GotoRelatedItem)element).navigate();
}
return true;
}
}
);
}
private static JBPopup getPsiElementPopup(final Object[] elements, final Map<PsiElement, GotoRelatedItem> itemsMap,
final String title, final Processor<Object> processor) {
final Ref<Boolean> hasMnemonic = Ref.create(false);
final DefaultPsiElementCellRenderer renderer = new DefaultPsiElementCellRenderer() {
{
setFocusBorderEnabled(false);
}
@Override
public String getElementText(PsiElement element) {
String customName = itemsMap.get(element).getCustomName();
return (customName != null ? customName : super.getElementText(element));
}
@Override
protected Icon getIcon(PsiElement element) {
Icon customIcon = itemsMap.get(element).getCustomIcon();
return customIcon != null ? customIcon : super.getIcon(element);
}
@Override
public String getContainerText(PsiElement element, String name) {
String customContainerName = itemsMap.get(element).getCustomContainerName();
if (customContainerName != null) {
return customContainerName;
}
PsiFile file = element.getContainingFile();
return file != null && !getElementText(element).equals(file.getName())
? "(" + file.getName() + ")"
: null;
}
@Override
protected DefaultListCellRenderer getRightCellRenderer(Object value) {
return null;
}
@Override
protected boolean customizeNonPsiElementLeftRenderer(ColoredListCellRenderer renderer,
JList list,
Object value,
int index,
boolean selected,
boolean hasFocus) {
final GotoRelatedItem item = (GotoRelatedItem)value;
Color color = list.getForeground();
final SimpleTextAttributes nameAttributes = new SimpleTextAttributes(Font.PLAIN, color);
final String name = item.getCustomName();
if (name == null) return false;
renderer.append(name, nameAttributes);
renderer.setIcon(item.getCustomIcon());
return true;
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
final JPanel component = (JPanel)super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (!hasMnemonic.get()) return component;
final JPanel panelWithMnemonic = new JPanel(new BorderLayout());
final int mnemonic = getMnemonic(value, itemsMap);
final JLabel label = new JLabel("");
if (mnemonic != -1) {
label.setText(mnemonic + ".");
label.setDisplayedMnemonicIndex(0);
}
label.setPreferredSize(new JLabel("8.").getPreferredSize());
final JComponent leftRenderer = (JComponent)component.getComponents()[0];
component.remove(leftRenderer);
panelWithMnemonic.setBorder(BorderFactory.createEmptyBorder(0, 7, 0, 0));
panelWithMnemonic.setBackground(leftRenderer.getBackground());
label.setBackground(leftRenderer.getBackground());
panelWithMnemonic.add(label, BorderLayout.WEST);
panelWithMnemonic.add(leftRenderer, BorderLayout.CENTER);
component.add(panelWithMnemonic);
return component;
}
};
final ListPopupImpl popup = new ListPopupImpl(new BaseListPopupStep<Object>(title, Arrays.asList(elements)) {
@Override
public boolean isSpeedSearchEnabled() {
return true;
}
@Override
public String getIndexedString(Object value) {
if (value instanceof GotoRelatedItem) {
//noinspection ConstantConditions
return ((GotoRelatedItem)value).getCustomName();
}
final PsiElement element = (PsiElement)value;
return renderer.getElementText(element) + " " + renderer.getContainerText(element, null);
}
@Override
public PopupStep onChosen(Object selectedValue, boolean finalChoice) {
processor.process(selectedValue);
return super.onChosen(selectedValue, finalChoice);
}
}) {
};
popup.getList().setCellRenderer(new PopupListElementRenderer(popup) {
Map<Object, String> separators = new HashMap<Object, String>();
{
final ListModel model = popup.getList().getModel();
String current = null;
boolean hasTitle = false;
for (int i = 0; i < model.getSize(); i++) {
final Object element = model.getElementAt(i);
final GotoRelatedItem item = itemsMap.get(element);
if (item != null && !StringUtil.equals(current, item.getGroup())) {
current = item.getGroup();
separators.put(element, current);
if (!hasTitle && !StringUtil.isEmpty(current)) {
hasTitle = true;
}
}
}
if (!hasTitle) {
separators.remove(model.getElementAt(0));
}
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
final Component component = renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
final String separator = separators.get(value);
if (separator != null) {
JPanel panel = new JPanel(new BorderLayout());
panel.add(component, BorderLayout.CENTER);
final SeparatorWithText sep = new SeparatorWithText() {
@Override
protected void paintComponent(Graphics g) {
g.setColor(new JBColor(Color.WHITE, UIUtil.getSeparatorColor()));
g.fillRect(0,0,getWidth(), getHeight());
super.paintComponent(g);
}
};
sep.setCaption(separator);
panel.add(sep, BorderLayout.NORTH);
return panel;
}
return component;
}
});
popup.setMinimumSize(new Dimension(200, -1));
for (Object item : elements) {
final int mnemonic = getMnemonic(item, itemsMap);
if (mnemonic != -1) {
final Action action = createNumberAction(mnemonic, popup, itemsMap, processor);
popup.registerAction(mnemonic + "Action", KeyStroke.getKeyStroke(String.valueOf(mnemonic)), action);
popup.registerAction(mnemonic + "Action", KeyStroke.getKeyStroke("NUMPAD" + String.valueOf(mnemonic)), action);
hasMnemonic.set(true);
}
}
return popup;
}
private static Action createNumberAction(final int mnemonic,
final ListPopupImpl listPopup,
final Map<PsiElement, GotoRelatedItem> itemsMap,
final Processor<Object> processor) {
return new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
for (final Object item : listPopup.getListStep().getValues()) {
if (getMnemonic(item, itemsMap) == mnemonic) {
listPopup.setFinalRunnable(new Runnable() {
@Override
public void run() {
processor.process(item);
}
});
listPopup.closeOk(null);
}
}
}
};
}
private static int getMnemonic(Object item, Map<PsiElement, GotoRelatedItem> itemsMap) {
return (item instanceof GotoRelatedItem ? (GotoRelatedItem)item : itemsMap.get((PsiElement)item)).getMnemonic();
}
@NotNull
public static List<GotoRelatedItem> collectRelatedItems(@NotNull PsiElement contextElement, @Nullable DataContext dataContext) {
Set<GotoRelatedItem> items = ContainerUtil.newLinkedHashSet();
for (GotoRelatedProvider provider : Extensions.getExtensions(GotoRelatedProvider.EP_NAME)) {
items.addAll(provider.getItems(contextElement));
if (dataContext != null) {
items.addAll(provider.getItems(dataContext));
}
}
GotoRelatedItem[] result = items.toArray(new GotoRelatedItem[items.size()]);
Arrays.sort(result, new Comparator<GotoRelatedItem>() {
@Override
public int compare(GotoRelatedItem i1, GotoRelatedItem i2) {
String o1 = i1.getGroup();
String o2 = i2.getGroup();
return StringUtil.isEmpty(o1) ? 1 : StringUtil.isEmpty(o2) ? -1 : o1.compareTo(o2);
}
});
return Arrays.asList(result);
}
}