From f6aa793f4d485992d966537e4777d67d8d0d7d2d Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Mon, 19 Feb 2024 20:59:23 +0400 Subject: [PATCH] tweak editor popup handlers for RemDev 1. Use groups instead of imperative popup handlers 2. Provide editor-local id for editor popup action 3. Encode EditorMouseEvent into that local id 4. Avoid breaking API changes, stick to `ContextMenuPopupHandler` Fixes GTW-7427 Git blame actions are missing GTW-6380 No context menu options in consoles inside LUXed windows GitOrigin-RevId: d9e109dac3268ab94e6bc64e4e156f8506872739 --- .../codeInsight/hints/PopupActions.kt | 9 +- .../hints/ToggleCompletionHintsAction.java | 10 +- .../tree/injected/EditorWindowImpl.java | 6 + .../messages/EditorBundle.properties | 1 - .../intellij/openapi/editor/ex/EditorEx.java | 5 + .../impl/EditorGutterComponentImpl.java | 117 +++++++++--------- .../openapi/editor/impl/EditorImpl.java | 42 ++++++- .../impl/EditorMousePopupActionGroup.java | 46 +++++++ 8 files changed, 171 insertions(+), 65 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMousePopupActionGroup.java diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/PopupActions.kt b/platform/lang-impl/src/com/intellij/codeInsight/hints/PopupActions.kt index 7a96b080d44b..6b0d982c984a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/PopupActions.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/PopupActions.kt @@ -22,6 +22,7 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehavior import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.impl.EditorImpl @@ -66,7 +67,9 @@ class ShowSettingsWithAddedPattern : AnAction() { } } -class ShowParameterHintsSettings : AnAction(), ActionRemoteBehaviorSpecification.Frontend { +class ShowParameterHintsSettings : AnAction(), ActionRemoteBehaviorSpecification { + + override fun getBehavior(): ActionRemoteBehavior = ActionRemoteBehavior.BackendOnly override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT @@ -263,7 +266,9 @@ private fun InlayParameterHintsProvider.hasDisabledOptionHintInfo(element: PsiEl } -class ToggleInlineHintsAction : AnAction(), ActionRemoteBehaviorSpecification.Frontend { +class ToggleInlineHintsAction : AnAction(), ActionRemoteBehaviorSpecification { + + override fun getBehavior(): ActionRemoteBehavior = ActionRemoteBehavior.BackendOnly override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hints/ToggleCompletionHintsAction.java b/platform/lang-impl/src/com/intellij/codeInsight/hints/ToggleCompletionHintsAction.java index 9a1c70b1098a..c3f469074d51 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hints/ToggleCompletionHintsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hints/ToggleCompletionHintsAction.java @@ -6,11 +6,19 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.ToggleAction; +import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehavior; import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification; import com.intellij.psi.PsiFile; import org.jetbrains.annotations.NotNull; -public final class ToggleCompletionHintsAction extends ToggleAction implements ActionRemoteBehaviorSpecification.Frontend { +public final class ToggleCompletionHintsAction extends ToggleAction implements ActionRemoteBehaviorSpecification { + + @NotNull + @Override + public ActionRemoteBehavior getBehavior() { + return ActionRemoteBehavior.BackendOnly; + } + @Override public @NotNull ActionUpdateThread getActionUpdateThread() { return ActionUpdateThread.BGT; diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/EditorWindowImpl.java b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/EditorWindowImpl.java index 5624d1bf030d..2ec8b732c041 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/EditorWindowImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/tree/injected/EditorWindowImpl.java @@ -10,6 +10,7 @@ import com.intellij.injected.editor.DocumentWindow; import com.intellij.injected.editor.EditorWindow; import com.intellij.injected.editor.MarkupModelWindow; import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.colors.EditorColorsManager; @@ -823,6 +824,11 @@ final class EditorWindowImpl extends UserDataHolderBase implements EditorWindow, myDelegate.installPopupHandler(popupHandler); } + @Override + public @Nullable ActionGroup getPopupActionGroup(@NotNull EditorMouseEvent event) { + return myDelegate.getPopupActionGroup(event); + } + @Override public void setCustomCursor(@NotNull Object requestor, @Nullable Cursor cursor) { myDelegate.setCustomCursor(requestor, cursor); diff --git a/platform/platform-api/resources/messages/EditorBundle.properties b/platform/platform-api/resources/messages/EditorBundle.properties index 64577b711996..b0672e5b136f 100644 --- a/platform/platform-api/resources/messages/EditorBundle.properties +++ b/platform/platform-api/resources/messages/EditorBundle.properties @@ -74,7 +74,6 @@ file.not.found=File not found: {0} guarded.block.modification.attempt.error.message=Unable to perform an action since it changes read-only fragments of the current document guarded.block.modification.attempt.error.title=Guarded Block Modification Attempt close.editor.annotations.action.name=Close Annotations -editor.annotations.action.group.name=Annotations move.cursor.command.name=Move Cursor paste.command.name=Paste move.selection.command.name=Move Selection diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java index b26b08af8fc4..659342dfdeaf 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/ex/EditorEx.java @@ -20,6 +20,7 @@ import com.intellij.ide.CutProvider; import com.intellij.ide.DeleteProvider; import com.intellij.ide.PasteProvider; import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.*; @@ -343,6 +344,10 @@ public interface EditorEx extends Editor { */ void uninstallPopupHandler(@NotNull EditorPopupHandler popupHandler); + default @Nullable ActionGroup getPopupActionGroup(@NotNull EditorMouseEvent event) { + return null; + } + /** * If {@code cursor} parameter value is not {@code null}, sets custom cursor to {@link #getContentComponent() editor's content component}, * otherwise restores default editor cursor management logic ({@code requestor} parameter value should be the same in both setting and diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java index 7da9e0216274..1e00b02186a0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorGutterComponentImpl.java @@ -34,6 +34,8 @@ import com.intellij.lang.Language; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.actionSystem.ex.ActionUtil; +import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehavior; +import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; @@ -43,6 +45,7 @@ import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorFontType; import com.intellij.openapi.editor.event.CaretEvent; import com.intellij.openapi.editor.event.CaretListener; +import com.intellij.openapi.editor.event.EditorMouseEvent; import com.intellij.openapi.editor.event.EditorMouseEventArea; import com.intellij.openapi.editor.ex.*; import com.intellij.openapi.editor.ex.util.EditorUIUtil; @@ -2458,11 +2461,17 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements updateSize(); } - private final class CloseAnnotationsAction extends DumbAwareAction { + private final class CloseAnnotationsAction extends DumbAwareAction implements ActionRemoteBehaviorSpecification { CloseAnnotationsAction() { super(EditorBundle.messagePointer("close.editor.annotations.action.name")); } + @NotNull + @Override + public ActionRemoteBehavior getBehavior() { + return ActionRemoteBehavior.BackendOnly; + } + @Override public void actionPerformed(@NotNull AnActionEvent e) { closeAllAnnotations(); @@ -2572,74 +2581,68 @@ final class EditorGutterComponentImpl extends EditorGutterComponentEx implements if (info != null) { logGutterIconClick(info.renderer); } - myLastActionableClick = new ClickInfo(logicalLineAtCursor, info == null ? point : info.iconCenterPosition); - final ActionManager actionManager = ActionManager.getInstance(); - if (myEditor.getMouseEventArea(e) == EditorMouseEventArea.ANNOTATIONS_AREA) { - final List addActions = getTextAnnotationPopupActions(logicalLineAtCursor); - if (!addActions.isEmpty()) { - e.consume(); - DefaultActionGroup actionGroup = DefaultActionGroup.createPopupGroup( - EditorBundle.messagePointer("editor.annotations.action.group.name")); - for (AnAction addAction : addActions) { - actionGroup.add(addAction); - } - JPopupMenu menu = actionManager.createActionPopupMenu(ActionPlaces.EDITOR_ANNOTATIONS_AREA_POPUP, actionGroup).getComponent(); - menu.show(this, e.getX(), e.getY()); - } + ClickInfo clickInfo = new ClickInfo(logicalLineAtCursor, info == null ? point : info.iconCenterPosition); + EditorMouseEventArea editorArea = myEditor.getMouseEventArea(e); + myLastActionableClick = clickInfo; + + AnAction rightButtonAction = info == null ? null : info.renderer.getRightButtonClickAction(); + if (rightButtonAction != null) { + e.consume(); + performAction(rightButtonAction, e, ActionPlaces.EDITOR_GUTTER_POPUP, myEditor.getDataContext(), info); + return; + } + EditorMouseEvent editorMouseEvent = new EditorMouseEvent( + myEditor, e, editorArea, 0, new LogicalPosition(logicalLineAtCursor, 0), + new VisualPosition(0, 0), true, null, null, null); + ActionGroup group = info != null ? info.renderer.getPopupMenuActions() : + getPopupActionGroup(editorMouseEvent); + if (group == null) { + // nothing + } + else if (!checkDumbAware(group)) { + notifyNotDumbAware(); } else { - if (info != null) { - AnAction rightButtonAction = info.renderer.getRightButtonClickAction(); - if (rightButtonAction != null) { - e.consume(); - performAction(rightButtonAction, e, ActionPlaces.EDITOR_GUTTER_POPUP, myEditor.getDataContext(), info); - } - else { - ActionGroup actionGroup = info.renderer.getPopupMenuActions(); - if (actionGroup != null) { - e.consume(); - if (checkDumbAware(actionGroup)) { - addLoadingIconForGutterMark(info); - actionManager.createActionPopupMenu(ActionPlaces.EDITOR_GUTTER_POPUP, actionGroup) - .getComponent() - .show(this, e.getX(), e.getY()); - } - else { - notifyNotDumbAware(); - } - } - } + if (info != null) addLoadingIconForGutterMark(info); + String place = editorArea == EditorMouseEventArea.ANNOTATIONS_AREA ? + ActionPlaces.EDITOR_ANNOTATIONS_AREA_POPUP : ActionPlaces.EDITOR_GUTTER_POPUP; + showGutterContextMenu(group, e, place); + } + } + + @Nullable ActionGroup getPopupActionGroup(@NotNull EditorMouseEvent event) { + List actions; + if (event.getArea() == EditorMouseEventArea.ANNOTATIONS_AREA) { + actions = getTextAnnotationPopupActions(event.getLogicalPosition().line); + } + else { + actions = new ArrayList<>(); + if (ExperimentalUI.isNewUI() && + event.getArea() == EditorMouseEventArea.LINE_NUMBERS_AREA && + getClientProperty("line.number.hover.icon.context.menu") instanceof ActionGroup g) { + actions.add(g); } - else { - List actions = new ArrayList<>(); - if (ExperimentalUI.isNewUI() && - myEditor.getMouseEventArea(e) == EditorMouseEventArea.LINE_NUMBERS_AREA && - getClientProperty("line.number.hover.icon.context.menu") instanceof ActionGroup g) { - actions.add(g); - } - if (myCustomGutterPopupGroup != null) { + if (myCustomGutterPopupGroup != null) { + actions.add(Separator.getInstance()); + actions.add(myCustomGutterPopupGroup); + } + else if (myShowDefaultGutterPopup) { + ActionGroup g = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_EDITOR_GUTTER); + if (g != null) { actions.add(Separator.getInstance()); - actions.add(myCustomGutterPopupGroup); - } - else if (myShowDefaultGutterPopup) { - ActionGroup g = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(IdeActions.GROUP_EDITOR_GUTTER); - if (g != null) { - actions.add(Separator.getInstance()); - actions.add(g); - } - } - if (!actions.isEmpty()) { - showGutterContextMenu(new DefaultActionGroup(actions), e); + actions.add(g); } } } + if (actions.isEmpty()) return null; + return new EditorMousePopupActionGroup(actions, event); } private static final String EDITOR_GUTTER_CONTEXT_MENU_KEY = "editor.gutter.context.menu"; - private void showGutterContextMenu(@NotNull ActionGroup group, MouseEvent e) { + private void showGutterContextMenu(@NotNull ActionGroup group, MouseEvent e, String place) { e.consume(); - ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.EDITOR_GUTTER_POPUP, group); + ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(place, group); putClientProperty(EDITOR_GUTTER_CONTEXT_MENU_KEY, popupMenu); popupMenu.getComponent().addPopupMenuListener(new PopupMenuListenerAdapter() { @Override 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 2b82e79425d3..d7258ac40d9c 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 @@ -2,6 +2,7 @@ package com.intellij.openapi.editor.impl; import com.intellij.application.options.EditorFontsConstants; +import com.intellij.codeWithMe.ClientId; import com.intellij.diagnostic.Dumpable; import com.intellij.ide.*; import com.intellij.ide.dnd.DnDManager; @@ -36,7 +37,6 @@ import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter; import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.editor.highlighter.HighlighterClient; import com.intellij.openapi.editor.impl.event.MarkupModelListener; -import com.intellij.openapi.editor.impl.stickyLines.StickyLinesPanel; import com.intellij.openapi.editor.impl.stickyLines.StickyLinesManager; import com.intellij.openapi.editor.impl.stickyLines.StickyLinesPanel; import com.intellij.openapi.editor.impl.view.EditorView; @@ -858,6 +858,22 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myPopupHandlers.remove(popupHandler); } + @Override + public @Nullable ActionGroup getPopupActionGroup(@NotNull EditorMouseEvent event) { + if (event.getArea() == EditorMouseEventArea.EDITING_AREA) { + for (int i = myPopupHandlers.size() - 1; i >= 0; i--) { + EditorPopupHandler handler = myPopupHandlers.get(i); + ActionGroup group = handler instanceof ContextMenuPopupHandler o ? o.getActionGroup(event) : null; + if (group instanceof DefaultActionGroup o && o.getChildrenCount() == 0) return group; + if (group != null) return new EditorMousePopupActionGroup(group, event); + } + return null; + } + else { + return myGutterComponent.getPopupActionGroup(event); + } + } + private @Nullable Cursor getCustomCursor() { return ContainerUtil.getFirstItem(myCustomCursors.values()); } @@ -5320,9 +5336,27 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } private void invokePopupIfNeeded(@NotNull EditorMouseEvent event) { + if (myPopupHandlers.isEmpty()) return; + if (event.getArea() == EditorMouseEventArea.EDITING_AREA && event.getMouseEvent().isPopupTrigger() && !event.isConsumed()) { - for (int i = myPopupHandlers.size() - 1; i >= 0; i--) { - if (myPopupHandlers.get(i).handlePopup(event)) break; + if (ContainerUtil.all(myPopupHandlers, o -> o instanceof ContextMenuPopupHandler)) { + ActionGroup group = getPopupActionGroup(event); + if (group == null) return; + if (group instanceof DefaultActionGroup o && o.getChildrenCount() == 0) return; + new ContextMenuPopupHandler.Simple(group).handlePopup(event); + } + else { + String message = "Non-ContextMenuPopupHandler popup handler detected: " + + ContainerUtil.map(myPopupHandlers, o -> o.getClass().getName()); + if (ClientId.isCurrentlyUnderLocalId()) { + LOG.warn(message); + } + else { + LOG.error(message); + } + for (int i = myPopupHandlers.size() - 1; i >= 0; i--) { + if (myPopupHandlers.get(i).handlePopup(event)) break; + } } } } @@ -5369,7 +5403,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private final class DefaultPopupHandler extends ContextMenuPopupHandler { @Override - public @Nullable ActionGroup getActionGroup(@NotNull EditorMouseEvent event) { + public @Nullable ActionGroup getActionGroup(@NotNull EditorMouseEvent event) { //TODO ! renderer, collapsed-host String contextMenuGroupId = myState.getContextMenuGroupId(); Inlay inlay = event.getInlay(); if (inlay != null) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMousePopupActionGroup.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMousePopupActionGroup.java new file mode 100644 index 000000000000..06c592ed4686 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMousePopupActionGroup.java @@ -0,0 +1,46 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.openapi.editor.impl; + +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.editor.event.EditorMouseEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +/** + * @author gregsh + */ +@ApiStatus.Internal +public class EditorMousePopupActionGroup extends DefaultActionGroup { + + private final EditorMouseEvent myEvent; + private final boolean myResetChildPopupFlag; + + public EditorMousePopupActionGroup(@NotNull List actions, @NotNull EditorMouseEvent event) { + addAll(actions); + myEvent = event; + myResetChildPopupFlag = false; + } + + public EditorMousePopupActionGroup(@NotNull ActionGroup group, @NotNull EditorMouseEvent event) { + add(group); + myEvent = event; + myResetChildPopupFlag = true; + } + + public @NotNull EditorMouseEvent getEvent() { + return myEvent; + } + + @Override + public AnAction @NotNull [] getChildren(@Nullable AnActionEvent e) { + AnAction[] children = super.getChildren(e); + if (e != null && myResetChildPopupFlag) { + Presentation presentation = e.getUpdateSession().presentation(children[0]); + presentation.setPopupGroup(false); + } + return children; + } +} \ No newline at end of file