sticky documentation popup [take 1]
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / documentation / DocumentationManager.java
blob1dfe1562961efd8775a8b086edea2896f3980de7
1 /*
2 * Copyright 2000-2009 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.codeInsight.documentation;
19 import com.intellij.codeInsight.CodeInsightBundle;
20 import com.intellij.codeInsight.TargetElementUtilBase;
21 import com.intellij.codeInsight.hint.HintManagerImpl;
22 import com.intellij.codeInsight.hint.ParameterInfoController;
23 import com.intellij.codeInsight.lookup.Lookup;
24 import com.intellij.codeInsight.lookup.LookupElement;
25 import com.intellij.codeInsight.lookup.LookupManager;
26 import com.intellij.ide.BrowserUtil;
27 import com.intellij.ide.DataManager;
28 import com.intellij.ide.IdeEventQueue;
29 import com.intellij.ide.util.PropertiesComponent;
30 import com.intellij.ide.util.gotoByName.ChooseByNameBase;
31 import com.intellij.lang.Language;
32 import com.intellij.lang.LanguageDocumentation;
33 import com.intellij.lang.documentation.CompositeDocumentationProvider;
34 import com.intellij.lang.documentation.DocumentationProvider;
35 import com.intellij.openapi.actionSystem.*;
36 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
37 import com.intellij.openapi.actionSystem.ex.AnActionListener;
38 import com.intellij.openapi.application.ApplicationManager;
39 import com.intellij.openapi.components.ServiceManager;
40 import com.intellij.openapi.editor.Editor;
41 import com.intellij.openapi.project.IndexNotReadyException;
42 import com.intellij.openapi.project.Project;
43 import com.intellij.openapi.ui.popup.JBPopup;
44 import com.intellij.openapi.ui.popup.JBPopupFactory;
45 import com.intellij.openapi.util.*;
46 import com.intellij.openapi.wm.*;
47 import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
48 import com.intellij.openapi.wm.ex.WindowManagerEx;
49 import com.intellij.psi.*;
50 import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
51 import com.intellij.psi.presentation.java.SymbolPresentationUtil;
52 import com.intellij.psi.util.PsiTreeUtil;
53 import com.intellij.psi.util.PsiUtilBase;
54 import com.intellij.ui.content.*;
55 import com.intellij.ui.popup.AbstractPopup;
56 import com.intellij.ui.popup.NotLookupOrSearchCondition;
57 import com.intellij.ui.popup.PopupUpdateProcessor;
58 import com.intellij.util.Alarm;
59 import com.intellij.util.Processor;
60 import com.intellij.util.containers.ContainerUtil;
61 import com.intellij.util.ui.update.Activatable;
62 import com.intellij.util.ui.update.UiNotifyConnector;
63 import org.jetbrains.annotations.NonNls;
64 import org.jetbrains.annotations.NotNull;
65 import org.jetbrains.annotations.Nullable;
67 import javax.swing.*;
68 import java.awt.*;
69 import java.awt.event.ActionEvent;
70 import java.awt.event.ActionListener;
71 import java.lang.ref.WeakReference;
72 import java.util.*;
73 import java.util.List;
75 public class DocumentationManager {
76 private static final String SHOW_DOCUMENTATION_IN_TOOL_WINDOW = "ShowDocumentationInToolWindow";
77 private static final String DOCUMENTATION_AUTO_UPDATE_ENABLED = "DocumentationAutoUpdateEnabled";
78 @NonNls public static final String JAVADOC_LOCATION_AND_SIZE = "javadoc.popup";
79 private final Project myProject;
80 private Editor myEditor = null;
81 private ParameterInfoController myParameterInfoController;
82 private final Alarm myUpdateDocAlarm;
83 private WeakReference<JBPopup> myDocInfoHintRef;
84 private Component myPreviouslyFocused = null;
85 public static final Key<SmartPsiElementPointer> ORIGINAL_ELEMENT_KEY = Key.create("Original element");
86 @NonNls public static final String PSI_ELEMENT_PROTOCOL = "psi_element://";
87 @NonNls private static final String DOC_ELEMENT_PROTOCOL = "doc_element://";
88 private ToolWindow myToolWindow = null;
90 private final ActionManagerEx myActionManagerEx;
92 private static final int ourFlagsForTargetElements = TargetElementUtilBase.getInstance().getAllAccepted();
93 private boolean myAutoUpdateDocumentation = PropertiesComponent.getInstance().isTrueValue(DOCUMENTATION_AUTO_UPDATE_ENABLED);
94 private Runnable myAutoUpdateRequest;
96 public static DocumentationManager getInstance(Project project) {
97 return ServiceManager.getService(project, DocumentationManager.class);
100 public DocumentationManager(Project project, ActionManagerEx managerEx) {
101 myProject = project;
102 myActionManagerEx = managerEx;
103 final AnActionListener actionListener = new AnActionListener() {
104 public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
105 final JBPopup hint = getDocInfoHint();
106 if (hint != null) {
107 if (action instanceof HintManagerImpl.ActionToIgnore) return;
108 if (action == myActionManagerEx.getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN)) return;
109 if (action == myActionManagerEx.getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_UP)) return;
110 if (action == myActionManagerEx.getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_PAGE_DOWN)) return;
111 if (action == myActionManagerEx.getAction(IdeActions.ACTION_EDITOR_MOVE_CARET_PAGE_UP)) return;
112 if (action == ActionManagerEx.getInstanceEx().getAction(IdeActions.ACTION_EDITOR_ESCAPE)) return;
113 hint.cancel();
117 public void beforeEditorTyping(char c, DataContext dataContext) {
118 final JBPopup hint = getDocInfoHint();
119 if (hint != null) {
120 hint.cancel();
125 public void afterActionPerformed(final AnAction action, final DataContext dataContext, AnActionEvent event) {
128 myActionManagerEx.addAnActionListener(actionListener, project);
129 myUpdateDocAlarm = new Alarm(Alarm.ThreadToUse.OWN_THREAD,myProject);
132 public void showJavaDocInfo(@NotNull final PsiElement element, final PsiElement original) {
133 PopupUpdateProcessor updateProcessor = new PopupUpdateProcessor(element.getProject()) {
134 public void updatePopup(Object lookupItemObject) {
135 if (lookupItemObject instanceof PsiElement) {
136 doShowJavaDocInfo((PsiElement)lookupItemObject, true, false, this, original);
141 doShowJavaDocInfo(element, true, false, updateProcessor, original);
144 public void showJavaDocInfo(final Editor editor, @Nullable final PsiFile file, boolean requestFocus) {
145 myEditor = editor;
146 final Project project = getProject(file);
147 PsiDocumentManager.getInstance(project).commitAllDocuments();
149 final PsiElement list =
150 ParameterInfoController.findArgumentList(file, editor.getCaretModel().getOffset(), -1);
151 if (list != null) {
152 myParameterInfoController = ParameterInfoController.findControllerAtOffset(editor, list.getTextRange().getStartOffset());
155 final PsiElement originalElement = file != null ? file.findElementAt(editor.getCaretModel().getOffset()) : null;
156 PsiElement element = findTargetElement(editor, file, originalElement);
158 if (element == null && myParameterInfoController != null) {
159 final Object[] objects = myParameterInfoController.getSelectedElements();
161 if (objects != null && objects.length > 0) {
162 if (objects[0] instanceof PsiElement) {
163 element = (PsiElement)objects[0];
168 if (element == null && file == null) return; //file == null for text field editor
170 if (element == null) { // look if we are within a javadoc comment
171 element = originalElement;
172 if (element == null) return;
173 PsiComment comment = PsiTreeUtil.getParentOfType(element, PsiComment.class);
174 if (comment == null) return;
175 element = comment.getParent();
176 //if (!(element instanceof PsiDocCommentOwner)) return null;
179 storeOriginalElement(project, originalElement, element);
181 final PopupUpdateProcessor updateProcessor = new PopupUpdateProcessor(project) {
182 public void updatePopup(Object lookupIteObject) {
183 if (lookupIteObject instanceof PsiElement) {
184 doShowJavaDocInfo((PsiElement)lookupIteObject, false, false, this, originalElement);
185 return;
188 DocumentationProvider documentationProvider = getProviderFromElement(file);
190 PsiElement element = null;
191 if (documentationProvider!=null) {
192 element = documentationProvider.getDocumentationElementForLookupItem(
193 PsiManager.getInstance(myProject),
194 lookupIteObject,
195 originalElement
199 if (element == null) return;
201 if (myEditor != null) {
202 final PsiFile file = element.getContainingFile();
203 if (file != null) {
204 Editor editor = myEditor;
205 showJavaDocInfo(myEditor, file, false);
206 myEditor = editor;
209 else {
210 doShowJavaDocInfo(element, false, false, this, originalElement);
215 doShowJavaDocInfo(element, false, requestFocus, updateProcessor, originalElement);
218 private void doShowJavaDocInfo(final PsiElement element, boolean heavyWeight, boolean requestFocus, PopupUpdateProcessor updateProcessor, final PsiElement originalElement) {
219 Project project = getProject(element);
221 if (myToolWindow == null && PropertiesComponent.getInstance().isTrueValue(SHOW_DOCUMENTATION_IN_TOOL_WINDOW)) {
222 createToolWindow(element, originalElement);
223 } else if (myToolWindow != null) {
224 final Content content = myToolWindow.getContentManager().getSelectedContent();
225 if (content != null) {
226 final DocumentationComponent component = (DocumentationComponent) content.getComponent();
227 if (component.getElement() != element) {
228 content.setDisplayName(getTitle(element, true));
229 fetchDocInfo(getDefaultCollector(element, originalElement), component, true);
232 if (!myToolWindow.isVisible()) myToolWindow.show(null);
234 } else {
235 final DocumentationComponent component = new DocumentationComponent(this);
236 Processor<JBPopup> pinCallback = new Processor<JBPopup>() {
237 public boolean process(JBPopup popup) {
238 createToolWindow(element, originalElement);
239 popup.cancel();
240 return false;
244 final List<Pair<ActionListener, KeyStroke>> actions = Collections.singletonList(Pair.<ActionListener, KeyStroke>create(new ActionListener() {
245 public void actionPerformed(ActionEvent e) {
246 createToolWindow(element, originalElement);
247 final JBPopup hint = getDocInfoHint();
248 if (hint != null && hint.isVisible()) hint.cancel();
250 }, ActionManagerEx.getInstanceEx().getKeyboardShortcut("QuickJavaDoc").getFirstKeyStroke()));
252 final JBPopup hint = JBPopupFactory.getInstance().createComponentPopupBuilder(component, component)
253 .setRequestFocusCondition(project, NotLookupOrSearchCondition.INSTANCE)
254 .setProject(project)
255 .addListener(updateProcessor)
256 .addUserData(updateProcessor)
257 .setKeyboardActions(actions)
258 .setForceHeavyweight(heavyWeight)
259 .setDimensionServiceKey(myProject, JAVADOC_LOCATION_AND_SIZE, false)
260 .setResizable(true)
261 .setMovable(true)
262 .setTitle(getTitle(element, false))
263 .setCouldPin(pinCallback)
264 .setCancelCallback(new Computable<Boolean>() {
265 public Boolean compute() {
266 if (fromQuickSearch()) {
267 ((ChooseByNameBase.JPanelProvider)myPreviouslyFocused.getParent()).unregisterHint();
270 Disposer.dispose(component);
271 myEditor = null;
272 myPreviouslyFocused = null;
273 myParameterInfoController = null;
274 return Boolean.TRUE;
277 .createPopup();
280 AbstractPopup oldHint = (AbstractPopup)getDocInfoHint();
281 if (oldHint != null) {
282 DocumentationComponent oldComponent = (DocumentationComponent)oldHint.getComponent();
283 PsiElement element1 = oldComponent.getElement();
284 if (Comparing.equal(element, element1)) {
285 if (requestFocus) {
286 component.getComponent().requestFocus();
288 return;
290 oldHint.cancel();
293 component.setHint(hint);
295 fetchDocInfo(getDefaultCollector(element, originalElement), component);
297 myDocInfoHintRef = new WeakReference<JBPopup>(hint);
298 myPreviouslyFocused = WindowManagerEx.getInstanceEx().getFocusedComponent(project);
300 if (fromQuickSearch()) {
301 ((ChooseByNameBase.JPanelProvider)myPreviouslyFocused.getParent()).registerHint(hint);
306 private void createToolWindow(final PsiElement element, PsiElement originalElement) {
307 assert myToolWindow == null;
309 final DocumentationComponent component = new DocumentationComponent(this, new AnAction[]{
310 new ToggleAction("Auto show documentation for selected element", "Show documentation for current element automatically",
311 IconLoader.getIcon("/general/autoscrollFromSource.png")) {
312 @Override
313 public boolean isSelected(AnActionEvent e) {
314 return myAutoUpdateDocumentation;
317 @Override
318 public void setSelected(AnActionEvent e, boolean state) {
319 PropertiesComponent.getInstance().setValue(DOCUMENTATION_AUTO_UPDATE_ENABLED, Boolean.TRUE.toString());
320 myAutoUpdateDocumentation = state;
321 restartAutoUpdate(state);
324 new AnAction("Restore popup behavior", "Restore documentation popup behavior", IconLoader.getIcon("/actions/cancel.png")) {
325 @Override
326 public void actionPerformed(AnActionEvent e) {
327 restorePopupBehavior();
329 }});
331 final ToolWindowManagerEx toolWindowManagerEx = ToolWindowManagerEx.getInstanceEx(myProject);
332 myToolWindow = toolWindowManagerEx.registerToolWindow(ToolWindowId.DOCUMENTATION, true, ToolWindowAnchor.RIGHT, myProject);
333 myToolWindow.setIcon(IconLoader.getIcon("/general/documentation.png"));
335 myToolWindow.setAvailable(true, null);
336 myToolWindow.setToHideOnEmptyContent(false);
337 myToolWindow.setAutoHide(false);
339 final Rectangle rectangle = WindowManager.getInstance().getIdeFrame(myProject).suggestChildFrameBounds();
340 myToolWindow.setDefaultState(ToolWindowAnchor.RIGHT, ToolWindowType.FLOATING, rectangle);
342 final ContentManager contentManager = myToolWindow.getContentManager();
343 final ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
344 final Content content = contentFactory.createContent(component, getTitle(element, true), false);
345 contentManager.addContent(content);
347 contentManager.addContentManagerListener(new ContentManagerAdapter() {
348 @Override
349 public void contentRemoved(ContentManagerEvent event) {
350 if (contentManager.getContentCount() == 0) {
351 restorePopupBehavior();
356 new UiNotifyConnector(component, new Activatable() {
357 public void showNotify() {
358 restartAutoUpdate(myAutoUpdateDocumentation);
361 public void hideNotify() {
362 restartAutoUpdate(false);
366 myToolWindow.show(null);
367 PropertiesComponent.getInstance().setValue(SHOW_DOCUMENTATION_IN_TOOL_WINDOW, Boolean.TRUE.toString());
368 restartAutoUpdate(PropertiesComponent.getInstance().isTrueValue(DOCUMENTATION_AUTO_UPDATE_ENABLED));
369 fetchDocInfo(getDefaultCollector(element, originalElement), component);
372 private void restartAutoUpdate(final boolean state) {
373 if (state && myToolWindow != null) {
374 if (myAutoUpdateRequest == null) {
375 myAutoUpdateRequest = new Runnable() {
376 public void run() {
377 final DataContext dataContext = DataManager.getInstance().getDataContext();
378 final Editor editor = PlatformDataKeys.EDITOR.getData(dataContext);
379 if (editor != null) {
380 final PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, myProject);
382 final Editor injectedEditor = InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(editor, file);
383 if (injectedEditor != null) {
384 final PsiFile psiFile = PsiUtilBase.getPsiFileInEditor(injectedEditor, myProject);
385 if (psiFile != null) {
386 showJavaDocInfo(injectedEditor, psiFile, false);
387 return;
391 if (file != null) {
392 showJavaDocInfo(editor, file, false);
398 IdeEventQueue.getInstance().addIdleListener(myAutoUpdateRequest, 500);
400 } else {
401 if (myAutoUpdateRequest != null) {
402 IdeEventQueue.getInstance().removeIdleListener(myAutoUpdateRequest);
403 myAutoUpdateRequest = null;
408 private void restorePopupBehavior() {
409 if (myToolWindow != null) {
410 PropertiesComponent.getInstance().setValue(SHOW_DOCUMENTATION_IN_TOOL_WINDOW, Boolean.FALSE.toString());
411 ToolWindowManagerEx.getInstanceEx(myProject).unregisterToolWindow(ToolWindowId.DOCUMENTATION);
412 myToolWindow = null;
413 restartAutoUpdate(false);
417 private static String getTitle(@NotNull final PsiElement element, final boolean _short) {
418 final String title = SymbolPresentationUtil.getSymbolPresentableText(element);
419 return _short ? title != null ? title : element.getText() : CodeInsightBundle.message("javadoc.info.title", title != null ? title : element.getText());
422 public static void storeOriginalElement(final Project project, final PsiElement originalElement, final PsiElement element) {
423 if (element == null) return;
424 try {
425 element.putUserData(
426 ORIGINAL_ELEMENT_KEY,
427 SmartPointerManager.getInstance(project).createSmartPsiElementPointer(originalElement)
429 } catch (RuntimeException ex) {
430 // PsiPackage does not allow putUserData
434 @Nullable
435 public PsiElement findTargetElement(final Editor editor, @Nullable final PsiFile file, PsiElement contextElement) {
436 PsiElement element = editor != null ? TargetElementUtilBase.findTargetElement(editor, ourFlagsForTargetElements) : null;
438 // Allow context doc over xml tag content
439 if (element != null || contextElement != null) {
440 final PsiElement adjusted = TargetElementUtilBase.getInstance()
441 .adjustElement(editor, ourFlagsForTargetElements, element, contextElement);
442 if (adjusted != null) {
443 element = adjusted;
447 if (element == null && editor != null) {
448 element = getElementFromLookup(editor, file);
450 if (element == null) {
451 final PsiReference ref = TargetElementUtilBase.findReference(editor, editor.getCaretModel().getOffset());
453 if (ref != null) {
454 element = TargetElementUtilBase.getInstance().adjustReference(ref);
455 if (element == null && ref instanceof PsiPolyVariantReference) {
456 element = ref.getElement();
462 storeOriginalElement(myProject, contextElement, element);
464 return element;
467 @Nullable
468 public PsiElement getElementFromLookup(final Editor editor, @Nullable final PsiFile file) {
470 final Lookup activeLookup = LookupManager.getInstance(myProject).getActiveLookup();
472 if (activeLookup != null) {
473 LookupElement item = activeLookup.getCurrentItem();
474 if (item != null) {
476 final PsiElement contextElement = file != null ? file.findElementAt(editor.getCaretModel().getOffset()) : null;
477 final PsiReference ref = TargetElementUtilBase.findReference(editor, editor.getCaretModel().getOffset());
479 final DocumentationProvider documentationProvider = getProviderFromElement(file);
481 return documentationProvider.getDocumentationElementForLookupItem(
482 PsiManager.getInstance(myProject),
483 item.getObject(),
484 ref != null ? ref.getElement():contextElement
488 return null;
491 private boolean fromQuickSearch() {
492 return myPreviouslyFocused != null && myPreviouslyFocused.getParent() instanceof ChooseByNameBase.JPanelProvider;
495 private DocumentationCollector getDefaultCollector(@NotNull final PsiElement element, @Nullable final PsiElement originalElement) {
496 return new DocumentationCollector() {
498 @Nullable
499 public String getDocumentation() throws Exception {
500 final DocumentationProvider provider = getProviderFromElement(element, originalElement);
501 if (myParameterInfoController != null) {
502 final Object[] objects = myParameterInfoController.getSelectedElements();
504 if (objects.length > 0) {
505 @NonNls StringBuffer sb = null;
507 for(Object o:objects) {
508 PsiElement parameter = null;
509 if (o instanceof PsiElement) {
510 parameter = (PsiElement)o;
513 if (parameter != null) {
514 final SmartPsiElementPointer originalElement = parameter.getUserData(ORIGINAL_ELEMENT_KEY);
515 final String str2 = provider.generateDoc(parameter, originalElement != null ? originalElement.getElement() : null);
516 if (str2 == null) continue;
517 if (sb == null) sb = new StringBuffer();
518 sb.append(str2);
519 sb.append("<br>");
520 } else {
521 sb = null;
522 break;
526 if (sb != null) return sb.toString();
530 final SmartPsiElementPointer originalElement = element.getUserData(ORIGINAL_ELEMENT_KEY);
531 return provider.generateDoc(element, originalElement != null ? originalElement.getElement() : null);
534 @Nullable
535 public PsiElement getElement() {
536 return element.isValid() ? element : null;
541 @Nullable
542 public JBPopup getDocInfoHint() {
543 if (myDocInfoHintRef == null) return null;
544 JBPopup hint = myDocInfoHintRef.get();
545 if (hint == null || !hint.isVisible()) {
546 myDocInfoHintRef = null;
547 return null;
549 return hint;
552 public void fetchDocInfo(final DocumentationCollector provider, final DocumentationComponent component) {
553 doFetchDocInfo(component, provider, true, false);
556 public void fetchDocInfo(final DocumentationCollector provider, final DocumentationComponent component, final boolean clearHistory) {
557 doFetchDocInfo(component, provider, true, clearHistory);
560 public void fetchDocInfo(final PsiElement element, final DocumentationComponent component) {
561 doFetchDocInfo(component, getDefaultCollector(element, null), true, false);
564 public ActionCallback queueFetchDocInfo(final DocumentationCollector provider, final DocumentationComponent component, final boolean clearHistory) {
565 return doFetchDocInfo(component, provider, false, clearHistory);
568 public ActionCallback queueFetchDocInfo(final PsiElement element, final DocumentationComponent component) {
569 return queueFetchDocInfo(getDefaultCollector(element, null), component, false);
572 private ActionCallback doFetchDocInfo(final DocumentationComponent component, final DocumentationCollector provider, final boolean cancelRequests, final boolean clearHistory) {
573 final ActionCallback callback = new ActionCallback();
574 component.startWait();
575 if (cancelRequests) {
576 myUpdateDocAlarm.cancelAllRequests();
578 if (component.isEmpty()) {
579 component.setText(CodeInsightBundle.message("javadoc.fetching.progress"), null, clearHistory);
582 myUpdateDocAlarm.addRequest(new Runnable() {
583 public void run() {
584 final Throwable[] ex = new Throwable[1];
585 final String text = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
586 @Nullable
587 public String compute() {
588 try {
589 return provider.getDocumentation();
591 catch (Throwable e) {
592 ex[0] = e;
594 return null;
597 if (ex[0] != null) {
598 //noinspection SSBasedInspection
599 SwingUtilities.invokeLater(new Runnable() {
600 public void run() {
601 String message = ex[0] instanceof IndexNotReadyException
602 ? "Documentation is not available until indices are built."
603 : CodeInsightBundle.message("javadoc.external.fetch.error.message", ex[0].getLocalizedMessage());
604 component.setText(message, null, true);
605 callback.setDone();
608 return;
611 final PsiElement element = ApplicationManager.getApplication().runReadAction(new Computable<PsiElement>() {
612 @Nullable
613 public PsiElement compute() {
614 return provider.getElement();
617 if (element == null) {
618 return;
620 //noinspection SSBasedInspection
621 SwingUtilities.invokeLater(new Runnable() {
622 public void run() {
623 ApplicationManager.getApplication().runWriteAction(new Runnable() {
624 public void run() {
625 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
629 if (!element.isValid()) {
630 callback.setDone();
631 return;
634 if (text == null) {
635 component.setText(CodeInsightBundle.message("no.documentation.found"), element, true);
637 else if (text.length() == 0) {
638 component.setText(component.getText(), element, true, clearHistory);
640 else {
641 component.setData(element, text, clearHistory);
644 final AbstractPopup jbPopup = (AbstractPopup)getDocInfoHint();
645 if(jbPopup==null){
646 callback.setDone();
647 return;
649 jbPopup.setCaption(getTitle(element, false));
650 final String dimensionServiceKey = jbPopup.getDimensionServiceKey();
651 Dimension dimension = component.getPreferredSize();
652 final Dimension storedSize = dimensionServiceKey != null ? DimensionService.getInstance().getSize(dimensionServiceKey, getProject(element)) : null;
653 if (storedSize != null) {
654 dimension = storedSize;
656 final Window window = SwingUtilities.getWindowAncestor(component);
657 if (window != null) {
658 window.setBounds(window.getX(), window.getY(), dimension.width, dimension.height);
659 window.validate();
660 window.repaint();
662 callback.setDone();
666 }, 10);
667 return callback;
670 @NotNull
671 public static DocumentationProvider getProviderFromElement(final PsiElement element) {
672 return getProviderFromElement(element, null);
675 @NotNull
676 private static DocumentationProvider getProviderFromElement(@Nullable PsiElement element, @Nullable PsiElement originalElement) {
677 if (element != null && !element.isValid()) {
678 element = null;
680 if (originalElement != null && !originalElement.isValid()) {
681 originalElement = null;
684 if (originalElement == null) {
685 originalElement = getOriginalElement(element);
688 PsiFile containingFile =
689 originalElement != null ? originalElement.getContainingFile() : element != null ? element.getContainingFile() : null;
690 Set<DocumentationProvider> result = new LinkedHashSet<DocumentationProvider>();
692 final Language containingFileLanguage = containingFile != null ? containingFile.getLanguage() : null;
693 DocumentationProvider originalProvider =
694 containingFile != null ? LanguageDocumentation.INSTANCE.forLanguage(containingFileLanguage) : null;
696 final Language elementLanguage = element != null ? element.getLanguage() : null;
697 DocumentationProvider elementProvider =
698 element == null || elementLanguage.is(containingFileLanguage) ? null : LanguageDocumentation.INSTANCE.forLanguage(elementLanguage);
700 addProviderToResult(result, elementProvider);
701 addProviderToResult(result, originalProvider);
703 if (containingFile != null) {
704 final Language baseLanguage = containingFile.getViewProvider().getBaseLanguage();
705 if (!baseLanguage.is(containingFileLanguage)) {
706 addProviderToResult(result, LanguageDocumentation.INSTANCE.forLanguage(baseLanguage));
709 // return extensible documentation provider even if the list is empty
710 return new CompositeDocumentationProvider(result);
713 private static void addProviderToResult(final Set<DocumentationProvider> result, final DocumentationProvider t) {
714 if (t instanceof CompositeDocumentationProvider) result.addAll(((CompositeDocumentationProvider)t).getProviders());
715 else ContainerUtil.addIfNotNull(t, result);
718 @Nullable
719 public static PsiElement getOriginalElement(final PsiElement element) {
720 SmartPsiElementPointer originalElementPointer = element!=null ? element.getUserData(ORIGINAL_ELEMENT_KEY):null;
721 return originalElementPointer != null ? originalElementPointer.getElement() : null;
724 void navigateByLink(final DocumentationComponent component, String url) {
725 component.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
726 final PsiElement psiElement = component.getElement();
727 final PsiManager manager = PsiManager.getInstance(getProject(psiElement));
728 if (url.startsWith(PSI_ELEMENT_PROTOCOL)) {
729 final String refText = url.substring(PSI_ELEMENT_PROTOCOL.length());
730 DocumentationProvider provider = getProviderFromElement(psiElement);
731 final PsiElement targetElement = provider.getDocumentationElementForLink(manager, refText, psiElement);
732 if (targetElement != null) {
733 fetchDocInfo(getDefaultCollector(targetElement, null), component);
736 else {
737 final String docUrl = url;
739 fetchDocInfo
740 (new DocumentationCollector() {
741 public String getDocumentation() throws Exception {
742 if (docUrl.startsWith(DOC_ELEMENT_PROTOCOL)) {
743 final DocumentationProvider provider = getProviderFromElement(psiElement);
744 final List<String> urls = provider.getUrlFor(psiElement, getOriginalElement(psiElement));
745 BrowserUtil.launchBrowser(urls != null && !urls.isEmpty() ? urls.get(0) : docUrl);
746 } else {
747 BrowserUtil.launchBrowser(docUrl);
749 return "";
752 public PsiElement getElement() {
753 //String loc = getElementLocator(docUrl);
755 //if (loc != null) {
756 // PsiElement context = component.getElement();
757 // return JavaDocUtil.findReferenceTarget(context.getManager(), loc, context);
760 return psiElement;
762 }, component);
765 component.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
768 void showHint(final JBPopup hint) {
769 if (myEditor != null) {
770 hint.showInBestPositionFor(myEditor);
772 else if (myPreviouslyFocused != null) {
773 hint.showInBestPositionFor(DataManager.getInstance().getDataContext(myPreviouslyFocused));
774 } else {
775 hint.showInBestPositionFor(DataManager.getInstance().getDataContext());
779 public void requestFocus() {
780 if (fromQuickSearch()) {
781 myPreviouslyFocused.getParent().requestFocus();
785 public Project getProject(@Nullable final PsiElement element) {
786 assert element == null || !element.isValid() || myProject == element.getProject();
787 return myProject;
790 @SuppressWarnings({"HardCodedStringLiteral"})
791 public static void createHyperlink(StringBuilder buffer, String refText,String label,boolean plainLink) {
792 buffer.append("<a href=\"");
793 buffer.append("psi_element://"); // :-)
794 buffer.append(refText);
795 buffer.append("\">");
796 if (!plainLink) {
797 buffer.append("<code>");
799 buffer.append(label);
800 if (!plainLink) {
801 buffer.append("</code>");
803 buffer.append("</a>");
806 private static interface DocumentationCollector {
807 @Nullable
808 String getDocumentation() throws Exception;
809 @Nullable
810 PsiElement getElement();