From 8ab13a8d74ddd624d5ff5ccfc3587bf2314bb9e9 Mon Sep 17 00:00:00 2001 From: Mikhail Sokolov Date: Mon, 30 Oct 2023 16:21:19 +0100 Subject: [PATCH] IDEA-314818 Go to Actions takes up to 5 seconds for the first invocation GitOrigin-RevId: 0620a678a423f2dbd5f81b8e8f34b4162b3b328c --- .../ide/util/gotoByName/GotoActionTest.groovy | 2 +- .../util/gotoByName/ActionAsyncProvider.kt | 14 +-- .../ide/util/gotoByName/ActionSearchUtil.kt | 73 +++++++++++++++ .../gotoByName/GotoActionItemProvider.java | 88 +++---------------- .../ide/util/gotoByName/GotoActionModel.java | 6 +- .../gotoByName/LocalizedActionAliasMatcher.kt | 2 +- ...EverywhereGeneralActionFeaturesProvider.kt | 4 +- 7 files changed, 99 insertions(+), 90 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/ide/util/gotoByName/ActionSearchUtil.kt diff --git a/java/java-tests/testSrc/com/intellij/ide/util/gotoByName/GotoActionTest.groovy b/java/java-tests/testSrc/com/intellij/ide/util/gotoByName/GotoActionTest.groovy index 39b55a2768b3..652ea4fa0ad9 100644 --- a/java/java-tests/testSrc/com/intellij/ide/util/gotoByName/GotoActionTest.groovy +++ b/java/java-tests/testSrc/com/intellij/ide/util/gotoByName/GotoActionTest.groovy @@ -406,7 +406,7 @@ class GotoActionTest extends LightJavaCodeInsightFixtureTestCase { } private def actionMatches(String pattern, AnAction action) { - def matcher = GotoActionItemProvider.buildMatcher(pattern) + def matcher = ActionSearchUtilKt.buildMatcher(pattern) def model = new GotoActionModel(project, null, null) model.buildGroupMappings() return model.actionMatches(pattern, matcher, action) diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ActionAsyncProvider.kt b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ActionAsyncProvider.kt index 2590bce79bdb..6d5d108f4a55 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ActionAsyncProvider.kt +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ActionAsyncProvider.kt @@ -143,7 +143,7 @@ class ActionAsyncProvider(private val myModel: GotoActionModel) { private suspend fun matchedActionsAndStubsFlow(pattern: String, allIds: Collection, presentationProvider: suspend (AnAction) -> Presentation): Flow { - val matcher = GotoActionItemProvider.buildMatcher(pattern) + val matcher = buildMatcher(pattern) val weightMatcher = buildWeightMatcher(pattern) val fromIdsFlow = allIds.asFlow().mapNotNull { @@ -158,7 +158,7 @@ class ActionAsyncProvider(private val myModel: GotoActionModel) { val actionsWithWeightsFlow = merge(fromIdsFlow, additionalActionsFlow).transform { val mode = myModel.actionMatches(pattern, matcher, it) if (mode != MatchMode.NONE) { - val weight = GotoActionItemProvider.calcElementWeight(it, pattern, weightMatcher) + val weight = calcElementWeight(it, pattern, weightMatcher) emit(MatchedAction(it, mode, weight)) } } @@ -185,7 +185,7 @@ class ActionAsyncProvider(private val myModel: GotoActionModel) { private fun unmatchedStubsFlow(pattern: String, allIds: Collection, presentationProvider: suspend (AnAction) -> Presentation): Flow { - val matcher = GotoActionItemProvider.buildMatcher(pattern) + val matcher = buildMatcher(pattern) val weightMatcher = buildWeightMatcher(pattern) return allIds.asFlow().buffer(allIds.size) @@ -199,7 +199,7 @@ class ActionAsyncProvider(private val myModel: GotoActionModel) { val action = myActionManager.getAction((it as ActionStubBase).id) val mode = myModel.actionMatches(pattern, matcher, action) if (mode != MatchMode.NONE) { - val weight = GotoActionItemProvider.calcElementWeight(action, pattern, weightMatcher) + val weight = calcElementWeight(action, pattern, weightMatcher) emit(MatchedAction(action, mode, weight)) } } @@ -245,7 +245,7 @@ class ActionAsyncProvider(private val myModel: GotoActionModel) { private fun intentionsFlow(pattern: String): Flow { LOG.debug("Create intentions flow ($pattern)") - val matcher = GotoActionItemProvider.buildMatcher(pattern) + val matcher = buildMatcher(pattern) val weightMatcher = buildWeightMatcher(pattern) return channelFlow { @@ -304,7 +304,7 @@ class ActionAsyncProvider(private val myModel: GotoActionModel) { } } if (!Strings.isEmptyOrSpaces(pattern)) { - val matcher = GotoActionItemProvider.buildMatcher(pattern) + val matcher = buildMatcher(pattern) if (optionDescriptions == null) { optionDescriptions = HashSet() } @@ -338,7 +338,7 @@ class ActionAsyncProvider(private val myModel: GotoActionModel) { } private fun matchItem(item: Any, matcher: MinusculeMatcher, pattern: String, matchType: MatchedValueType): MatchedValue { - val weight = GotoActionItemProvider.calcElementWeight(item, pattern, matcher) + val weight = calcElementWeight(item, pattern, matcher) return if (weight == null) MatchedValue(item, pattern, matchType) else MatchedValue(item, pattern, weight, matchType) } diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ActionSearchUtil.kt b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ActionSearchUtil.kt new file mode 100644 index 000000000000..21663c83c59a --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/ActionSearchUtil.kt @@ -0,0 +1,73 @@ +// 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.ide.util.gotoByName + +import com.intellij.ide.ui.search.OptionDescription +import com.intellij.openapi.actionSystem.ActionPlaces +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.psi.codeStyle.MinusculeMatcher +import com.intellij.psi.codeStyle.NameUtil +import com.intellij.psi.codeStyle.WordPrefixMatcher +import com.intellij.util.DefaultBundleService +import com.intellij.util.text.Matcher +import org.jetbrains.annotations.Nls +import kotlin.math.max + +private const val BONUS_FOR_SPACE_IN_PATTERN = 100 +private const val SETTINGS_PENALTY = 100 + + +fun getActionText(value: Any?): @Nls String? { + if (value is OptionDescription) return value.hit + if (value is AnAction) return getAnActionText(value) + if (value is GotoActionModel.ActionWrapper) return getAnActionText(value.action) + return null +} + +fun getAnActionText(value: AnAction): @Nls String? { + val presentation = value.templatePresentation.clone() + value.applyTextOverride(ActionPlaces.ACTION_SEARCH, presentation) + return presentation.text +} + +fun buildMatcher(pattern: String): Matcher { + return if (pattern.contains(" ")) WordPrefixMatcher(pattern) + else NameUtil.buildMatcher("*$pattern", NameUtil.MatchingCaseSensitivity.NONE) +} + +fun calcElementWeight(element: Any, pattern: String, matcher: MinusculeMatcher): Int? { + var degree = calculateDegree(matcher, getActionText(element)) + if (degree == null) return null + + if (degree == 0) { + degree = calculateDegree(matcher, DefaultBundleService.getInstance().compute { + getAnActionOriginalText(getAction(element)) + }) + if (degree == null) return null + } + + if (pattern.trim { it <= ' ' }.contains(" ")) degree += BONUS_FOR_SPACE_IN_PATTERN + if (element is OptionDescription && degree > 0) degree -= SETTINGS_PENALTY + + return max(degree, 0) +} + +fun calculateDegree(matcher: MinusculeMatcher, text: String?): Int? { + if (text == null) return null + return matcher.matchingDegree(text) +} + +private fun getAnActionOriginalText(value: AnAction?): String? { + if (value == null) return null + val presentation = value.templatePresentation.clone() + value.applyTextOverride(ActionPlaces.ACTION_SEARCH, presentation) + val mnemonic = presentation.textWithPossibleMnemonic.get() + if (mnemonic == null) return null + + return mnemonic.text +} + +private fun getAction(value: Any): AnAction? = when (value) { + is AnAction -> value + is GotoActionModel.ActionWrapper -> value.action + else -> null +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionItemProvider.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionItemProvider.java index 2068b9279b30..143bca731abe 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionItemProvider.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionItemProvider.java @@ -10,7 +10,10 @@ import com.intellij.ide.ui.search.ActionFromOptionDescriptorProvider; import com.intellij.ide.ui.search.OptionDescription; import com.intellij.ide.ui.search.SearchableOptionsRegistrar; import com.intellij.ide.ui.search.SearchableOptionsRegistrarImpl; -import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.AbbreviationManager; +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.impl.ActionManagerImpl; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; @@ -21,19 +24,14 @@ import com.intellij.openapi.util.NlsActions.ActionText; import com.intellij.openapi.util.NlsContexts; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.Strings; -import com.intellij.openapi.util.text.TextWithMnemonic; import com.intellij.psi.codeStyle.MinusculeMatcher; import com.intellij.psi.codeStyle.NameUtil; -import com.intellij.psi.codeStyle.WordPrefixMatcher; import com.intellij.ui.switcher.QuickActionProvider; import com.intellij.util.CollectConsumer; -import com.intellij.util.DefaultBundleService; import com.intellij.util.Processor; import com.intellij.util.text.Matcher; -import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.function.Predicate; @@ -42,9 +40,11 @@ import java.util.stream.Stream; import static com.intellij.ide.util.gotoByName.GotoActionModel.*; +/** + * @deprecated This class is marked for removal and should not be used. Please use {@link ActionAsyncProvider} instead + */ +@Deprecated(forRemoval = true) public final class GotoActionItemProvider implements ChooseByNameWeightedItemProvider { - private static final int BONUS_FOR_SPACE_IN_PATTERN = 100; - private static final int SETTINGS_PENALTY = 100; private final ActionManager myActionManager = ActionManager.getInstance(); private final GotoActionModel myModel; @@ -173,7 +173,7 @@ public final class GotoActionItemProvider implements ChooseByNameWeightedItemPro } } if (!Strings.isEmptyOrSpaces(pattern)) { - Matcher matcher = buildMatcher(pattern); + Matcher matcher = ActionSearchUtilKt.buildMatcher(pattern); if (optionDescriptions == null) { optionDescriptions = new HashSet<>(); } @@ -211,7 +211,7 @@ public final class GotoActionItemProvider implements ChooseByNameWeightedItemPro public boolean processActions(String pattern, Predicate consumer, @NotNull Set ids) { Stream actions = ids.stream().map(myActionManager::getAction).filter(Objects::nonNull); - Matcher matcher = buildMatcher(pattern); + Matcher matcher = ActionSearchUtilKt.buildMatcher(pattern); QuickActionProvider provider = myModel.getDataContext().getData(QuickActionProvider.KEY); if (provider != null) { @@ -239,13 +239,8 @@ public final class GotoActionItemProvider implements ChooseByNameWeightedItemPro myIntentions.drop(); } - @NotNull - static Matcher buildMatcher(String pattern) { - return pattern.contains(" ") ? new WordPrefixMatcher(pattern) : NameUtil.buildMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE); - } - private boolean processIntentions(String pattern, Predicate consumer) { - Matcher matcher = buildMatcher(pattern); + Matcher matcher = ActionSearchUtilKt.buildMatcher(pattern); Map<@ActionText String, ApplyIntentionAction> intentionMap = myIntentions.getValue(); Stream intentions = intentionMap.keySet().stream() .map(intentionText -> { @@ -275,7 +270,7 @@ public final class GotoActionItemProvider implements ChooseByNameWeightedItemPro return (MatchedValue)o; } - Integer weight = calcElementWeight(o, pattern, matcher); + Integer weight = ActionSearchUtilKt.calcElementWeight(o, pattern, matcher); return weight == null ? new MatchedValue(o, pattern, type) : new MatchedValue(o, pattern, weight, type); }).collect(Collectors.toList()); @@ -294,69 +289,10 @@ public final class GotoActionItemProvider implements ChooseByNameWeightedItemPro return true; } - static @Nullable Integer calcElementWeight(Object element, String pattern, MinusculeMatcher matcher) { - Integer degree = calculateDegree(matcher, getActionText(element)); - if (degree == null) return null; - - if (degree == 0) { - degree = calculateDegree(matcher, DefaultBundleService.getInstance().compute(() -> getAnActionOriginalText(getAction(element)))); - if (degree == null) return null; - } - - if (pattern.trim().contains(" ")) degree += BONUS_FOR_SPACE_IN_PATTERN; - if (element instanceof OptionDescription && degree > 0) degree -= SETTINGS_PENALTY; - - return Math.max(degree, 0); - } - - @Nullable - static Integer calculateDegree(MinusculeMatcher matcher, @Nullable String text) { - if (text == null) return null; - return matcher.matchingDegree(text); - } - private static MinusculeMatcher buildWeightMatcher(String pattern) { return NameUtil.buildMatcher("*" + pattern) .withCaseSensitivity(NameUtil.MatchingCaseSensitivity.NONE) .preferringStartMatches() .build(); } - - @Nullable - @Nls - public static String getActionText(Object value) { - if (value instanceof OptionDescription) return ((OptionDescription)value).getHit(); - if (value instanceof AnAction) return getAnActionText((AnAction)value); - if (value instanceof ActionWrapper) return getAnActionText(((ActionWrapper)value).getAction()); - return null; - } - - @Nullable - private static AnAction getAction(Object value) { - if (value instanceof AnAction) { - return (AnAction)value; - } - else if (value instanceof ActionWrapper) { - return ((ActionWrapper)value).getAction(); - } - return null; - } - - @Nullable - @Nls - public static String getAnActionText(AnAction value) { - Presentation presentation = value.getTemplatePresentation().clone(); - value.applyTextOverride(ActionPlaces.ACTION_SEARCH, presentation); - return presentation.getText(); - } - - private static @Nullable String getAnActionOriginalText(@Nullable AnAction value) { - if (value == null) return null; - Presentation presentation = value.getTemplatePresentation().clone(); - value.applyTextOverride(ActionPlaces.ACTION_SEARCH, presentation); - TextWithMnemonic mnemonic = presentation.getTextWithPossibleMnemonic().get(); - if (mnemonic == null) return null; - - return mnemonic.getText(); - } } diff --git a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java index 6f2a443bc183..0a9c09c275dd 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java +++ b/platform/lang-impl/src/com/intellij/ide/util/gotoByName/GotoActionModel.java @@ -224,7 +224,7 @@ public final class GotoActionModel implements ChooseByNameModel, Comparator value is OptionDescription -> GotoActionModel.GotoActionListCellRenderer.calcHit(value) is GotoActionModel.ActionWrapper -> value.presentation.text - is AnAction -> GotoActionItemProvider.getAnActionText(value) + is AnAction -> getAnActionText(value) else -> null } }