inline documentation.v2.ctrl.mouse key

GitOrigin-RevId: 8c256b53e99693af3b211f8d4f70f2f09b885f7c
This commit is contained in:
Daniil Ovchinnikov
2023-02-03 16:34:15 +01:00
committed by intellij-monorepo-bot
parent 6c2873d4a1
commit d3bd081feb
5 changed files with 2 additions and 665 deletions

View File

@@ -1,7 +1,6 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight package com.intellij.codeInsight
import com.intellij.codeInsight.navigation.CtrlMouseHandler
import com.intellij.codeInsight.navigation.CtrlMouseHandler2 import com.intellij.codeInsight.navigation.CtrlMouseHandler2
import com.intellij.ide.highlighter.JavaFileType import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.components.service import com.intellij.openapi.components.service
@@ -11,37 +10,16 @@ import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.openapi.editor.impl.AbstractEditorTest import com.intellij.openapi.editor.impl.AbstractEditorTest
import com.intellij.openapi.editor.markup.RangeHighlighter import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.progress.timeoutRunBlocking import com.intellij.openapi.progress.timeoutRunBlocking
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.util.TestTimeOut
import com.intellij.util.ui.UIUtil import com.intellij.util.ui.UIUtil
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.LockSupport
import java.util.stream.Collectors import java.util.stream.Collectors
import java.util.stream.Stream import java.util.stream.Stream
class CtrlMouseHandlerTest : AbstractEditorTest() { class CtrlMouseHandlerTest : AbstractEditorTest() {
fun testHighlighterDisappearsOnMouseMovingAway() {
if (Registry.`is`("documentation.v2.ctrl.mouse")) {
return
}
init("class A {}", JavaFileType.INSTANCE)
val mouse = mouse()
mouse.ctrl().moveTo(0, 6)
waitForHighlighting()
assertHighlighted(6, 7)
mouse.moveTo(0, 0)
waitForHighlighting()
assertHighlighted()
}
fun `test highlighter disappears on mouse moving away`() { fun `test highlighter disappears on mouse moving away`() {
if (!Registry.`is`("documentation.v2.ctrl.mouse")) {
return
}
init("class A {}", JavaFileType.INSTANCE) init("class A {}", JavaFileType.INSTANCE)
val mouse = mouse() val mouse = mouse()
val handler = project.service<CtrlMouseHandler2>() val handler = project.service<CtrlMouseHandler2>()
@@ -76,18 +54,6 @@ class CtrlMouseHandlerTest : AbstractEditorTest() {
.collect(Collectors.toList()) .collect(Collectors.toList())
} }
private fun waitForHighlighting() {
PsiDocumentManager.getInstance(project).commitAllDocuments()
UIUtil.dispatchAllInvocationEvents()
val handler = project.getService(CtrlMouseHandler::class.java)
val t = TestTimeOut.setTimeout(1, TimeUnit.MINUTES)
while (handler.isCalculationInProgress) {
if (t.timedOut()) throw RuntimeException("Timed out waiting for CtrlMouseHandler")
LockSupport.parkNanos(10_000_000)
UIUtil.dispatchAllInvocationEvents()
}
}
private fun Job.joinPumping() { private fun Job.joinPumping() {
PsiDocumentManager.getInstance(project).commitAllDocuments() PsiDocumentManager.getInstance(project).commitAllDocuments()
UIUtil.dispatchAllInvocationEvents() UIUtil.dispatchAllInvocationEvents()

View File

@@ -1,80 +1,18 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.navigation; package com.intellij.codeInsight.navigation;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.documentation.DocumentationManager;
import com.intellij.codeInsight.documentation.DocumentationManagerProtocol;
import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.hint.HintManagerImpl;
import com.intellij.codeInsight.hint.HintUtil;
import com.intellij.injected.editor.EditorWindow;
import com.intellij.internal.statistic.service.fus.collectors.UIEventLogger;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.MouseShortcut;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable;
import com.intellij.openapi.editor.markup.HighlighterLayer;
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.fileEditor.FileEditorManagerEvent;
import com.intellij.openapi.fileEditor.FileEditorManagerListener;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.ui.LightweightHint;
import com.intellij.ui.ScreenUtil;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.util.Consumer;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly; import org.jetbrains.annotations.TestOnly;
import org.jetbrains.concurrency.CancellablePromise;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import static com.intellij.openapi.actionSystem.IdeActions.ACTION_GOTO_DECLARATION; import static com.intellij.openapi.actionSystem.IdeActions.ACTION_GOTO_DECLARATION;
@@ -86,571 +24,11 @@ import static com.intellij.openapi.actionSystem.IdeActions.ACTION_GOTO_DECLARATI
public final class CtrlMouseHandler { public final class CtrlMouseHandler {
static final Logger LOG = Logger.getInstance(CtrlMouseHandler.class); static final Logger LOG = Logger.getInstance(CtrlMouseHandler.class);
private final Project myProject;
private HighlightersSet myHighlighter;
@JdkConstants.InputEventMask private int myStoredModifiers;
private TooltipProvider myTooltipProvider;
@Nullable private Point myPrevMouseLocation;
private LightweightHint myHint;
private final KeyListener myEditorKeyListener = new KeyAdapter() {
@Override
public void keyPressed(final KeyEvent e) {
handleKey(e);
}
@Override
public void keyReleased(final KeyEvent e) {
handleKey(e);
}
private void handleKey(final KeyEvent e) {
int modifiers = e.getModifiers();
if (modifiers == myStoredModifiers) {
return;
}
CtrlMouseAction action = getCtrlMouseAction(modifiers);
if (action == null) {
disposeHighlighter();
cancelPreviousTooltip();
}
else {
TooltipProvider tooltipProvider = myTooltipProvider;
if (tooltipProvider != null) {
if (action != tooltipProvider.getAction()) {
disposeHighlighter();
}
myStoredModifiers = modifiers;
cancelPreviousTooltip();
myTooltipProvider = new TooltipProvider(tooltipProvider, action);
myTooltipProvider.execute();
}
}
}
};
private final VisibleAreaListener myVisibleAreaListener = __ -> {
disposeHighlighter();
cancelPreviousTooltip();
};
private final EditorMouseListener myEditorMouseAdapter = new EditorMouseListener() {
@Override
public void mouseReleased(@NotNull EditorMouseEvent e) {
disposeHighlighter();
cancelPreviousTooltip();
}
};
private final EditorMouseMotionListener myEditorMouseMotionListener = new EditorMouseMotionListener() {
@Override
public void mouseMoved(@NotNull final EditorMouseEvent e) {
if (e.isConsumed() || !myProject.isInitialized() || myProject.isDisposed()) {
return;
}
MouseEvent mouseEvent = e.getMouseEvent();
Point prevLocation = myPrevMouseLocation;
myPrevMouseLocation = mouseEvent.getLocationOnScreen();
if (isMouseOverTooltip(mouseEvent.getLocationOnScreen())
|| ScreenUtil.isMovementTowards(prevLocation, mouseEvent.getLocationOnScreen(), getHintBounds())) {
return;
}
cancelPreviousTooltip();
myStoredModifiers = mouseEvent.getModifiers();
CtrlMouseAction ctrlMouseAction = getCtrlMouseAction(myStoredModifiers);
if (ctrlMouseAction == null || e.getArea() != EditorMouseEventArea.EDITING_AREA) {
disposeHighlighter();
return;
}
Editor editor = e.getEditor();
if (!(editor instanceof EditorEx) || editor.getProject() != null && editor.getProject() != myProject) return;
if (!e.isOverText()) {
disposeHighlighter();
return;
}
myTooltipProvider = new TooltipProvider((EditorEx)editor, e.getLogicalPosition(), ctrlMouseAction);
myTooltipProvider.execute();
}
};
public CtrlMouseHandler(@NotNull Project project) {
myProject = project;
if (Registry.is("documentation.v2.ctrl.mouse")) {
return;
}
StartupManager.getInstance(project).runAfterOpened(() -> {
EditorEventMulticaster eventMulticaster = EditorFactory.getInstance().getEventMulticaster();
eventMulticaster.addEditorMouseListener(myEditorMouseAdapter, project);
eventMulticaster.addEditorMouseMotionListener(myEditorMouseMotionListener, project);
eventMulticaster.addCaretListener(new CaretListener() {
@Override
public void caretPositionChanged(@NotNull CaretEvent e) {
if (myHint != null) {
DocumentationManager.getInstance(myProject).updateToolwindowContext();
}
}
}, project);
});
project.getMessageBus().connect().subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener(){
@Override
public void selectionChanged(@NotNull FileEditorManagerEvent event) {
disposeHighlighter();
cancelPreviousTooltip();
}
});
}
private void cancelPreviousTooltip() {
if (myTooltipProvider != null) {
myTooltipProvider.dispose();
myTooltipProvider = null;
}
}
private boolean isMouseOverTooltip(@NotNull Point mouseLocationOnScreen) {
Rectangle bounds = getHintBounds();
return bounds != null && bounds.contains(mouseLocationOnScreen);
}
@Nullable
private Rectangle getHintBounds() {
LightweightHint hint = myHint;
if (hint == null) {
return null;
}
JComponent hintComponent = hint.getComponent();
if (!hintComponent.isShowing()) {
return null;
}
return new Rectangle(hintComponent.getLocationOnScreen(), hintComponent.getSize());
}
private static @Nullable CtrlMouseAction getCtrlMouseAction(@JdkConstants.InputEventMask int modifiers) {
if (modifiers == 0) {
return null;
}
KeymapManager keymapManager = KeymapManager.getInstance();
if (keymapManager == null) {
return null;
}
MouseShortcut shortcut = new MouseShortcut(MouseEvent.BUTTON1, modifiers, 1);
List<String> actionIds = keymapManager.getActiveKeymap().getActionIds(shortcut);
return ContainerUtil.getOnlyItem(ContainerUtil.mapNotNull(actionIds, CtrlMouseHandler::getCtrlMouseAction));
}
private static @Nullable CtrlMouseAction getCtrlMouseAction(@NotNull String actionId) { private static @Nullable CtrlMouseAction getCtrlMouseAction(@NotNull String actionId) {
AnAction action = ActionManager.getInstance().getAction(actionId); AnAction action = ActionManager.getInstance().getAction(actionId);
return action instanceof CtrlMouseAction ? (CtrlMouseAction)action : null; return action instanceof CtrlMouseAction ? (CtrlMouseAction)action : null;
} }
private static boolean areSimilar(@NotNull CtrlMouseInfo info1, @NotNull CtrlMouseInfo info2) {
return info1.getRanges().equals(info2.getRanges());
}
private static boolean isValidAndRangesAreCorrect(@NotNull CtrlMouseInfo info, @NotNull Document document) {
if (!info.isValid()) {
return false;
}
List<TextRange> ranges = info.getRanges();
final TextRange docRange = new TextRange(0, document.getTextLength());
for (TextRange range : ranges) {
if (!docRange.contains(range)) return false;
}
return true;
}
private static void showDumbModeNotification(@NotNull Project project) {
DumbService.getInstance(project).showDumbModeNotification(
CodeInsightBundle.message("notification.element.information.is.not.available.during.index.update"));
}
private void disposeHighlighter() {
HighlightersSet highlighter = myHighlighter;
if (highlighter != null) {
myHighlighter = null;
highlighter.uninstall();
HintManager.getInstance().hideAllHints();
}
}
private void updateText(@NotNull String updatedText,
@NotNull Consumer<? super String> newTextConsumer,
@NotNull LightweightHint hint,
@NotNull Editor editor) {
UIUtil.invokeLaterIfNeeded(() -> {
// There is a possible case that quick doc control width is changed, e.g. it contained text
// like 'public final class String implements java.io.Serializable, java.lang.Comparable<java.lang.String>' and
// new text replaces fully-qualified class names by hyperlinks with short name.
// That's why we might need to update the control size. We assume that the hint component is located at the
// layered pane, so, the algorithm is to find an ancestor layered pane and apply new size for the target component.
JComponent component = hint.getComponent();
Dimension oldSize = component.getPreferredSize();
newTextConsumer.consume(updatedText);
Dimension newSize = component.getPreferredSize();
if (newSize.width == oldSize.width) {
return;
}
component.setPreferredSize(new Dimension(newSize.width, newSize.height));
// We're assuming here that there are two possible hint representation modes: popup and layered pane.
if (hint.isRealPopup()) {
TooltipProvider tooltipProvider = myTooltipProvider;
if (tooltipProvider != null) {
// There is a possible case that 'raw' control was rather wide but the 'rich' one is narrower. That's why we try to
// re-show the hint here. Benefits: there is a possible case that we'll be able to show nice layered pane-based balloon;
// the popup will be re-positioned according to the new width.
hint.hide();
tooltipProvider.showHint(new LightweightHint(component), editor);
}
else {
component.setPreferredSize(new Dimension(newSize.width, oldSize.height));
hint.pack();
}
return;
}
Container topLevelLayeredPaneChild = null;
boolean adjustBounds = false;
for (Container current = component.getParent(); current != null; current = current.getParent()) {
if (current instanceof JLayeredPane) {
adjustBounds = true;
break;
}
else {
topLevelLayeredPaneChild = current;
}
}
if (adjustBounds && topLevelLayeredPaneChild != null) {
Rectangle bounds = topLevelLayeredPaneChild.getBounds();
topLevelLayeredPaneChild.setBounds(bounds.x, bounds.y, bounds.width + newSize.width - oldSize.width, bounds.height);
}
});
}
private final class TooltipProvider {
private final @NotNull EditorEx myHostEditor;
private final int myHostOffset;
private final @NotNull CtrlMouseAction myAction;
private boolean myDisposed;
private CancellablePromise<?> myExecutionProgress;
TooltipProvider(@NotNull EditorEx hostEditor, @NotNull LogicalPosition hostPos, @NotNull CtrlMouseAction action) {
myHostEditor = hostEditor;
myHostOffset = hostEditor.logicalPositionToOffset(hostPos);
myAction = action;
}
TooltipProvider(@NotNull TooltipProvider source, @NotNull CtrlMouseAction action) {
myHostEditor = source.myHostEditor;
myHostOffset = source.myHostOffset;
myAction = action;
}
void dispose() {
myDisposed = true;
if (myExecutionProgress != null) {
myExecutionProgress.cancel();
}
}
@NotNull CtrlMouseAction getAction() {
return myAction;
}
void execute() {
if (PsiDocumentManager.getInstance(myProject).getPsiFile(myHostEditor.getDocument()) == null) return;
int selStart = myHostEditor.getSelectionModel().getSelectionStart();
int selEnd = myHostEditor.getSelectionModel().getSelectionEnd();
if (myHostOffset >= selStart && myHostOffset < selEnd) {
disposeHighlighter();
return;
}
myExecutionProgress = ReadAction
.nonBlocking(() -> doExecute())
.withDocumentsCommitted(myProject)
.expireWhen(() -> isTaskOutdated(myHostEditor))
.finishOnUiThread(ModalityState.defaultModalityState(), Runnable::run)
.submit(AppExecutorUtil.getAppExecutorService());
}
private Runnable createDisposalContinuation() {
return CtrlMouseHandler.this::disposeHighlighter;
}
@NotNull
private Runnable doExecute() {
EditorEx editor = getPossiblyInjectedEditor();
int offset = getOffset(editor);
PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(editor.getDocument());
if (file == null) return createDisposalContinuation();
final CtrlMouseInfo info;
final CtrlMouseDocInfo docInfo;
try {
info = myAction.getCtrlMouseInfo(editor, file, offset);
if (info == null) return createDisposalContinuation();
docInfo = info.getDocInfo();
}
catch (IndexNotReadyException e) {
showDumbModeNotification(myProject);
return createDisposalContinuation();
}
LOG.debug("Obtained info about element under cursor");
return () -> addHighlighterAndShowHint(info, docInfo, editor);
}
@NotNull
private EditorEx getPossiblyInjectedEditor() {
final Document document = myHostEditor.getDocument();
if (PsiDocumentManager.getInstance(myProject).isCommitted(document)) {
PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(document);
return (EditorEx)InjectedLanguageUtil.getEditorForInjectedLanguageNoCommit(myHostEditor, psiFile, myHostOffset);
}
return myHostEditor;
}
private boolean isTaskOutdated(@NotNull Editor editor) {
return myDisposed || myProject.isDisposed() || editor.isDisposed() ||
!ApplicationManager.getApplication().isUnitTestMode() && !UIUtil.isShowing(editor.getContentComponent());
}
private int getOffset(@NotNull Editor editor) {
return editor instanceof EditorWindow ? ((EditorWindow)editor).getDocument().hostToInjected(myHostOffset) : myHostOffset;
}
private void addHighlighterAndShowHint(@NotNull CtrlMouseInfo info, @NotNull CtrlMouseDocInfo docInfo, @NotNull EditorEx editor) {
if (myDisposed || editor.isDisposed()) return;
if (myHighlighter != null) {
if (!areSimilar(info, myHighlighter.getStoredInfo())) {
disposeHighlighter();
}
else {
// highlighter already set
if (info.isNavigatable()) {
editor.setCustomCursor(CtrlMouseHandler.class, Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
return;
}
}
if (!isValidAndRangesAreCorrect(info, editor.getDocument()) || !info.isNavigatable() && docInfo.text == null) {
return;
}
boolean highlighterOnly = EditorSettingsExternalizable.getInstance().isShowQuickDocOnMouseOverElement() &&
DocumentationManager.getInstance(myProject).getDocInfoHint() != null;
myHighlighter = installHighlighterSet(info, editor, highlighterOnly);
if (highlighterOnly || docInfo.text == null) return;
UIEventLogger.QuickNavigateInfoPopupShown.log(myProject, docInfo.context == null ? null : docInfo.context.getLanguage());
HyperlinkListener hyperlinkListener = docInfo.docProvider == null || docInfo.context == null
? null
: new QuickDocHyperlinkListener(docInfo.docProvider, docInfo.context);
Ref<Consumer<? super String>> newTextConsumerRef = new Ref<>();
JComponent component = HintUtil.createInformationLabel(docInfo.text, hyperlinkListener, null, newTextConsumerRef);
component.setBorder(JBUI.Borders.empty(6, 6, 5, 6));
final LightweightHint hint = new LightweightHint(wrapInScrollPaneIfNeeded(component, editor));
myHint = hint;
hint.addHintListener(__ -> myHint = null);
showHint(hint, editor);
Consumer<? super String> newTextConsumer = newTextConsumerRef.get();
if (newTextConsumer != null) {
updateOnPsiChanges(hint, info, newTextConsumer, docInfo.text, editor);
}
}
@NotNull
private JComponent wrapInScrollPaneIfNeeded(@NotNull JComponent component, @NotNull Editor editor) {
if (!ApplicationManager.getApplication().isHeadlessEnvironment()) {
Dimension preferredSize = component.getPreferredSize();
Dimension maxSize = getMaxPopupSize(editor);
if (preferredSize.width > maxSize.width || preferredSize.height > maxSize.height) {
// We expect documentation providers to exercise good judgement in limiting the displayed information,
// but in any case, we don't want the hint to cover the whole screen, so we also implement certain limiting here.
JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(component, true);
scrollPane.setPreferredSize(new Dimension(Math.min(preferredSize.width, maxSize.width),
Math.min(preferredSize.height, maxSize.height)));
return scrollPane;
}
}
return component;
}
@NotNull
private Dimension getMaxPopupSize(@NotNull Editor editor) {
Rectangle rectangle = ScreenUtil.getScreenRectangle(editor.getContentComponent());
return new Dimension((int)(0.9 * Math.max(640, rectangle.width)), (int)(0.33 * Math.max(480, rectangle.height)));
}
private void updateOnPsiChanges(@NotNull LightweightHint hint,
@NotNull CtrlMouseInfo info,
@NotNull Consumer<? super String> textConsumer,
@NotNull String oldText,
@NotNull Editor editor) {
if (!hint.isVisible()) return;
Disposable hintDisposable = Disposer.newDisposable("CtrlMouseHandler.TooltipProvider.updateOnPsiChanges");
hint.addHintListener(__ -> Disposer.dispose(hintDisposable));
myProject.getMessageBus().connect(hintDisposable).subscribe(PsiModificationTracker.TOPIC, () -> ReadAction
.nonBlocking(() -> {
try {
CtrlMouseDocInfo newDocInfo = info.getDocInfo();
return (Runnable)() -> {
if (newDocInfo.text != null && !oldText.equals(newDocInfo.text)) {
updateText(newDocInfo.text, textConsumer, hint, editor);
}
};
}
catch (IndexNotReadyException e) {
showDumbModeNotification(myProject);
return createDisposalContinuation();
}
})
.finishOnUiThread(ModalityState.defaultModalityState(), Runnable::run)
.withDocumentsCommitted(myProject)
.expireWith(hintDisposable)
.expireWhen(() -> !isValidAndRangesAreCorrect(info, editor.getDocument()))
.coalesceBy(CtrlMouseHandler.class, hint)
.submit(AppExecutorUtil.getAppExecutorService()));
}
public void showHint(@NotNull LightweightHint hint, @NotNull Editor editor) {
if (ApplicationManager.getApplication().isUnitTestMode() || editor.isDisposed()) return;
final HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl();
short constraint = HintManager.ABOVE;
LogicalPosition position = editor.offsetToLogicalPosition(getOffset(editor));
Point p = HintManagerImpl.getHintPosition(hint, editor, position, constraint);
if (p.y - hint.getComponent().getPreferredSize().height < 0) {
constraint = HintManager.UNDER;
p = HintManagerImpl.getHintPosition(hint, editor, position, constraint);
}
hintManager.showEditorHint(hint, editor, p,
HintManager.HIDE_BY_ANY_KEY | HintManager.HIDE_BY_TEXT_CHANGE | HintManager.HIDE_BY_SCROLLING,
0, false, HintManagerImpl.createHintHint(editor, p, hint, constraint).setContentActive(false));
}
}
@NotNull
private HighlightersSet installHighlighterSet(@NotNull CtrlMouseInfo info, @NotNull EditorEx editor, boolean highlighterOnly) {
editor.getContentComponent().addKeyListener(myEditorKeyListener);
editor.getScrollingModel().addVisibleAreaListener(myVisibleAreaListener);
if (info.isNavigatable()) {
editor.setCustomCursor(CtrlMouseHandler.class, Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
List<RangeHighlighter> highlighters = new ArrayList<>();
if (!highlighterOnly || info.isNavigatable()) {
TextAttributes attributes = info.isNavigatable()
? EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.REFERENCE_HYPERLINK_COLOR)
: new TextAttributes(null, HintUtil.getInformationColor(), null, null, Font.PLAIN);
for (TextRange range : info.getRanges()) {
TextAttributes attr = NavigationUtil.patchAttributesColor(attributes, range, editor);
final RangeHighlighter highlighter = editor.getMarkupModel().addRangeHighlighter(range.getStartOffset(), range.getEndOffset(),
HighlighterLayer.HYPERLINK,
attr,
HighlighterTargetArea.EXACT_RANGE);
highlighters.add(highlighter);
}
}
return new HighlightersSet(highlighters, editor, info);
}
@TestOnly
public boolean isCalculationInProgress() {
TooltipProvider provider = myTooltipProvider;
if (provider == null) return false;
Future<?> progress = provider.myExecutionProgress;
if (progress == null) return false;
return !progress.isDone();
}
private final class HighlightersSet {
@NotNull private final List<? extends RangeHighlighter> myHighlighters;
@NotNull private final EditorEx myHighlighterView;
@NotNull private final CtrlMouseInfo myStoredInfo;
private HighlightersSet(@NotNull List<? extends RangeHighlighter> highlighters,
@NotNull EditorEx highlighterView,
@NotNull CtrlMouseInfo storedInfo) {
myHighlighters = highlighters;
myHighlighterView = highlighterView;
myStoredInfo = storedInfo;
}
public void uninstall() {
for (RangeHighlighter highlighter : myHighlighters) {
highlighter.dispose();
}
myHighlighterView.setCustomCursor(CtrlMouseHandler.class, null);
myHighlighterView.getContentComponent().removeKeyListener(myEditorKeyListener);
myHighlighterView.getScrollingModel().removeVisibleAreaListener(myVisibleAreaListener);
}
@NotNull
CtrlMouseInfo getStoredInfo() {
return myStoredInfo;
}
}
private final class QuickDocHyperlinkListener implements HyperlinkListener {
@NotNull private final DocumentationProvider myProvider;
@NotNull private final PsiElement myContext;
QuickDocHyperlinkListener(@NotNull DocumentationProvider provider, @NotNull PsiElement context) {
myProvider = provider;
myContext = context;
}
@Override
public void hyperlinkUpdate(@NotNull HyperlinkEvent e) {
if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) {
return;
}
String description = e.getDescription();
if (StringUtil.isEmpty(description) || !description.startsWith(DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL)) {
return;
}
String elementName = e.getDescription().substring(DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL.length());
DumbService.getInstance(myProject).withAlternativeResolveEnabled(() -> {
PsiElement targetElement = myProvider.getDocumentationElementForLink(PsiManager.getInstance(myProject), elementName, myContext);
if (targetElement != null) {
LightweightHint hint = myHint;
if (hint != null) {
hint.hide(true);
}
DocumentationManager.getInstance(myProject).showJavaDocInfo(targetElement, myContext, null);
}
});
}
}
@TestOnly @TestOnly
public static @Nullable String getInfo(PsiElement element, PsiElement atPointer) { public static @Nullable String getInfo(PsiElement element, PsiElement atPointer) {
return SingleTargetElementInfo.generateInfo(element, atPointer, true).text; return SingleTargetElementInfo.generateInfo(element, atPointer, true).text;

View File

@@ -40,7 +40,6 @@ import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.NlsContexts.HintText import com.intellij.openapi.util.NlsContexts.HintText
import com.intellij.openapi.util.TextRange import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.registry.Registry
import com.intellij.platform.documentation.DocumentationTarget import com.intellij.platform.documentation.DocumentationTarget
import com.intellij.ui.LightweightHint import com.intellij.ui.LightweightHint
import com.intellij.ui.ScreenUtil import com.intellij.ui.ScreenUtil
@@ -64,9 +63,6 @@ import kotlin.math.min
internal class InitCtrlMouseHandlerActivity : ProjectActivity { internal class InitCtrlMouseHandlerActivity : ProjectActivity {
override suspend fun execute(project: Project) { override suspend fun execute(project: Project) {
if (!Registry.`is`("documentation.v2.ctrl.mouse", true)) {
return
}
project.service<CtrlMouseHandler2>() project.service<CtrlMouseHandler2>()
} }
} }

View File

@@ -99,7 +99,6 @@
<actionConfigurationCustomizer implementation="com.intellij.ide.actionMacro.ActionMacroManager$MyActionTuner"/> <actionConfigurationCustomizer implementation="com.intellij.ide.actionMacro.ActionMacroManager$MyActionTuner"/>
<projectService serviceImplementation="com.intellij.codeInsight.daemon.impl.DaemonListeners" preload="await"/> <projectService serviceImplementation="com.intellij.codeInsight.daemon.impl.DaemonListeners" preload="await"/>
<projectService serviceImplementation="com.intellij.codeInsight.navigation.CtrlMouseHandler" preload="await"/>
<postStartupActivity implementation="com.intellij.codeInsight.navigation.InitCtrlMouseHandlerActivity"/> <postStartupActivity implementation="com.intellij.codeInsight.navigation.InitCtrlMouseHandlerActivity"/>
<projectService serviceImplementation="com.intellij.facet.impl.pointers.FacetPointersManagerImpl" <projectService serviceImplementation="com.intellij.facet.impl.pointers.FacetPointersManagerImpl"

View File

@@ -913,8 +913,6 @@ documentation.v2.tw.focus.invocation.timeout.description=How many milliseconds t
so the action invocation moves focus to the TW without the need for this timeout. so the action invocation moves focus to the TW without the need for this timeout.
documentation.v2.component=true documentation.v2.component=true
documentation.v2.component.description=Turns on DocumentationTarget-based implementation for explicitly requested documentation component. documentation.v2.component.description=Turns on DocumentationTarget-based implementation for explicitly requested documentation component.
documentation.v2.ctrl.mouse=true
documentation.v2.ctrl.mouse.description=Turns on DocumentationTarget-based implementation of ctrl mouse handler.
ide.completion.show.lower.case.classes=false ide.completion.show.lower.case.classes=false
ide.completion.show.lower.case.classes.description=Show non-imported classes starting with a lowercase letter in basic completion ide.completion.show.lower.case.classes.description=Show non-imported classes starting with a lowercase letter in basic completion