cleanup
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / navigation / CtrlMouseHandler.java
blobdd31f780313c6e787ae7029d2b7a2486f3addbf4
1 package com.intellij.codeInsight.navigation;
3 import com.intellij.codeInsight.CodeInsightBundle;
4 import com.intellij.codeInsight.TargetElementUtilBase;
5 import com.intellij.codeInsight.documentation.DocumentationManager;
6 import com.intellij.codeInsight.hint.HintManager;
7 import com.intellij.codeInsight.hint.HintManagerImpl;
8 import com.intellij.codeInsight.hint.HintUtil;
9 import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
10 import com.intellij.codeInsight.navigation.actions.GotoTypeDeclarationAction;
11 import com.intellij.ide.util.EditSourceUtil;
12 import com.intellij.lang.documentation.DocumentationProvider;
13 import com.intellij.navigation.ItemPresentation;
14 import com.intellij.navigation.NavigationItem;
15 import com.intellij.openapi.actionSystem.IdeActions;
16 import com.intellij.openapi.actionSystem.MouseShortcut;
17 import com.intellij.openapi.actionSystem.Shortcut;
18 import com.intellij.openapi.components.AbstractProjectComponent;
19 import com.intellij.openapi.editor.Document;
20 import com.intellij.openapi.editor.Editor;
21 import com.intellij.openapi.editor.EditorFactory;
22 import com.intellij.openapi.editor.LogicalPosition;
23 import com.intellij.openapi.editor.colors.EditorColorsManager;
24 import com.intellij.openapi.editor.colors.TextAttributesKey;
25 import com.intellij.openapi.editor.event.*;
26 import com.intellij.openapi.editor.markup.*;
27 import com.intellij.openapi.fileEditor.FileEditorManager;
28 import com.intellij.openapi.fileEditor.FileEditorManagerAdapter;
29 import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
30 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
31 import com.intellij.openapi.keymap.Keymap;
32 import com.intellij.openapi.keymap.KeymapManager;
33 import com.intellij.openapi.project.DumbAwareRunnable;
34 import com.intellij.openapi.project.DumbService;
35 import com.intellij.openapi.project.IndexNotReadyException;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.startup.StartupManager;
38 import com.intellij.openapi.ui.MultiLineLabelUI;
39 import com.intellij.openapi.util.Comparing;
40 import com.intellij.openapi.util.TextRange;
41 import com.intellij.openapi.vfs.VirtualFile;
42 import com.intellij.pom.Navigatable;
43 import com.intellij.psi.*;
44 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
45 import com.intellij.psi.search.searches.DefinitionsSearch;
46 import com.intellij.ui.LightweightHint;
47 import com.intellij.util.Processor;
48 import com.intellij.util.ui.UIUtil;
49 import org.jetbrains.annotations.NotNull;
50 import org.jetbrains.annotations.Nullable;
52 import javax.swing.*;
53 import java.awt.*;
54 import java.awt.event.*;
55 import java.util.ArrayList;
56 import java.util.List;
58 public class CtrlMouseHandler extends AbstractProjectComponent {
59 private final TextAttributes ourReferenceAttributes;
60 private RangeHighlighter myHighlighter;
61 private Editor myHighlighterView;
62 private Cursor myStoredCursor;
63 private Info myStoredInfo;
64 private int myStoredModifiers = 0;
65 private TooltipProvider myTooltipProvider = null;
66 private final FileEditorManager myFileEditorManager;
68 private enum BrowseMode { None, Declaration, TypeDeclaration, Implementation }
70 private final KeyListener myEditorKeyListener = new KeyAdapter() {
71 public void keyPressed(final KeyEvent e) {
72 handleKey(e);
75 public void keyReleased(final KeyEvent e) {
76 handleKey(e);
79 private void handleKey(final KeyEvent e) {
80 int modifiers = e.getModifiers();
81 if ( modifiers == myStoredModifiers) {
82 return;
85 BrowseMode browseMode = getBrowseMode(modifiers);
87 if (browseMode != BrowseMode.None) {
88 if (myTooltipProvider != null) {
89 if (browseMode != myTooltipProvider.getBrowseMode()) {
90 disposeHighlighter();
92 myStoredModifiers = modifiers;
93 myTooltipProvider.execute(browseMode);
95 } else {
96 disposeHighlighter();
97 myTooltipProvider = null;
102 private final FileEditorManagerListener myFileEditorManagerListener = new FileEditorManagerAdapter() {
103 public void selectionChanged(FileEditorManagerEvent e) {
104 disposeHighlighter();
105 myTooltipProvider = null;
109 private final VisibleAreaListener myVisibleAreaListener = new VisibleAreaListener() {
110 public void visibleAreaChanged(VisibleAreaEvent e) {
111 disposeHighlighter();
112 myTooltipProvider = null;
116 private final EditorMouseAdapter myEditorMouseAdapter = new EditorMouseAdapter() {
117 public void mouseReleased(EditorMouseEvent e) {
118 disposeHighlighter();
119 myTooltipProvider = null;
123 private final EditorMouseMotionListener myEditorMouseMotionListener = new EditorMouseMotionAdapter() {
124 public void mouseMoved(final EditorMouseEvent e) {
125 if (e.isConsumed() || myProject.isDisposed()) {
126 return;
128 MouseEvent mouseEvent = e.getMouseEvent();
130 Editor editor = e.getEditor();
131 if (editor.getProject() != null && editor.getProject() != myProject) return;
132 PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
133 Point point = new Point(mouseEvent.getPoint());
134 if (!PsiDocumentManager.getInstance(myProject).isUncommited(editor.getDocument())) {
135 // when document is committed, try to check injected stuff - it's fast
136 editor = InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(editor, psiFile, editor.logicalPositionToOffset(editor.xyToLogicalPosition(point)));
139 LogicalPosition pos = editor.xyToLogicalPosition(point);
140 int offset = editor.logicalPositionToOffset(pos);
141 int selStart = editor.getSelectionModel().getSelectionStart();
142 int selEnd = editor.getSelectionModel().getSelectionEnd();
144 myStoredModifiers = mouseEvent.getModifiers();
145 BrowseMode browseMode = getBrowseMode(myStoredModifiers);
147 if (browseMode == BrowseMode.None || offset >= selStart && offset < selEnd) {
148 disposeHighlighter();
149 myTooltipProvider = null;
150 return;
153 myTooltipProvider = new TooltipProvider(editor, pos);
154 myTooltipProvider.execute(browseMode);
158 private static final TextAttributesKey CTRL_CLICKABLE_ATTRIBUTES_KEY =
159 TextAttributesKey.createTextAttributesKey("CTRL_CLICKABLE", new TextAttributes(Color.blue, null, Color.blue, EffectType.LINE_UNDERSCORE, 0));
161 public CtrlMouseHandler(final Project project, StartupManager startupManager, EditorColorsManager colorsManager,
162 FileEditorManager fileEditorManager) {
163 super(project);
164 startupManager.registerPostStartupActivity(new DumbAwareRunnable(){
165 public void run() {
166 EditorEventMulticaster eventMulticaster = EditorFactory.getInstance().getEventMulticaster();
167 eventMulticaster.addEditorMouseListener(myEditorMouseAdapter, project);
168 eventMulticaster.addEditorMouseMotionListener(myEditorMouseMotionListener, project);
171 ourReferenceAttributes = colorsManager.getGlobalScheme().getAttributes(CTRL_CLICKABLE_ATTRIBUTES_KEY);
172 myFileEditorManager = fileEditorManager;
175 @NotNull
176 public String getComponentName() {
177 return "CtrlMouseHandler";
180 private static BrowseMode getBrowseMode(final int modifiers) {
181 if ( modifiers != 0 ) {
182 final Keymap activeKeymap = KeymapManager.getInstance().getActiveKeymap();
183 if (matchMouseShourtcut(activeKeymap, modifiers, IdeActions.ACTION_GOTO_DECLARATION)) return BrowseMode.Declaration;
184 if (matchMouseShourtcut(activeKeymap, modifiers, IdeActions.ACTION_GOTO_TYPE_DECLARATION)) return BrowseMode.TypeDeclaration;
185 if (matchMouseShourtcut(activeKeymap, modifiers, IdeActions.ACTION_GOTO_IMPLEMENTATION)) return BrowseMode.Implementation;
187 return BrowseMode.None;
190 private static boolean matchMouseShourtcut(final Keymap activeKeymap, final int modifiers, final String actionId) {
191 final MouseShortcut syntheticShortcat = new MouseShortcut(MouseEvent.BUTTON1, modifiers, 1);
192 for ( Shortcut shortcut : activeKeymap.getShortcuts(actionId)) {
193 if ( shortcut instanceof MouseShortcut) {
194 final MouseShortcut mouseShortcut = (MouseShortcut)shortcut;
195 if ( mouseShortcut.getModifiers() == syntheticShortcat.getModifiers() ) {
196 return true;
200 return false;
203 @Nullable
204 private static String generateInfo(PsiElement element) {
205 final DocumentationProvider documentationProvider = DocumentationManager.getProviderFromElement(element);
207 String info = documentationProvider.getQuickNavigateInfo(element);
208 if (info != null) {
209 return info;
212 if (element instanceof PsiFile) {
213 final VirtualFile virtualFile = ((PsiFile)element).getVirtualFile();
214 if (virtualFile != null) {
215 return virtualFile.getPresentableUrl();
219 if (element instanceof NavigationItem) {
220 final ItemPresentation presentation = ((NavigationItem)element).getPresentation();
221 if (presentation != null) {
222 return presentation.getPresentableText();
226 return null;
229 private abstract static class Info {
230 @NotNull protected final PsiElement myElementAtPointer;
231 public final int myStartOffset;
232 public final int myEndOffset;
234 public Info(@NotNull PsiElement elementAtPointer, int startOffset, int endOffset ) {
235 myElementAtPointer = elementAtPointer;
236 myStartOffset = startOffset;
237 myEndOffset = endOffset;
240 public Info(@NotNull PsiElement elementAtPointer ) {
241 this(elementAtPointer, elementAtPointer.getTextOffset(), elementAtPointer.getTextOffset() + elementAtPointer.getTextLength());
244 boolean isSimilarTo(final Info that) {
245 return Comparing.equal(myElementAtPointer, that.myElementAtPointer) &&
246 myStartOffset == that.myStartOffset &&
247 myEndOffset == that.myEndOffset;
250 @Nullable
251 public abstract String getInfo();
253 public abstract boolean isValid(Document document);
256 private static void showDumbModeNotification(final Project project) {
257 DumbService.getInstance(project).showDumbModeNotification("Element information is not available during index update");
260 private static class InfoSingle extends Info {
261 @NotNull private final PsiElement myTargetElement;
263 public InfoSingle(@NotNull PsiElement elementAtPointer, @NotNull PsiElement targetElement) {
264 super(elementAtPointer);
265 myTargetElement = targetElement;
268 public InfoSingle(final PsiReference ref, @NotNull final PsiElement targetElement) {
269 super(ref.getElement(), ref.getElement().getTextRange().getStartOffset() + ref.getRangeInElement().getStartOffset(),
270 ref.getElement().getTextRange().getStartOffset() + ref.getRangeInElement().getEndOffset());
271 myTargetElement = targetElement;
274 @Nullable
275 public String getInfo() {
276 try {
277 return generateInfo(myTargetElement);
279 catch (IndexNotReadyException e) {
280 showDumbModeNotification(myTargetElement.getProject());
281 return null;
285 public boolean isValid(Document document) {
286 return myTargetElement.isValid() &&
287 myTargetElement != myElementAtPointer &&
288 myTargetElement != myElementAtPointer.getParent() &&
289 new TextRange(0, document.getTextLength()).containsRange(myStartOffset, myEndOffset)
290 /* && targetNavigateable(myTargetElement)*/;
294 private static class InfoMultiple extends Info {
296 public InfoMultiple(@NotNull final PsiElement elementAtPointer) {
297 super(elementAtPointer);
300 public String getInfo() {
301 return CodeInsightBundle.message("multiple.implementations.tooltip");
304 public boolean isValid(Document document) {
305 return new TextRange(0, document.getTextLength()).containsRange(myStartOffset, myEndOffset);
309 @Nullable
310 private Info getInfoAt(final Editor editor, LogicalPosition pos, BrowseMode browseMode) {
311 Document document = editor.getDocument();
312 PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
313 if (file == null) return null;
314 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
316 if (TargetElementUtilBase.inVirtualSpace(editor, pos)) {
317 return null;
320 final int offset = editor.logicalPositionToOffset(pos);
322 int selStart = editor.getSelectionModel().getSelectionStart();
323 int selEnd = editor.getSelectionModel().getSelectionEnd();
325 if (offset >= selStart && offset < selEnd) return null;
327 PsiElement targetElement = null;
329 if (browseMode == BrowseMode.TypeDeclaration) {
330 try {
331 targetElement = GotoTypeDeclarationAction.findSymbolType(editor, offset);
333 catch (IndexNotReadyException e) {
334 showDumbModeNotification(myProject);
337 else if (browseMode == BrowseMode.Declaration) {
338 PsiReference ref = TargetElementUtilBase.findReference(editor, offset);
339 if (ref != null) {
340 PsiElement resolvedElement = resolve(ref);
341 if (resolvedElement != null) {
342 return new InfoSingle (ref, resolvedElement);
345 targetElement = GotoDeclarationAction.findTargetElement(myProject, editor, offset);
346 } else if ( browseMode == BrowseMode.Implementation ) {
347 final PsiElement element = TargetElementUtilBase.getInstance().findTargetElement(editor, ImplementationSearcher.getFlags(), offset);
348 PsiElement[] targetElements = new ImplementationSearcher() {
349 @NotNull
350 protected PsiElement[] searchDefinitions(final PsiElement element) {
351 final List<PsiElement> found = new ArrayList<PsiElement>(2);
352 DefinitionsSearch.search(element).forEach( new Processor<PsiElement>() {
353 public boolean process(final PsiElement psiElement) {
354 found.add ( psiElement );
355 return found.size() != 2;
358 return found.toArray(new PsiElement[found.size()]);
360 }.searchImplementations(editor, element, offset);
361 if ( targetElements.length > 1) {
362 PsiElement elementAtPointer = file.findElementAt(offset);
363 if (elementAtPointer != null) {
364 return new InfoMultiple(elementAtPointer);
366 return null;
368 if (targetElements.length == 1) {
369 Navigatable descriptor = EditSourceUtil.getDescriptor(targetElements[0]);
370 if (descriptor == null || !descriptor.canNavigate()) {
371 return null;
373 targetElement = targetElements [ 0 ];
377 if (targetElement != null && targetElement.isPhysical() ) {
378 PsiElement elementAtPointer = file.findElementAt(offset);
379 if ( elementAtPointer != null ) {
380 return new InfoSingle(elementAtPointer, targetElement);
384 return null;
387 @Nullable
388 private static PsiElement resolve(final PsiReference ref) {
389 PsiElement resolvedElement = null;
391 if (ref instanceof PsiPolyVariantReference) {
392 final ResolveResult[] psiElements = ((PsiPolyVariantReference)ref).multiResolve(false);
393 if (psiElements.length > 0) {
394 final ResolveResult resolveResult = psiElements[0];
395 if (resolveResult != null) {
396 resolvedElement = resolveResult.getElement();
400 else {
401 resolvedElement = ref.resolve();
403 return resolvedElement;
406 private void disposeHighlighter() {
407 if (myHighlighter != null) {
408 myHighlighterView.getMarkupModel().removeHighlighter(myHighlighter);
409 Component internalComponent = myHighlighterView.getContentComponent();
410 internalComponent.setCursor(myStoredCursor);
411 internalComponent.removeKeyListener(myEditorKeyListener);
412 myHighlighterView.getScrollingModel().removeVisibleAreaListener(myVisibleAreaListener);
413 myFileEditorManager.removeFileEditorManagerListener(myFileEditorManagerListener);
414 HintManager.getInstance().hideAllHints();
415 myHighlighter = null;
416 myHighlighterView = null;
417 myStoredCursor = null;
419 myStoredInfo = null;
422 private class TooltipProvider {
423 private final Editor myEditor;
424 private final LogicalPosition myPosition;
425 private BrowseMode myBrowseMode;
427 public TooltipProvider(Editor editor, LogicalPosition pos) {
428 myEditor = editor;
429 myPosition = pos;
432 public BrowseMode getBrowseMode() {
433 return myBrowseMode;
436 public void execute(BrowseMode browseMode) {
437 myBrowseMode = browseMode;
438 final Info info;
439 try {
440 info = getInfoAt(myEditor, myPosition, myBrowseMode);
442 catch (IndexNotReadyException e) {
443 showDumbModeNotification(myProject);
444 return;
446 if (info == null) return;
448 Component internalComponent = myEditor.getContentComponent();
449 if (myHighlighter != null) {
450 if (!info.isSimilarTo(myStoredInfo)) {
451 disposeHighlighter();
452 } else {
453 // highlighter already set
454 internalComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
455 return;
459 if (info.isValid(myEditor.getDocument())) {
460 installLinkHighlighter(info);
462 internalComponent.addKeyListener(myEditorKeyListener);
463 myEditor.getScrollingModel().addVisibleAreaListener(myVisibleAreaListener);
464 myStoredCursor = internalComponent.getCursor();
465 myStoredInfo = info;
466 internalComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
467 myFileEditorManager.addFileEditorManagerListener(myFileEditorManagerListener);
469 String text = info.getInfo();
471 if (text == null) return;
473 JLabel label = HintUtil.createInformationLabel(text);
474 label.setUI(new MultiLineLabelUI());
475 Font FONT = UIUtil.getLabelFont();
476 label.setFont(FONT);
477 final LightweightHint hint = new LightweightHint(label);
478 final HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();
479 label.addMouseMotionListener(new MouseMotionAdapter() {
480 public void mouseMoved(MouseEvent e) {
481 hintManager.hideAllHints();
484 Point p = HintManagerImpl.getHintPosition(hint, myEditor, myPosition, HintManager.ABOVE);
485 hintManager.showEditorHint(hint, myEditor, p,
486 HintManager.HIDE_BY_ANY_KEY | HintManager.HIDE_BY_TEXT_CHANGE | HintManager.HIDE_BY_SCROLLING,
487 0, false);
491 private void installLinkHighlighter(Info info) {
492 int startOffset = info.myStartOffset;
493 int endOffset = info.myEndOffset;
494 myHighlighter =
495 myEditor.getMarkupModel().addRangeHighlighter(startOffset, endOffset, HighlighterLayer.SELECTION + 1,
496 ourReferenceAttributes, HighlighterTargetArea.EXACT_RANGE);
497 myHighlighterView = myEditor;