From 71cd34003914edbd5c888eeddb7c3fea7f81fd2f Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Fri, 29 May 2020 11:46:18 +0300 Subject: [PATCH] IDEA-237977 Rendered JavaDoc not selectable GitOrigin-RevId: e9250377047c1fe84d5bd8c3e5c72f713fec0f10 --- .../editor/event/EditorMouseEvent.java | 24 +------ .../messages/CodeInsightBundle.properties | 1 + .../render/DocRenderMouseEventBridge.java | 65 ++++++++++++++----- .../documentation/render/DocRenderer.java | 45 +++++++++++-- .../openapi/editor/impl/EditorImpl.java | 2 +- 5 files changed, 91 insertions(+), 46 deletions(-) diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/event/EditorMouseEvent.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/event/EditorMouseEvent.java index 69de9b0e26ad..6d346a2020fb 100644 --- a/platform/editor-ui-api/src/com/intellij/openapi/editor/event/EditorMouseEvent.java +++ b/platform/editor-ui-api/src/com/intellij/openapi/editor/event/EditorMouseEvent.java @@ -30,9 +30,8 @@ import java.util.EventObject; * dispatching, and is not guaranteed to be actual by the time some event listener receives the event (if previously called listener has * modified the editor state). *

- * The additional information is currently provided for the following types of events: {@link MouseEvent#MOUSE_MOVED MOUSE_MOVED}, - * {@link MouseEvent#MOUSE_PRESSED MOUSE_PRESSED}, {@link MouseEvent#MOUSE_RELEASED MOUSE_RELEASED} and - * {@link MouseEvent#MOUSE_CLICKED MOUSE_CLICKED}. For other events, return values of corresponding event getters are unspecified. + * The additional information is not provided for {@link MouseEvent#MOUSE_ENTERED MOUSE_ENTERED} and + * {@link MouseEvent#MOUSE_EXITED MOUSE_EXITED} events. Return values of corresponding event getters are unspecified for those events. */ public class EditorMouseEvent extends EventObject { @NotNull @@ -88,52 +87,33 @@ public class EditorMouseEvent extends EventObject { return myEditorArea; } - /** - * See {@link EditorMouseEvent class documentation} with regard to the availability of this information. - */ public int getOffset() { return myOffset; } - /** - * See {@link EditorMouseEvent class documentation} with regard to the availability of this information. - */ public @NotNull LogicalPosition getLogicalPosition() { return myLogicalPosition; } - /** - * See {@link EditorMouseEvent class documentation} with regard to the availability of this information. - */ public @NotNull VisualPosition getVisualPosition() { return myVisualPosition; } /** * Returns {@code false} if mouse is below the last line of text, to the right of the last character on the line, or over an inlay. - * See {@link EditorMouseEvent class documentation} with regard to the availability of this information. */ public boolean isOverText() { return myIsOverText; } - /** - * See {@link EditorMouseEvent class documentation} with regard to the availability of this information. - */ public @Nullable FoldRegion getCollapsedFoldRegion() { return myCollapsedFoldRegion == null || !myCollapsedFoldRegion.isValid() ? null : myCollapsedFoldRegion; } - /** - * See {@link EditorMouseEvent class documentation} with regard to the availability of this information. - */ public @Nullable Inlay getInlay() { return myInlay == null || !myInlay.isValid() ? null : myInlay; } - /** - * See {@link EditorMouseEvent class documentation} with regard to the availability of this information. - */ public @Nullable GutterIconRenderer getGutterIconRenderer() { return myGutterIconRenderer; } diff --git a/platform/lang-api/resources/messages/CodeInsightBundle.properties b/platform/lang-api/resources/messages/CodeInsightBundle.properties index 9929f8878c9f..a2c6308e52f7 100644 --- a/platform/lang-api/resources/messages/CodeInsightBundle.properties +++ b/platform/lang-api/resources/messages/CodeInsightBundle.properties @@ -196,6 +196,7 @@ doc.render.dumb.mode.text=Cannot render documentation during indexing doc.render.loading.text=Loading... doc.render.not.available.text=Not available doc.render.gutter.icon.setting=Documentation comments in-place rendering +doc.render.copy.action.text=Copy Selection declaration.navigation.title=Choose Declaration declaration.navigation.nowhere.to.go=Cannot find declaration to go to template.shortcut.enter=Enter diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderMouseEventBridge.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderMouseEventBridge.java index 33d275c4d45b..1ed483feb387 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderMouseEventBridge.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderMouseEventBridge.java @@ -13,30 +13,39 @@ import org.jetbrains.annotations.Nullable; import sun.awt.AWTAccessor; import java.awt.*; +import java.awt.event.InputEvent; import java.awt.event.MouseEvent; class DocRenderMouseEventBridge implements EditorMouseListener, EditorMouseMotionListener { - private DocRenderer.EditorPane myCurrentPane; + private DocRenderer.EditorPane myMouseOverPane; + private DocRenderer.EditorPane myDragPane; @Override public void mouseMoved(@NotNull EditorMouseEvent event) { if (event.getArea() != EditorMouseEventArea.EDITING_AREA) return; - DocRenderer.EditorPane currentPane = redispatchEvent(event, MouseEvent.MOUSE_MOVED); + DocRenderer.EditorPane currentPane = redispatchEvent(event, MouseEvent.MOUSE_MOVED, null); if (currentPane == null) { restoreCursor(); } else { ((EditorEx)event.getEditor()).setCustomCursor(DocRenderMouseEventBridge.class, currentPane.getCursor()); - if (currentPane != myCurrentPane) { - if (myCurrentPane != null) { - dispatchMouseExitEvent(myCurrentPane); + if (currentPane != myMouseOverPane) { + if (myMouseOverPane != null) { + dispatchMouseExitEvent(myMouseOverPane); } - myCurrentPane = currentPane; + myMouseOverPane = currentPane; } } } + @Override + public void mouseDragged(@NotNull EditorMouseEvent e) { + if (e.getArea() != EditorMouseEventArea.EDITING_AREA) return; + + redispatchEvent(e, MouseEvent.MOUSE_DRAGGED, myDragPane); + } + @Override public void mouseExited(@NotNull EditorMouseEvent event) { if (event.getArea() != EditorMouseEventArea.EDITING_AREA) return; @@ -44,23 +53,42 @@ class DocRenderMouseEventBridge implements EditorMouseListener, EditorMouseMotio restoreCursor(); } + @Override + public void mousePressed(@NotNull EditorMouseEvent event) { + if (event.getArea() != EditorMouseEventArea.EDITING_AREA) return; + + myDragPane = redispatchEvent(event, MouseEvent.MOUSE_PRESSED, null); + } + + @Override + public void mouseReleased(@NotNull EditorMouseEvent event) { + if (event.getArea() != EditorMouseEventArea.EDITING_AREA) return; + + redispatchEvent(event, MouseEvent.MOUSE_RELEASED, null); + myDragPane = null; + } + @Override public void mouseClicked(@NotNull EditorMouseEvent event) { if (event.getArea() != EditorMouseEventArea.EDITING_AREA) return; - redispatchEvent(event, MouseEvent.MOUSE_CLICKED); + redispatchEvent(event, MouseEvent.MOUSE_CLICKED, null); } private void restoreCursor() { - if (myCurrentPane != null) { - dispatchMouseExitEvent(myCurrentPane); - ((EditorEx)myCurrentPane.getEditor()).setCustomCursor(DocRenderMouseEventBridge.class, null); - myCurrentPane = null; + if (myMouseOverPane != null) { + dispatchMouseExitEvent(myMouseOverPane); + ((EditorEx)myMouseOverPane.getEditor()).setCustomCursor(DocRenderMouseEventBridge.class, null); + myMouseOverPane = null; } } @Nullable - private static DocRenderer.EditorPane redispatchEvent(@NotNull EditorMouseEvent event, int eventId) { + private static DocRenderer.EditorPane redispatchEvent(@NotNull EditorMouseEvent event, + // we need a separately passed eventId because EditorImpl dispatches MOUSE_RELEASED + // events to both 'mouseReleased' and 'mouseClicked' methods in listeners + int eventId, + @Nullable DocRenderer.EditorPane targetPane) { MouseEvent mouseEvent = event.getMouseEvent(); Point mousePoint = mouseEvent.getPoint(); Inlay inlay = event.getInlay(); @@ -74,11 +102,14 @@ class DocRenderMouseEventBridge implements EditorMouseListener, EditorMouseMotio int y = mousePoint.y - inlayBounds.y - relativeBounds.y; if (x >= 0 && x < relativeBounds.width && y >= 0 && y < relativeBounds.height) { DocRenderer.EditorPane editorPane = ((DocRenderer)renderer).getRendererComponent(inlay, relativeBounds.width); - int button = mouseEvent.getButton(); - dispatchEvent(editorPane, new MouseEvent(editorPane, eventId, 0, 0, x, y, mouseEvent.getClickCount(), false, - // hack to process middle-button clicks (JEditorPane ignores them) - button == MouseEvent.BUTTON2 ? MouseEvent.BUTTON1 : button)); - return editorPane; + if (targetPane == null || targetPane == editorPane) { + int button = mouseEvent.getButton(); + int modifiers = eventId == MouseEvent.MOUSE_DRAGGED && button == MouseEvent.BUTTON1 ? InputEvent.BUTTON1_DOWN_MASK : 0; + dispatchEvent(editorPane, new MouseEvent(editorPane, eventId, 0, modifiers, x, y, mouseEvent.getClickCount(), false, + // hack to process middle-button clicks (JEditorPane ignores them) + button == MouseEvent.BUTTON2 ? MouseEvent.BUTTON1 : button)); + return editorPane; + } } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderer.java b/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderer.java index 24b81d0c2ff3..e031f1b38517 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderer.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/documentation/render/DocRenderer.java @@ -9,10 +9,7 @@ import com.intellij.icons.AllIcons; import com.intellij.ide.IdeEventQueue; import com.intellij.ide.ui.UISettings; import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.ActionGroup; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.openapi.actionSystem.IdeActions; -import com.intellij.openapi.actionSystem.MouseShortcut; +import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.colors.EditorColorsScheme; @@ -22,8 +19,10 @@ import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.markup.GutterIconRenderer; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; +import com.intellij.openapi.ide.CopyPasteManager; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; @@ -42,6 +41,7 @@ import com.intellij.util.ui.JBUI; import com.intellij.util.ui.StartupUiUtil; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.HyperlinkEvent; @@ -50,6 +50,7 @@ import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.ImageView; import javax.swing.text.html.StyleSheet; import java.awt.*; +import java.awt.datatransfer.StringSelection; import java.awt.event.MouseEvent; import java.awt.font.TextAttribute; import java.awt.image.ImageObserver; @@ -159,7 +160,12 @@ class DocRenderer implements EditorCustomElementRenderer { @Override public ActionGroup getContextMenuGroup(@NotNull Inlay inlay) { - return new DefaultActionGroup(myItem.createToggleAction(), new DocRenderItem.ChangeFontSize()); + DefaultActionGroup group = new DefaultActionGroup(); + group.add(new CopySelection()); + group.addSeparator(); + group.add(myItem.createToggleAction()); + group.add(new DocRenderItem.ChangeFontSize()); + return group; } private static int scale(int value) { @@ -200,6 +206,7 @@ class DocRenderer implements EditorCustomElementRenderer { clearCachedComponent(); myPane = new EditorPane(); myPane.setEditable(false); + myPane.getCaret().setSelectionVisible(true); myPane.putClientProperty("caretWidth", 0); // do not reserve space for caret (making content one pixel narrower than component) myPane.setEditorKit(createEditorKit(editor)); myPane.setBorder(JBUI.Borders.empty()); @@ -208,7 +215,10 @@ class DocRenderer implements EditorCustomElementRenderer { // disable kerning for now - laying out all fragments in a file with it takes too much time fontAttributes.put(TextAttribute.KERNING, 0); myPane.setFont(myPane.getFont().deriveFont(fontAttributes)); - myPane.setForeground(getTextColor(editor.getColorsScheme())); + Color textColor = getTextColor(editor.getColorsScheme()); + myPane.setForeground(textColor); + myPane.setSelectedTextColor(textColor); + myPane.setSelectionColor(editor.getSelectionModel().getTextAttributes().getBackgroundColor()); UIUtil.enableEagerSoftWrapping(myPane); String textToRender = myItem.textToRender; if (textToRender == null) { @@ -330,6 +340,10 @@ class DocRenderer implements EditorCustomElementRenderer { } } + @Nullable String getSelectedText() { + return myPane == null ? null : myPane.getSelectedText(); + } + private static EditorKit createEditorKit(@NotNull Editor editor) { HTMLEditorKit editorKit = new MyEditorKit(); editorKit.getStyleSheet().addStyleSheet(getStyleSheet(editor)); @@ -568,4 +582,23 @@ class DocRenderer implements EditorCustomElementRenderer { super.paint(scalingGraphics, a); } } + + private class CopySelection extends DumbAwareAction { + CopySelection() { + super(CodeInsightBundle.messagePointer("doc.render.copy.action.text"), AllIcons.Actions.Copy); + } + + @Override + public void update(@NotNull AnActionEvent e) { + e.getPresentation().setVisible(!StringUtil.isEmpty(getSelectedText())); + } + + @Override + public void actionPerformed(@NotNull AnActionEvent e) { + String text = getSelectedText(); + if (!StringUtil.isEmpty(text)) { + CopyPasteManager.getInstance().setContents(new StringSelection(text)); + } + } + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 4e3f6280934d..cc5ecc6683a9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -4284,7 +4284,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } validateMousePointer(e, null); ((TransactionGuardImpl)TransactionGuard.getInstance()).performUserActivity(() -> runMouseDraggedCommand(e)); - EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e)); + EditorMouseEvent event = createEditorMouseEvent(e); if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) { myGutterComponent.mouseDragged(e); }