avoid calling actions update during the main menu calculation 2

In RemDev, the host updates menu groups directly without the enclosing `IdeMainMenuActionGroup` group. Its `ALWAYS_VISIBLE` setting is ignored. The solution is to use `ActionConfigurationCustomizer` that works both on client and host.

Also, we now copy client properties from stub presentation to its action template presentation. See `Presentation.copyUnsetTemplateProperties`.

GitOrigin-RevId: 153d76dc7a3d97e7355bad3236b7ca09b244d0e3
This commit is contained in:
Gregory.Shrago
2024-02-20 19:56:00 +04:00
committed by intellij-monorepo-bot
parent 03ee822772
commit d21dad3611
6 changed files with 41 additions and 25 deletions

View File

@@ -120,7 +120,8 @@ public abstract class AnAction implements PossiblyDumbAware, ActionUpdateThreadA
* @param icon the action's icon
*/
public AnAction(@Nullable @ActionText String text, @Nullable @ActionDescription String description, @Nullable Icon icon) {
this(() -> text, () -> description, icon);
this(text == null ? Presentation.NULL_STRING : () -> text,
description == null ? Presentation.NULL_STRING : () -> description, icon);
}
@ApiStatus.Experimental

View File

@@ -9,6 +9,7 @@ import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.SystemInfoRt;
import com.intellij.openapi.util.text.Strings;
import com.intellij.openapi.util.text.TextWithMnemonic;
import com.intellij.util.BitUtil;
import com.intellij.util.SmartFMap;
@@ -36,6 +37,7 @@ public final class Presentation implements Cloneable {
private static final Logger LOG = Logger.getInstance(Presentation.class);
public static final Supplier<String> NULL_STRING = () -> null;
public static final Supplier<TextWithMnemonic> NULL_TEXT_WITH_MNEMONIC = () -> null;
// Property keys for the PropertyChangeListener API
public static final @NonNls String PROP_TEXT = "text";
@@ -71,7 +73,7 @@ public final class Presentation implements Cloneable {
private int myFlags = IS_ENABLED | IS_VISIBLE | IS_DISABLE_GROUP_IF_EMPTY;
private @NotNull Supplier<@ActionDescription String> descriptionSupplier = NULL_STRING;
private @NotNull Supplier<TextWithMnemonic> textWithMnemonicSupplier = () -> null;
private @NotNull Supplier<TextWithMnemonic> textWithMnemonicSupplier = NULL_TEXT_WITH_MNEMONIC;
private @NotNull SmartFMap<String, Object> myUserMap = SmartFMap.emptyMap();
private @Nullable Supplier<? extends @Nullable Icon> icon;
@@ -169,7 +171,10 @@ public final class Presentation implements Cloneable {
public @NotNull Supplier<TextWithMnemonic> getTextWithMnemonic(@NotNull Supplier<@Nls(capitalization = Nls.Capitalization.Title) String> text,
boolean mayContainMnemonic) {
if (mayContainMnemonic) {
if (text == NULL_STRING) {
return NULL_TEXT_WITH_MNEMONIC;
}
else if (mayContainMnemonic) {
return () -> {
String s = text.get();
if (s == null) {
@@ -283,10 +288,18 @@ public final class Presentation implements Cloneable {
return icon;
}
public void copyIconIfUnset(@NotNull Presentation other) {
if (icon == null && other.icon != null) {
@ApiStatus.Internal // do not expose
void copyUnsetTemplateProperties(@NotNull Presentation other) {
if (icon == null) {
icon = other.icon;
}
if (Strings.isEmpty(getText()) && Strings.isNotEmpty(other.getText())) {
textWithMnemonicSupplier = other.textWithMnemonicSupplier;
}
if (Strings.isEmpty(descriptionSupplier.get()) && Strings.isNotEmpty(other.descriptionSupplier.get())) {
descriptionSupplier = other.descriptionSupplier;
}
myUserMap = myUserMap.plusAll(other.myUserMap);
}
public void setIcon(@Nullable Icon icon) {

View File

@@ -4,7 +4,6 @@ package com.intellij.openapi.actionSystem;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.project.ProjectType;
import com.intellij.openapi.util.text.Strings;
import com.intellij.util.SmartList;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -105,13 +104,10 @@ public final class ActionStub extends AnAction implements ActionStubBase {
}
}
public static void copyTemplatePresentation(Presentation sourcePresentation, Presentation targetPresentation) {
targetPresentation.copyIconIfUnset(sourcePresentation);
if (Strings.isEmpty(targetPresentation.getText()) && sourcePresentation.getText() != null) {
targetPresentation.setTextWithMnemonic(sourcePresentation.getTextWithPossibleMnemonic());
}
if (targetPresentation.getDescription() == null && sourcePresentation.getDescription() != null) {
targetPresentation.setDescription(sourcePresentation.getDescription());
}
public static void copyTemplatePresentation(@NotNull Presentation sourcePresentation,
@NotNull Presentation targetPresentation) {
// Note: actions can update templatePresentation in constructor.
// We apply stub properties only after that. It could be confusing.
targetPresentation.copyUnsetTemplateProperties(sourcePresentation);
}
}

View File

@@ -17,8 +17,11 @@ internal class ActionGroupStub(override val id: String,
fun initGroup(target: ActionGroup, actionToId: Function<AnAction, String?>) {
ActionStub.copyTemplatePresentation(templatePresentation, target.templatePresentation)
if (popupDefinedInXml) target.isPopup = isPopup
copyActionTextOverrides(target)
target.isSearchable = isSearchable
target.shortcutSet = shortcutSet
val children = childActionsOrStubs
if (children.isNotEmpty()) {
@@ -29,9 +32,5 @@ internal class ActionGroupStub(override val id: String,
target.addAction(action, Constraints.LAST, actionToId)
}
}
if (popupDefinedInXml) {
target.isPopup = isPopup
}
target.isSearchable = isSearchable
}
}

View File

@@ -7,6 +7,7 @@ import com.intellij.ide.ui.UISettingsListener
import com.intellij.ide.ui.customization.CustomActionsSchema
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
import com.intellij.openapi.actionSystem.ex.ActionRuntimeRegistrar
import com.intellij.openapi.actionSystem.impl.*
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT
@@ -188,17 +189,22 @@ private suspend fun expandMainActionGroup(mainActionGroup: ActionGroup,
@ApiStatus.Internal
suspend fun IdeMainMenuActionGroup(): ActionGroup? {
val group = CustomActionsSchema.getInstanceAsync().getCorrectedActionAsync(IdeActions.GROUP_MAIN_MENU) ?: return null
// enforce the "always-visible" flag for all main menu items
// without forcing everyone to employ custom groups in their plugin.xml files.
return object : ActionGroupWrapper(group) {
override fun getChildren(e: AnActionEvent?): Array<out AnAction> {
return super.getChildren(e).onEach { it.templatePresentation.putClientProperty(ActionMenu.ALWAYS_VISIBLE, true) }
}
override fun postProcessVisibleChildren(visibleChildren: List<AnAction>,
updateSession: UpdateSession): List<AnAction?> {
return super.postProcessVisibleChildren(visibleChildren, updateSession)
.filterIsInstance<ActionGroup>()
}
}
}
}
class IdeMainMenuActionCustomizer : ActionConfigurationCustomizer, ActionConfigurationCustomizer.LightCustomizeStrategy {
// enforce the "always-visible" flag for all main menu items
// without forcing everyone to employ custom groups in their plugin.xml files.
override suspend fun customize(actionRegistrar: ActionRuntimeRegistrar) {
val group = actionRegistrar.getActionOrStub(IdeActions.GROUP_MAIN_MENU) as? DefaultActionGroup
group?.childActionsOrStubs?.forEach {
it.templatePresentation.putClientProperty(ActionMenu.ALWAYS_VISIBLE, true)
}
}
}

View File

@@ -1383,6 +1383,7 @@
<environmentKeyProvider implementation="com.intellij.ide.plugins.PluginEnvironmentKeyProvider"/>
<actionConfigurationCustomizer implementation="com.intellij.platform.ide.menu.IdeMainMenuActionCustomizer"/>
<actionConfigurationCustomizer implementation="com.intellij.platform.ide.menu.GlobalMenuLinux$MyActionTuner"/>
<titleInfoProvider implementation="com.intellij.openapi.wm.impl.simpleTitleParts.ProductTitleInfoProvider"/>