IDEA-66333 Quick documentation lookup on mouse hover

Added ability to configure the ide to auto-show full quick doc control for element under mouse
This commit is contained in:
Denis.Zhdanov
2013-03-19 16:42:56 +04:00
parent 0bfbc72fe8
commit 6fc0a83581
7 changed files with 601 additions and 32 deletions
@@ -2,7 +2,7 @@
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.application.options.editor.EditorOptionsPanel">
<tabbedpane id="97a1e">
<constraints>
<xy x="22" y="49" width="770" height="743"/>
<xy x="22" y="49" width="812" height="743"/>
</constraints>
<properties/>
<border type="none"/>
@@ -304,7 +304,7 @@
</component>
</children>
</grid>
<grid id="7f3f5" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="7f3f5" layout-manager="GridLayoutManager" row-count="4" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
@@ -317,7 +317,7 @@
<children>
<component id="901e0" class="javax.swing.JCheckBox" binding="myCbEnsureBlankLineBeforeCheckBox" default-binding="true">
<constraints>
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="1" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Ensure line feed at file end on Save"/>
@@ -325,7 +325,7 @@
</component>
<component id="95b8f" class="javax.swing.JLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="combobox.strip.trailing.spaces.on.save"/>
@@ -333,13 +333,40 @@
</component>
<component id="87620" class="javax.swing.JComboBox" binding="myStripTrailingSpacesCombo">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<vspacer id="82d85">
<component id="ce8e2" class="com.intellij.ui.components.JBCheckBox" binding="myCbShowQuickDocOnMouseMove">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Show quick doc on mouse move"/>
</properties>
</component>
<component id="ec4ad" class="javax.swing.JTextField" binding="myQuickDocDelayTextField">
<constraints>
<grid row="2" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false">
<preferred-size width="50" height="-1"/>
</grid>
</constraints>
<properties>
<text value="500"/>
</properties>
</component>
<component id="8691a" class="com.intellij.ui.components.JBLabel" binding="myQuickDocDelayLabel">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<horizontalAlignment value="11"/>
<text value="Delay (ms):"/>
</properties>
</component>
<vspacer id="612dd">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
</children>
@@ -21,8 +21,10 @@ import com.intellij.application.options.OptionsApplicabilityFilter;
import com.intellij.codeInsight.CodeInsightSettings;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.codeInsight.daemon.impl.IdentifierHighlighterPass;
import com.intellij.codeInsight.documentation.QuickDocOnMouseOverManager;
import com.intellij.ide.ui.UISettings;
import com.intellij.openapi.application.ApplicationBundle;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
@@ -38,7 +40,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBLabel;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
@@ -46,11 +51,11 @@ import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class EditorOptionsPanel {
private JPanel myBehaviourPanel;
private JPanel myBehaviourPanel;
private JCheckBox myCbHighlightBraces;
private static final String STRIP_CHANGED = ApplicationBundle.message("combobox.strip.modified.lines");
private static final String STRIP_ALL = ApplicationBundle.message("combobox.strip.all");
private static final String STRIP_ALL = ApplicationBundle.message("combobox.strip.all");
private static final String STRIP_NONE = ApplicationBundle.message("combobox.strip.none");
private JComboBox myStripTrailingSpacesCombo;
@@ -63,32 +68,35 @@ public class EditorOptionsPanel {
private JCheckBox myCbHighlightScope;
private JTextField myClipboardContentLimitTextField;
private JCheckBox myCbSmoothScrolling;
private JCheckBox myCbVirtualPageAtBottom;
private JCheckBox myCbEnableDnD;
private JCheckBox myCbEnableWheelFontChange;
private JCheckBox myCbHonorCamelHumpsWhenSelectingByClicking;
private JCheckBox myCbSmoothScrolling;
private JCheckBox myCbVirtualPageAtBottom;
private JCheckBox myCbEnableDnD;
private JCheckBox myCbEnableWheelFontChange;
private JCheckBox myCbHonorCamelHumpsWhenSelectingByClicking;
private JPanel myHighlightSettingsPanel;
private JPanel myHighlightSettingsPanel;
private JRadioButton myRbPreferScrolling;
private JRadioButton myRbPreferMovingCaret;
private JCheckBox myCbRenameLocalVariablesInplace;
private JCheckBox myCbHighlightIdentifierUnderCaret;
private JCheckBox myCbEnsureBlankLineBeforeCheckBox;
private JCheckBox myShowReformatCodeDialogCheckBox;
private JCheckBox myShowOptimizeImportsDialogCheckBox;
private JCheckBox myCbUseSoftWrapsAtEditor;
private JCheckBox myCbUseSoftWrapsAtConsole;
private JCheckBox myCbUseCustomSoftWrapIndent;
private JTextField myCustomSoftWrapIndent;
private JCheckBox myCbShowAllSoftWraps;
private JCheckBox myPreselectCheckBox;
private JCheckBox myCbRenameLocalVariablesInplace;
private JCheckBox myCbHighlightIdentifierUnderCaret;
private JCheckBox myCbEnsureBlankLineBeforeCheckBox;
private JCheckBox myShowReformatCodeDialogCheckBox;
private JCheckBox myShowOptimizeImportsDialogCheckBox;
private JCheckBox myCbUseSoftWrapsAtEditor;
private JCheckBox myCbUseSoftWrapsAtConsole;
private JCheckBox myCbUseCustomSoftWrapIndent;
private JTextField myCustomSoftWrapIndent;
private JCheckBox myCbShowAllSoftWraps;
private JCheckBox myPreselectCheckBox;
private JBCheckBox myCbShowQuickDocOnMouseMove;
private JBLabel myQuickDocDelayLabel;
private JTextField myQuickDocDelayTextField;
private final ErrorHighlightingPanel myErrorHighlightingPanel = new ErrorHighlightingPanel();
private final MyConfigurable myConfigurable;
public EditorOptionsPanel(){
public EditorOptionsPanel() {
if (SystemInfo.isMac) {
myCbEnableWheelFontChange.setText(ApplicationBundle.message("checkbox.enable.ctrl.mousewheel.changes.font.size.macos"));
}
@@ -106,6 +114,7 @@ public class EditorOptionsPanel {
myCbRenameLocalVariablesInplace.setVisible(OptionsApplicabilityFilter.isApplicable(OptionId.RENAME_IN_PLACE));
myConfigurable = new MyConfigurable();
initQuickDocProcessing();
initSoftWrapsSettingsProcessing();
}
@@ -113,7 +122,7 @@ public class EditorOptionsPanel {
public void reset() {
EditorSettingsExternalizable editorSettings = EditorSettingsExternalizable.getInstance();
CodeInsightSettings codeInsightSettings = CodeInsightSettings.getInstance();
UISettings uiSettings=UISettings.getInstance();
UISettings uiSettings = UISettings.getInstance();
// Display
@@ -156,6 +165,10 @@ public class EditorOptionsPanel {
}
myCbEnsureBlankLineBeforeCheckBox.setSelected(editorSettings.isEnsureNewLineAtEOF());
myCbShowQuickDocOnMouseMove.setSelected(editorSettings.isShowQuickDocOnMouseOverElement());
myQuickDocDelayTextField.setText(Long.toString(editorSettings.getQuickDocOnMouseOverElementDelayMillis()));
myQuickDocDelayTextField.setEnabled(editorSettings.isShowQuickDocOnMouseOverElement());
myQuickDocDelayLabel.setEnabled(editorSettings.isShowQuickDocOnMouseOverElement());
// Advanced mouse
myCbEnableDnD.setSelected(editorSettings.isDndEnabled());
@@ -235,6 +248,17 @@ public class EditorOptionsPanel {
editorSettings.setEnsureNewLineAtEOF(myCbEnsureBlankLineBeforeCheckBox.isSelected());
if (myCbShowQuickDocOnMouseMove.isSelected() ^ editorSettings.isShowQuickDocOnMouseOverElement()) {
boolean enabled = myCbShowQuickDocOnMouseMove.isSelected();
editorSettings.setShowQuickDocOnMouseOverElement(enabled);
ServiceManager.getService(QuickDocOnMouseOverManager.class).setEnabled(enabled);
}
Long quickDocDelay = getQuickDocDelayFromGui();
if (quickDocDelay != null) {
editorSettings.setQuickDocOnMouseOverElementDelayMillis(quickDocDelay);
}
editorSettings.setDndEnabled(myCbEnableDnD.isSelected());
editorSettings.setWheelFontChangeEnabled(myCbEnableWheelFontChange.isSelected());
@@ -268,6 +292,23 @@ public class EditorOptionsPanel {
restartDaemons();
}
@Nullable
private Long getQuickDocDelayFromGui() {
String quickDocDelayAsText = myQuickDocDelayTextField.getText();
if (StringUtil.isEmptyOrSpaces(quickDocDelayAsText)) {
return null;
}
try {
long delay = Long.parseLong(quickDocDelayAsText);
return delay > 0 ? delay : null;
}
catch (NumberFormatException e) {
// Ignore incorrect value.
return null;
}
}
public static void restartDaemons() {
Project[] projects = ProjectManager.getInstance().getOpenProjects();
for (Project project : projects) {
@@ -342,6 +383,12 @@ public class EditorOptionsPanel {
isModified |= !getStripTrailingSpacesValue().equals(editorSettings.getStripTrailingSpaces());
isModified |= isModified(myCbEnsureBlankLineBeforeCheckBox, editorSettings.isEnsureNewLineAtEOF());
isModified |= isModified(myCbShowQuickDocOnMouseMove, editorSettings.isShowQuickDocOnMouseOverElement());
Long quickDocDelay = getQuickDocDelayFromGui();
if (quickDocDelay != null && !quickDocDelay.equals(Long.valueOf(editorSettings.getQuickDocOnMouseOverElementDelayMillis()))) {
return true;
}
// advanced mouse
isModified |= isModified(myCbEnableDnD, editorSettings.isDndEnabled());
isModified |= isModified(myCbEnableWheelFontChange, editorSettings.isWheelFontChangeEnabled());
@@ -404,6 +451,16 @@ public class EditorOptionsPanel {
return defaultIndent;
}
private void initQuickDocProcessing() {
myCbShowQuickDocOnMouseMove.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
myQuickDocDelayTextField.setEnabled(myCbShowQuickDocOnMouseMove.isSelected());
myQuickDocDelayLabel.setEnabled(myCbShowQuickDocOnMouseMove.isSelected());
}
});
}
private void initSoftWrapsSettingsProcessing() {
ItemListener listener = new ItemListener() {
@Override
@@ -95,6 +95,8 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
private static final int ourFlagsForTargetElements = TargetElementUtilBase.getInstance().getAllAccepted();
private boolean myCloseOnSneeze;
@Override
protected String getToolwindowId() {
return ToolWindowId.DOCUMENTATION;
@@ -120,6 +122,15 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
return "Auto Show Documentation for Selected Element";
}
/**
* @return <code>true</code> if quick doc control is configured to not prevent user-IDE interaction (e.g. should be closed if
* the user presses a key);
* <code>false</code> otherwise
*/
public boolean isCloseOnSneeze() {
return myCloseOnSneeze;
}
public static DocumentationManager getInstance(Project project) {
return ServiceManager.getService(project, DocumentationManager.class);
}
@@ -169,6 +180,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
if (hint == null) {
return;
}
myCloseOnSneeze = false;
hint.cancel();
Component toFocus = myPreviouslyFocused;
hint.cancel();
@@ -200,6 +212,33 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
showJavaDocInfo(element, original, false, null);
}
/**
* Asks to show quick doc for the target element.
*
* @param editor editor with an element for which quick do should be shown
* @param element target element which documentation should be shown
* @param original element that was used as a quick doc anchor. Example: consider a code like {@code Runnable task;}.
* A user wants to see javadoc for the {@code Runnable}, so, original element is a class name from the variable
* declaration but <code>'element'</code> argument is a {@code Runnable} descriptor
* @param closeCallback callback to be notified on target hint close (if any)
* @param closeOnSneeze flag that defines whether quick doc control should be as non-obtrusive as possible. E.g. there are at least
* two possible situations - the quick doc is shown automatically on mouse over element; the quick doc is shown
* on explicit action call (Ctrl+Q). We want to close the doc on, say, editor viewport position change
* at the first situation but don't want to do that at the second
* @param allowReuse defines whether currently requested documentation should reuse existing doc control (if any)
*/
public void showJavaDocInfo(@NotNull Editor editor,
@NotNull final PsiElement element,
@NotNull final PsiElement original,
@Nullable Runnable closeCallback,
boolean closeOnSneeze,
boolean allowReuse)
{
myEditor = editor;
myCloseOnSneeze = closeOnSneeze;
showJavaDocInfo(element, original, allowReuse, closeCallback);
}
public void showJavaDocInfo(@NotNull final PsiElement element,
final PsiElement original,
boolean allowReuse,
@@ -403,6 +442,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
.setCancelCallback(new Computable<Boolean>() {
@Override
public Boolean compute() {
myCloseOnSneeze = false;
if (closeCallback != null) {
closeCallback.run();
}
@@ -420,6 +460,9 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
.setKeyEventHandler(new BooleanFunction<KeyEvent>() {
@Override
public boolean fun(KeyEvent event) {
if (myCloseOnSneeze) {
closeDocHint();
}
return false;
}
})
@@ -476,7 +519,11 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
@Nullable
public PsiElement findTargetElement(@NotNull final Editor editor, @Nullable final PsiFile file, PsiElement contextElement) {
int offset = editor.getCaretModel().getOffset();
return findTargetElement(editor, editor.getCaretModel().getOffset(), file, contextElement);
}
@Nullable
public PsiElement findTargetElement(final Editor editor, int offset, @Nullable final PsiFile file, PsiElement contextElement) {
TargetElementUtilBase util = TargetElementUtilBase.getInstance();
PsiElement element = assertSameProject(getElementFromLookup(editor, file));
if (element == null && file != null) {
@@ -485,10 +532,10 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
element = assertSameProject(((DocumentationProviderEx)documentationProvider).getCustomDocumentationElement(editor, file, contextElement));
}
}
if (element == null) {
element = assertSameProject(util.findTargetElement(editor, ourFlagsForTargetElements, offset));
// Allow context doc over xml tag content
if (element != null || contextElement != null) {
final PsiElement adjusted = assertSameProject(util.adjustElement(editor, ourFlagsForTargetElements, element, contextElement));
@@ -497,7 +544,7 @@ public class DocumentationManager extends DockablePopupManager<DocumentationComp
}
}
}
if (element == null) {
final PsiReference ref = TargetElementUtilBase.findReference(editor, offset);
if (ref != null) {
@@ -0,0 +1,376 @@
/*
* 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.documentation;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationActivationListener;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.wm.IdeFrame;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.ui.popup.PopupFactoryImpl;
import com.intellij.util.Alarm;
import com.intellij.util.containers.WeakHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.lang.ref.WeakReference;
import java.util.Map;
/**
* Serves as a facade to the 'show quick doc on mouse over an element' functionality.
* <p/>
* Not thread-safe.
*
* @author Denis Zhdanov
* @since 7/2/12 9:09 AM
*/
public class QuickDocOnMouseOverManager {
@NotNull private final EditorMouseMotionListener myMouseListener = new MyEditorMouseListener();
@NotNull private final VisibleAreaListener myVisibleAreaListener = new MyVisibleAreaListener();
@NotNull private final CaretListener myCaretListener = new MyCaretListener();
@NotNull private final DocumentListener myDocumentListener = new MyDocumentListener();
@NotNull private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
@NotNull private final Runnable myRequest = new MyShowQuickDocRequest();
@NotNull private final Runnable myHintCloseCallback = new MyCloseDocCallback();
@NotNull private final Map<Document, Boolean> myMonitoredDocuments = new WeakHashMap<Document, Boolean>();
private final Map<Editor, PsiElement /** PSI element which is located under the current mouse position */> myActiveElements
= new WeakHashMap<Editor, PsiElement>();
/** Holds a reference (if any) to the documentation manager used last time to show an 'auto quick doc' popup. */
@Nullable private WeakReference<DocumentationManager> myDocumentationManager;
@Nullable private DelayedQuickDocInfo myDelayedQuickDocInfo;
private boolean myEnabled;
private boolean myApplicationActive;
public QuickDocOnMouseOverManager(@NotNull Application application) {
EditorFactory factory = EditorFactory.getInstance();
if (factory != null) {
factory.addEditorFactoryListener(new MyEditorFactoryListener(), application);
}
ApplicationManager.getApplication().getMessageBus().connect().subscribe(
ApplicationActivationListener.TOPIC,
new ApplicationActivationListener() {
@Override
public void applicationActivated(IdeFrame ideFrame) {
myApplicationActive = true;
}
@Override
public void applicationDeactivated(IdeFrame ideFrame) {
myApplicationActive = false;
}
});
}
/**
* Instructs the manager to enable or disable 'show quick doc automatically when the mouse goes over an editor element' mode.
*
* @param enabled flag that identifies if quick doc should be automatically shown
*/
public void setEnabled(boolean enabled) {
myEnabled = enabled;
myApplicationActive = enabled;
if (!enabled) {
closeQuickDocIfPossible();
myAlarm.cancelAllRequests();
}
EditorFactory factory = EditorFactory.getInstance();
if (factory == null) {
return;
}
for (Editor editor : factory.getAllEditors()) {
if (enabled) {
registerListeners(editor);
}
else {
unRegisterListeners(editor);
}
}
}
private void registerListeners(@NotNull Editor editor) {
editor.addEditorMouseMotionListener(myMouseListener);
editor.getScrollingModel().addVisibleAreaListener(myVisibleAreaListener);
editor.getCaretModel().addCaretListener(myCaretListener);
Document document = editor.getDocument();
if (myMonitoredDocuments.put(document, Boolean.TRUE) == null) {
document.addDocumentListener(myDocumentListener);
}
}
private void unRegisterListeners(@NotNull Editor editor) {
editor.removeEditorMouseMotionListener(myMouseListener);
editor.getScrollingModel().removeVisibleAreaListener(myVisibleAreaListener);
editor.getCaretModel().removeCaretListener(myCaretListener);
Document document = editor.getDocument();
if (myMonitoredDocuments.remove(document) != null) {
document.removeDocumentListener(myDocumentListener);
}
}
private void processMouseMove(@NotNull EditorMouseEvent e) {
if (!myApplicationActive || e.getArea() != EditorMouseEventArea.EDITING_AREA) {
// Skip if the mouse is not at the editing area.
closeQuickDocIfPossible();
return;
}
if (e.getMouseEvent().getModifiers() != 0) {
// Don't show the control when any modifier is active (e.g. Ctrl or Alt is hold). There is a common situation that a user
// wants to navigate via Ctrl+click or perform quick evaluate by Alt+click.
return;
}
Editor editor = e.getEditor();
if (editor.isOneLineMode()) {
// Don't want auto quick doc to mess at, say, editor used for debugger condition.
return;
}
Project project = editor.getProject();
if (project == null) {
return;
}
DocumentationManager documentationManager = DocumentationManager.getInstance(project);
JBPopup hint = documentationManager.getDocInfoHint();
if (hint != null) {
// Skip the event if the control is shown because of explicit 'show quick doc' action call.
WeakReference<DocumentationManager> ref = myDocumentationManager;
if (ref == null) {
return;
}
DocumentationManager manager = ref.get();
if (manager == null || !manager.isCloseOnSneeze()) {
return;
}
// Skip the event if the mouse is under the opened quick doc control.
Point hintLocation = hint.getLocationOnScreen();
Dimension hintSize = hint.getSize();
int mouseX = e.getMouseEvent().getXOnScreen();
int mouseY = e.getMouseEvent().getYOnScreen();
if (mouseX >= hintLocation.x && mouseX <= hintLocation.x + hintSize.width && mouseY >= hintLocation.y
&& mouseY <= hintLocation.y + hintSize.height)
{
return;
}
}
PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (psiFile == null) {
closeQuickDocIfPossible();
return;
}
int mouseOffset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(e.getMouseEvent().getPoint()));
PsiElement elementUnderMouse = psiFile.findElementAt(mouseOffset);
if (elementUnderMouse == null || elementUnderMouse instanceof PsiWhiteSpace) {
closeQuickDocIfPossible();
return;
}
PsiElement targetElementUnderMouse = documentationManager.findTargetElement(editor, mouseOffset, psiFile, elementUnderMouse);
if (targetElementUnderMouse == null) {
// No PSI element is located under the current mouse position - close quick doc if any.
closeQuickDocIfPossible();
return;
}
PsiElement activeElement = myActiveElements.get(editor);
if (targetElementUnderMouse.equals(activeElement)
&& (myAlarm.getActiveRequestCount() > 0 // Request to show documentation for the target component has been already queued.
|| hint != null)) // Documentation for the target component is being shown.
{
return;
}
allowUpdateFromContext(false);
closeQuickDocIfPossible();
myActiveElements.put(editor, targetElementUnderMouse);
myDelayedQuickDocInfo = new DelayedQuickDocInfo(documentationManager, editor, targetElementUnderMouse, elementUnderMouse);
myAlarm.cancelAllRequests();
myAlarm.addRequest(myRequest, EditorSettingsExternalizable.getInstance().getQuickDocOnMouseOverElementDelayMillis());
}
private void closeQuickDocIfPossible() {
myAlarm.cancelAllRequests();
DocumentationManager docManager = getDocManager();
if (docManager == null) {
return;
}
JBPopup hint = docManager.getDocInfoHint();
if (hint == null) {
return;
}
hint.cancel();
myDocumentationManager = null;
}
private void allowUpdateFromContext(boolean allow) {
DocumentationManager documentationManager = getDocManager();
if (documentationManager != null) {
documentationManager.setAllowContentUpdateFromContext(allow);
}
}
@Nullable
private DocumentationManager getDocManager() {
WeakReference<DocumentationManager> ref = myDocumentationManager;
if (ref == null) {
return null;
}
DocumentationManager docManager = ref.get();
if (docManager == null) {
return null;
}
return docManager;
}
private static class DelayedQuickDocInfo {
@NotNull public final DocumentationManager docManager;
@NotNull public final Editor editor;
@NotNull public final PsiElement targetElement;
@NotNull public final PsiElement originalElement;
private DelayedQuickDocInfo(@NotNull DocumentationManager docManager,
@NotNull Editor editor, @NotNull PsiElement targetElement,
@NotNull PsiElement originalElement)
{
this.docManager = docManager;
this.editor = editor;
this.targetElement = targetElement;
this.originalElement = originalElement;
}
}
private class MyShowQuickDocRequest implements Runnable {
private final HintManager myHintManager = HintManager.getInstance();
@Override
public void run() {
myAlarm.cancelAllRequests();
// Skip the request if it's outdated (the mouse is moved other another element).
DelayedQuickDocInfo info = myDelayedQuickDocInfo;
if (info == null || !info.targetElement.equals(myActiveElements.get(info.editor))) {
return;
}
// Skip the request if there is a control shown as a result of explicit 'show quick doc' (Ctrl + Q) invocation.
if (info.docManager.getDocInfoHint() != null && !info.docManager.isCloseOnSneeze()) {
return;
}
// We don't want to show a quick doc control if there is an active hint (e.g. the mouse is under an invalid element
// and corresponding error info is shown).
if (!info.docManager.hasActiveDockedDocWindow() && myHintManager.hasShownHintsThatWillHideByOtherHint(false)) {
myAlarm.addRequest(this, EditorSettingsExternalizable.getInstance().getQuickDocOnMouseOverElementDelayMillis());
return;
}
info.editor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION,
info.editor.offsetToVisualPosition(info.originalElement.getTextRange().getStartOffset()));
try {
info.docManager.showJavaDocInfo(info.editor, info.targetElement, info.originalElement, myHintCloseCallback, true, true);
myDocumentationManager = new WeakReference<DocumentationManager>(info.docManager);
myDocumentationManager = new WeakReference<DocumentationManager>(info.docManager);
}
finally {
info.editor.putUserData(PopupFactoryImpl.ANCHOR_POPUP_POSITION, null);
}
}
}
private class MyCloseDocCallback implements Runnable {
@Override
public void run() {
myActiveElements.clear();
myDocumentationManager = null;
}
}
private class MyEditorFactoryListener implements EditorFactoryListener {
@Override
public void editorCreated(@NotNull EditorFactoryEvent event) {
if (myEnabled) {
registerListeners(event.getEditor());
}
}
@Override
public void editorReleased(@NotNull EditorFactoryEvent event) {
if (myEnabled) {
// We do this in the 'if' block because editor logs an error on attempt to remove already released listener.
unRegisterListeners(event.getEditor());
}
}
}
private class MyEditorMouseListener extends EditorMouseMotionAdapter {
@Override
public void mouseMoved(EditorMouseEvent e) {
processMouseMove(e);
}
}
private class MyVisibleAreaListener implements VisibleAreaListener {
@Override
public void visibleAreaChanged(VisibleAreaEvent e) {
closeQuickDocIfPossible();
}
}
private class MyCaretListener implements CaretListener {
@Override
public void caretPositionChanged(CaretEvent e) {
allowUpdateFromContext(true);
closeQuickDocIfPossible();
}
}
private class MyDocumentListener extends DocumentAdapter {
@Override
public void documentChanged(DocumentEvent e) {
closeQuickDocIfPossible();
}
}
}
@@ -0,0 +1,35 @@
/*
* 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.documentation;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupActivity;
/**
* @author Denis Zhdanov
* @since 7/2/12 9:44 AM
*/
public class QuickDocOnMouseOverStartupActivity implements StartupActivity {
@Override
public void runActivity(Project project) {
if (EditorSettingsExternalizable.getInstance().isShowQuickDocOnMouseOverElement()) {
ServiceManager.getService(QuickDocOnMouseOverManager.class).setEnabled(true);
}
}
}
@@ -51,6 +51,8 @@ public class EditorSettingsExternalizable implements NamedJDOMExternalizable, Ex
public boolean IS_CARET_INSIDE_TABS;
@NonNls public String STRIP_TRAILING_SPACES = STRIP_TRAILING_SPACES_CHANGED;
public boolean IS_ENSURE_NEWLINE_AT_EOF = false;
public boolean SHOW_QUICK_DOC_ON_MOUSE_OVER_ELEMENT = false;
public long QUICK_DOC_ON_MOUSE_OVER_DELAY_MS = 500;
public boolean IS_CARET_BLINKING = true;
public int CARET_BLINKING_PERIOD = 500;
public boolean IS_RIGHT_MARGIN_SHOWN = true;
@@ -369,6 +371,28 @@ public class EditorSettingsExternalizable implements NamedJDOMExternalizable, Ex
myOptions.STRIP_TRAILING_SPACES = stripTrailingSpaces;
}
public boolean isShowQuickDocOnMouseOverElement() {
return myOptions.SHOW_QUICK_DOC_ON_MOUSE_OVER_ELEMENT;
}
public void setShowQuickDocOnMouseOverElement(boolean show) {
myOptions.SHOW_QUICK_DOC_ON_MOUSE_OVER_ELEMENT = show;
}
public long getQuickDocOnMouseOverElementDelayMillis() {
return myOptions.QUICK_DOC_ON_MOUSE_OVER_DELAY_MS;
}
public void setQuickDocOnMouseOverElementDelayMillis(long delay) throws IllegalArgumentException {
if (delay <= 0) {
throw new IllegalArgumentException(String.format(
"Non-positive delay for the 'show quick doc on mouse over element' value detected! Expected positive value but got %d",
delay
));
}
myOptions.QUICK_DOC_ON_MOUSE_OVER_DELAY_MS = delay;
}
public boolean isRefrainFromScrolling() {
return myOptions.REFRAIN_FROM_SCROLLING;
}
@@ -764,6 +764,9 @@
serviceImplementation="com.intellij.ide.todo.TodoConfiguration"/>
<indexPatternProvider implementation="com.intellij.ide.todo.TodoIndexPatternProvider"/>
<applicationService serviceImplementation="com.intellij.codeInsight.documentation.QuickDocOnMouseOverManager"/>
<postStartupActivity implementation="com.intellij.codeInsight.documentation.QuickDocOnMouseOverStartupActivity"/>
<hectorComponentProvider implementation="com.intellij.codeInsight.daemon.PowerSaveHectorProvider"/>
<copyPastePostProcessor implementation="com.intellij.codeInsight.editorActions.CopyPasteIndentProcessor"/>