ui: do not show duplicated entries in "Go To Action" completion

This commit is contained in:
Aleksey Pivovarov
2018-05-25 17:31:07 +03:00
parent 9c736f8c29
commit 8c4a65702c
3 changed files with 134 additions and 21 deletions
@@ -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<MatchedValue>()
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)
}
}
@@ -242,6 +242,20 @@ public class GotoActionModel implements ChooseByNameModel, Comparator<Object>, 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
@@ -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 {