From 8c4a65702c4c81b5e1492352ec4e719cd13aa780 Mon Sep 17 00:00:00 2001 From: Aleksey Pivovarov Date: Wed, 23 May 2018 15:54:33 +0300 Subject: [PATCH] ui: do not show duplicated entries in "Go To Action" completion --- .../ide/util/gotoByName/GotoActionTest.groovy | 139 +++++++++++++++--- .../ide/util/gotoByName/GotoActionModel.java | 14 ++ .../actionSystem/impl/ToolbarUpdater.java | 2 +- 3 files changed, 134 insertions(+), 21 deletions(-) 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 bc8e913be962..214242e8af51 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 @@ -1,22 +1,34 @@ // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ide.util.gotoByName +import com.intellij.ide.ui.search.BooleanOptionDescription +import com.intellij.ide.ui.search.OptionDescription +import com.intellij.ide.util.gotoByName.GotoActionModel.ActionWrapper +import com.intellij.ide.util.gotoByName.GotoActionModel.MatchMode +import com.intellij.ide.util.gotoByName.GotoActionModel.MatchedValue +import com.intellij.java.navigation.ChooseByNameTest import com.intellij.openapi.actionSystem.ActionManager import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.DataContext -import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import com.intellij.openapi.application.ApplicationManager +import com.intellij.testFramework.PlatformTestUtil +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase +import groovy.transform.CompileStatic + +import java.util.concurrent.TimeUnit + /** * @author peter */ +@CompileStatic class GotoActionTest extends LightCodeInsightFixtureTestCase { - void "test shorter actions first despite ellipsis"() { def pattern = 'Rebas' def fork = 'Rebase my GitHub fork' def rebase = 'Rebase...' - def items = [matchedValue(fork, pattern), - matchedValue(rebase, pattern)].sort() + def items = [matchedAction(fork, pattern), + matchedAction(rebase, pattern)].sort() assert [rebase, fork] == items.collect { it.valueText } } @@ -24,8 +36,8 @@ class GotoActionTest extends LightCodeInsightFixtureTestCase { def pattern = 'by' def byName = 'By Name' def byDesc = 'By Desc' - def items = [matchedValue(byName, pattern), - matchedValue(byDesc, pattern, GotoActionModel.MatchMode.DESCRIPTION)].sort() + def items = [matchedAction(byName, pattern), + matchedAction(byDesc, pattern, MatchMode.DESCRIPTION)].sort() assert [byName, byDesc] == items.collect { it.valueText } } @@ -38,36 +50,123 @@ class GotoActionTest extends LightCodeInsightFixtureTestCase { def eclaire = 'eclaire' def cut = 'Cut' def c = 'c' - def items = [matchedValue(boom, pattern), matchedValue(aardvark, pattern), matchedValue(copy, pattern), - matchedValue(eclaire, pattern), matchedValue(deaf, pattern), matchedValue(cut, pattern), matchedValue(c, pattern)].sort() + def items = [matchedAction(boom, pattern), + matchedAction(aardvark, pattern), + matchedAction(copy, pattern), + matchedAction(eclaire, pattern), + matchedAction(deaf, pattern), + matchedAction(cut, pattern), + matchedAction(c, pattern)].sort() assert [c, copy, cut, aardvark, eclaire, boom, deaf] == items.collect { it.valueText } } void "test match action by parent and grandparent group name"() { def extractMethod = ActionManager.instance.getAction("ExtractMethod") - assert actionMatches('method', extractMethod) == GotoActionModel.MatchMode.NAME - assert actionMatches('extract method', extractMethod) == GotoActionModel.MatchMode.GROUP - assert actionMatches('refactor method', extractMethod) == GotoActionModel.MatchMode.GROUP + assert actionMatches('method', extractMethod) == MatchMode.NAME + assert actionMatches('extract method', extractMethod) == MatchMode.GROUP + assert actionMatches('refactor method', extractMethod) == MatchMode.GROUP } - def actionMatches(String pattern, AnAction action) { + void "test matched value comparator"() { + def pattern = 'Text' + def names = ['Text', 'Text completion', 'Completion Text', 'Add text', 'Retextovize mapping', 'Value', 'A', 'Z'] + + def items = new ArrayList() + names.forEach { String name -> + items += matchedAction(name, pattern, MatchMode.NAME, true) + items += matchedAction(name, pattern, MatchMode.NAME, false) + items += matchedAction(name, pattern, MatchMode.DESCRIPTION, true) + items += matchedAction(name, pattern, MatchMode.DESCRIPTION, false) + items += matchedOption(name, pattern) + items += matchedOption(name, pattern) + items += matchedBooleanOption(name, pattern) + items += new MatchedValue(name, pattern) + } + + PlatformTestUtil.assertComparisonContractNotViolated(items, + { def item1, def item2 -> (item1 <=> item2) }, + { def item1, def item2 -> item1 == item2 }) + + // order can be different on EDT and pooled threads + ApplicationManager.getApplication().executeOnPooledThread { + PlatformTestUtil.assertComparisonContractNotViolated(items, + { def item1, def item2 -> (item1 <=> item2) }, + { def item1, def item2 -> item1 == item2 }) + }.get(20000, TimeUnit.MILLISECONDS) + } + + void "test same action is not reported twice"() { + def patterns = ["Patch", "Add", "Delete", "Show", "Toggle"] + + def model = new GotoActionModel(project, null, null) + def provider = new GotoActionItemProvider(model); + + def popup = ChooseByNamePopup.createPopup(project, model, provider) + try { + patterns.forEach { String pattern -> + def result = ChooseByNameTest.calcPopupElements(popup, pattern, true) + def actions = result.findResults { + if (it instanceof MatchedValue) { + def value = it.value + if (value instanceof ActionWrapper) { + return (value as ActionWrapper).action; + } + if (value instanceof OptionDescription) { + return value + } + } + return null + } + assert actions.size() == actions.toSet().size() + } + } + finally { + popup.close(false) + } + } + + private def actionMatches(String pattern, AnAction action) { return new GotoActionModel(project, null, null).actionMatches(pattern, GotoActionItemProvider.buildMatcher(pattern), action) } - - def matchedValue(String fork, String pattern) { - matchedValue(fork, pattern, GotoActionModel.MatchMode.NAME) + + private MatchedValue matchedAction(String text, String pattern, MatchMode mode = MatchMode.NAME, boolean isAvailable = true) { + return matchedAction(createAction(text), pattern, mode, isAvailable) } - def matchedValue(String fork, String pattern, GotoActionModel.MatchMode mode) { - new GotoActionModel.MatchedValue(createAction(fork, mode), pattern) + private MatchedValue matchedAction(AnAction action, String pattern, MatchMode mode = MatchMode.NAME, boolean isAvailable = true) { + def model = new GotoActionModel(project, null, null) + def wrapper = new ActionWrapper(action, "", mode, DataContext.EMPTY_CONTEXT, model) { + @Override + boolean isAvailable() { + return isAvailable + } + } + new MatchedValue(wrapper, pattern) } - def createAction(String text, GotoActionModel.MatchMode mode) { - def action = new AnAction(text) { + private static AnAction createAction(String text) { + new AnAction(text) { @Override void actionPerformed(AnActionEvent e) { } } - new GotoActionModel.ActionWrapper(action, "", mode, DataContext.EMPTY_CONTEXT, new GotoActionModel(project, null, null)) + } + + private static MatchedValue matchedOption(String text, String pattern) { + return new MatchedValue(new OptionDescription(text), pattern) + } + + private static MatchedValue matchedBooleanOption(String text, String pattern) { + def option = new BooleanOptionDescription(text, text) { + @Override + boolean isOptionEnabled() { + return false + } + + @Override + void setOptionState(boolean enabled) { + } + } + return new MatchedValue(option, pattern) } } 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 7c2590003e4f..832f34436616 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 @@ -242,6 +242,20 @@ public class GotoActionModel implements ChooseByNameModel, Comparator, D return o.hashCode() - hashCode(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MatchedValue value1 = (MatchedValue)o; + return Objects.equals(value, value1.value) && + Objects.equals(pattern, value1.pattern); + } + + @Override + public int hashCode() { + return Objects.hash(value, pattern); + } } @Override diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ToolbarUpdater.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ToolbarUpdater.java index 29a83c04ee5f..5f84ebcf06bb 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ToolbarUpdater.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ToolbarUpdater.java @@ -97,7 +97,7 @@ public abstract class ToolbarUpdater implements Activatable { final Runnable updateRunnable = new MyUpdateRunnable(this, transparentOnly, forced); final Application app = ApplicationManager.getApplication(); - if (now || app.isUnitTestMode()) { + if (now || (app.isUnitTestMode() && app.isDispatchThread())) { updateRunnable.run(); } else {