layout-fixing matcher for got-to-action; less garbage

This commit is contained in:
Gregory.Shrago
2016-06-03 22:10:05 +03:00
parent 2cc6de3cc1
commit f7ec503297
3 changed files with 66 additions and 195 deletions
@@ -85,7 +85,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.ComponentPopupBuilder;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Iconable;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
@@ -1962,10 +1965,9 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA
private GotoActionItemProvider createActionProvider() {
GotoActionModel model = new GotoActionModel(project, myFocusComponent, myEditor, myFile) {
@Override
protected MatchMode actionMatches(@NotNull String pattern, @NotNull AnAction anAction) {
String text = anAction.getTemplatePresentation().getText();
return text != null && NameUtil.buildMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE)
.matches(text) ? MatchMode.NAME : MatchMode.NONE;
protected MatchMode actionMatches(@NotNull String pattern, MinusculeMatcher matcher, @NotNull AnAction anAction) {
MatchMode mode = super.actionMatches(pattern, matcher, anAction);
return mode == MatchMode.NAME ? mode : MatchMode.NONE;
}
};
return new GotoActionItemProvider(model);
@@ -28,11 +28,14 @@ import com.intellij.openapi.actionSystem.impl.ActionManagerImpl;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.codeStyle.MinusculeMatcher;
import com.intellij.psi.codeStyle.NameUtil;
import com.intellij.util.CollectConsumer;
import com.intellij.util.Function;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -65,7 +68,7 @@ public class GotoActionItemProvider implements ChooseByNameItemProvider {
boolean everywhere,
@NotNull ProgressIndicator cancelled,
@NotNull final Processor<Object> consumer) {
return filterElements(pattern, everywhere, value -> consumer.process(value));
return filterElements(pattern, everywhere, consumer::process);
}
public boolean filterElements(String pattern, boolean everywhere, Processor<MatchedValue> consumer) {
@@ -81,20 +84,20 @@ public class GotoActionItemProvider implements ChooseByNameItemProvider {
}
private boolean processAbbreviations(final String pattern, Processor<MatchedValue> consumer, DataContext context) {
List<String> actions = AbbreviationManager.getInstance().findActions(pattern);
if (actions.isEmpty()) return true;
List<ActionWrapper> wrappers = ContainerUtil.newArrayListWithCapacity(actions.size());
for (String actionId : actions) {
AnAction action = myActionManager.getAction(actionId);
wrappers.add(new ActionWrapper(action, myModel.myActionGroups.get(action), MatchMode.NAME, context));
}
return ContainerUtil.process(ContainerUtil.map(wrappers, (Function<ActionWrapper, MatchedValue>)w -> new MatchedValue(w, pattern) {
@Nullable
@Override
public String getValueText() {
return pattern;
}
}), consumer);
List<String> actionIds = AbbreviationManager.getInstance().findActions(pattern);
JBIterable<MatchedValue> wrappers = JBIterable.from(actionIds)
.transform(actionId -> {
AnAction action = myActionManager.getAction(actionId);
ActionWrapper wrapper = new ActionWrapper(action, myModel.myActionGroups.get(action), MatchMode.NAME, context);
return new MatchedValue(wrapper, pattern) {
@Nullable
@Override
public String getValueText() {
return pattern;
}
};
});
return processItems(pattern, wrappers, consumer);
}
private static boolean processTopHits(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) {
@@ -109,14 +112,8 @@ public class GotoActionItemProvider implements ChooseByNameItemProvider {
}
provider.consumeTopHits(pattern, collector, project);
}
final Collection<Object> result = collector.getResult();
final List<Comparable> c = new ArrayList<Comparable>();
for (Object o : result) {
if (o instanceof Comparable) {
c.add((Comparable)o);
}
}
return processItems(pattern, c, consumer);
Collection<Object> result = collector.getResult();
return processItems(pattern, JBIterable.from(result).filter(Comparable.class), consumer);
}
private boolean processOptions(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) {
@@ -163,49 +160,45 @@ public class GotoActionItemProvider implements ChooseByNameItemProvider {
}
}
}
return processItems(pattern, options, consumer);
return processItems(pattern, JBIterable.from(options), consumer);
}
private boolean processActions(String pattern, boolean everywhere, Processor<MatchedValue> consumer, DataContext dataContext) {
List<AnAction> actions = ContainerUtil.newArrayList();
JBIterable<AnAction> actions;
if (everywhere) {
for (String id : ((ActionManagerImpl)myActionManager).getActionIds()) {
ProgressManager.checkCanceled();
ContainerUtil.addIfNotNull(actions, myActionManager.getAction(id));
}
} else {
actions.addAll(myModel.myActionGroups.keySet());
Set<String> ids = ((ActionManagerImpl)myActionManager).getActionIds();
actions = JBIterable.from(ids).transform(myActionManager::getAction).filter(Condition.NOT_NULL);
}
List<ActionWrapper> actionWrappers = ContainerUtil.newArrayList();
for (AnAction action : actions) {
ProgressManager.checkCanceled();
MatchMode mode = myModel.actionMatches(pattern, action);
if (mode != MatchMode.NONE) {
actionWrappers.add(new ActionWrapper(action, myModel.myActionGroups.get(action), mode, dataContext));
}
else {
actions = JBIterable.from(myModel.myActionGroups.keySet());
}
MinusculeMatcher matcher = NameUtil.buildMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE);
JBIterable<ActionWrapper> actionWrappers = actions.transform(action -> {
MatchMode mode = myModel.actionMatches(pattern, matcher, action);
if (mode == MatchMode.NONE) return null;
return new ActionWrapper(action, myModel.myActionGroups.get(action), mode, dataContext);
}).filter(Condition.NOT_NULL);
return processItems(pattern, actionWrappers, consumer);
}
private boolean processIntentions(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) {
List<ActionWrapper> intentions = ContainerUtil.newArrayList();
for (String intentionText : myModel.myIntentions.keySet()) {
final ApplyIntentionAction intentionAction = myModel.myIntentions.get(intentionText);
if (myModel.actionMatches(pattern, intentionAction) != MatchMode.NONE) {
intentions.add(new ActionWrapper(intentionAction, intentionText, MatchMode.INTENTION, dataContext));
}
}
MinusculeMatcher matcher = NameUtil.buildMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE);
JBIterable<ActionWrapper> intentions = JBIterable.from(myModel.myIntentions.keySet())
.transform(intentionText -> {
ApplyIntentionAction intentionAction = myModel.myIntentions.get(intentionText);
if (myModel.actionMatches(pattern, matcher, intentionAction) == MatchMode.NONE) return null;
return new ActionWrapper(intentionAction, intentionText, MatchMode.INTENTION, dataContext);
})
.filter(Condition.NOT_NULL);
return processItems(pattern, intentions, consumer);
}
private static boolean processItems(final String pattern, Collection<? extends Comparable> items, Processor<MatchedValue> consumer) {
List<MatchedValue> matched = ContainerUtil.map(items, new Function<Comparable, MatchedValue>() {
@Override
public MatchedValue fun(Comparable comparable) {
return new MatchedValue(comparable, pattern);
}
});
private static boolean processItems(String pattern, JBIterable<? extends Comparable> items, Processor<MatchedValue> consumer) {
ArrayList<MatchedValue> matched = ContainerUtil.newArrayList();
items.transform(o -> {
ProgressManager.checkCanceled();
return o instanceof MatchedValue ? (MatchedValue)o : new MatchedValue(o, pattern);
}).addAllTo(matched);
Collections.sort(matched);
return ContainerUtil.process(matched, consumer);
}
@@ -39,6 +39,8 @@ import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.MinusculeMatcher;
import com.intellij.psi.codeStyle.NameUtil;
import com.intellij.ui.*;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.OnOffButton;
@@ -49,7 +51,6 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.ContainerUtilRt;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.UIUtil;
import org.apache.oro.text.regex.*;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -60,13 +61,14 @@ import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.intellij.ui.SimpleTextAttributes.STYLE_PLAIN;
import static com.intellij.ui.SimpleTextAttributes.STYLE_SEARCH_MATCH;
@SuppressWarnings("TestOnlyProblems")
public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, Comparator<Object>, EdtSortingModel, DumbAware {
@NotNull private static java.util.regex.Pattern INNER_GROUP_WITH_IDS = java.util.regex.Pattern.compile("(.*) \\(\\d+\\)");
private static final Pattern INNER_GROUP_WITH_IDS = Pattern.compile("(.*) \\(\\d+\\)");
@Nullable private final Project myProject;
private final Component myContextComponent;
@@ -75,8 +77,6 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C
private static final Icon EMPTY_ICON = EmptyIcon.ICON_18;
private Pattern myCompiledPattern;
protected final SearchableOptionsRegistrar myIndex;
protected final Map<AnAction, String> myActionGroups = ContainerUtil.newHashMap();
@@ -235,7 +235,7 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C
@Override
public ListCellRenderer getListCellRenderer() {
return new GotoActionListCellRenderer(description -> getGroupName(description));
return new GotoActionListCellRenderer(this::getGroupName);
}
protected String getActionId(@NotNull AnAction anAction) {
@@ -402,36 +402,28 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C
public boolean matches(@NotNull String name, @NotNull String pattern) {
AnAction anAction = myActionManager.getAction(name);
if (anAction == null) return true;
return actionMatches(pattern, anAction) != MatchMode.NONE;
MinusculeMatcher matcher = NameUtil.buildMatcher("*" + pattern, NameUtil.MatchingCaseSensitivity.NONE);
return actionMatches(pattern, matcher, anAction) != MatchMode.NONE;
}
protected MatchMode actionMatches(@NotNull String pattern, @NotNull AnAction anAction) {
Pattern compiledPattern = getPattern(pattern);
protected MatchMode actionMatches(@NotNull String pattern, MinusculeMatcher matcher, @NotNull AnAction anAction) {
Presentation presentation = anAction.getTemplatePresentation();
String text = presentation.getText();
String description = presentation.getDescription();
String groupName = myActionGroups.get(anAction);
PatternMatcher matcher = getMatcher();
if (text != null && matcher.matches(text, compiledPattern)) {
if (text != null && matcher.matches(text)) {
return MatchMode.NAME;
}
else if (description != null && !description.equals(text) && matcher.matches(description, compiledPattern)) {
else if (description != null && !description.equals(text) && matcher.matches(description)) {
return MatchMode.DESCRIPTION;
}
if (text == null) {
return MatchMode.NONE;
}
if (groupName == null) {
return matches(pattern, compiledPattern, matcher, text) ? MatchMode.NON_MENU : MatchMode.NONE;
}
if (matches(pattern, compiledPattern, matcher, groupName + " " + text)) {
if (matcher.matches(groupName + " " + text)) {
return anAction instanceof ToggleAction ? MatchMode.NAME : MatchMode.GROUP;
}
return matches(pattern, compiledPattern, matcher, text + " " + groupName) ? MatchMode.GROUP : MatchMode.NONE;
}
private static boolean matches(@NotNull String pattern, Pattern compiledPattern, @NotNull PatternMatcher matcher, @NotNull String str) {
return StringUtil.containsIgnoreCase(str, pattern) || matcher.matches(str, compiledPattern);
return matcher.matches(text + " " + groupName) ? MatchMode.GROUP : MatchMode.NONE;
}
@Nullable
@@ -443,25 +435,6 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C
return myContextComponent;
}
@NotNull
Pattern getPattern(@NotNull String pattern) {
String converted = convertPattern(pattern.trim());
Pattern compiledPattern = myCompiledPattern;
if (compiledPattern != null && !Comparing.strEqual(converted, compiledPattern.getPattern())) {
compiledPattern = null;
}
if (compiledPattern == null) {
try {
myCompiledPattern = compiledPattern = new Perl5Compiler().compile(converted, Perl5Compiler.READ_ONLY_MASK);
}
catch (MalformedPatternException e) {
//do nothing
}
}
return compiledPattern;
}
@NotNull
@Override
public SortedSet<Object> sort(@NotNull Set<Object> elements) {
@@ -474,93 +447,7 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C
NONE, INTENTION, NAME, DESCRIPTION, GROUP, NON_MENU
}
static String convertPattern(@NotNull String pattern) {
int eol = pattern.indexOf('\n');
if (eol != -1) {
pattern = pattern.substring(0, eol);
}
if (pattern.length() >= 80) {
pattern = pattern.substring(0, 80);
}
@NonNls StringBuilder buffer = new StringBuilder();
boolean allowToLower = true;
if (containsOnlyUppercaseLetters(pattern)) {
allowToLower = false;
}
if (allowToLower) {
buffer.append(".*");
}
boolean firstIdentifierLetter = true;
for (int i = 0; i < pattern.length(); i++) {
char c = pattern.charAt(i);
if (Character.isLetterOrDigit(c)) {
// This logic allows to use uppercase letters only to catch the name like PDM for PsiDocumentManager
if (Character.isUpperCase(c) || Character.isDigit(c)) {
if (!firstIdentifierLetter) {
buffer.append("[^A-Z]*");
}
buffer.append("[");
buffer.append(c);
if (allowToLower || i == 0) {
buffer.append('|');
buffer.append(Character.toLowerCase(c));
}
buffer.append("]");
}
else if (Character.isLowerCase(c)) {
buffer.append('[');
buffer.append(c);
buffer.append('|');
buffer.append(Character.toUpperCase(c));
buffer.append(']');
}
else {
buffer.append(c);
}
firstIdentifierLetter = false;
}
else if (c == '*') {
buffer.append(".*");
firstIdentifierLetter = true;
}
else if (c == '.') {
buffer.append("\\.");
firstIdentifierLetter = true;
}
else if (c == ' ') {
buffer.append(".*\\ ");
firstIdentifierLetter = true;
}
else {
firstIdentifierLetter = true;
// for standard RegExp engine
// buffer.append("\\u");
// buffer.append(Integer.toHexString(c + 0x20000).substring(1));
// for OROMATCHER RegExp engine
buffer.append("\\x");
buffer.append(Integer.toHexString(c + 0x20000).substring(3));
}
}
buffer.append(".*");
return buffer.toString();
}
private static boolean containsOnlyUppercaseLetters(@NotNull String s) {
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (c != '*' && c != ' ' && !Character.isUpperCase(c)) return false;
}
return true;
}
@Override
public boolean willOpenEditor() {
@@ -572,17 +459,6 @@ public class GotoActionModel implements ChooseByNameModel, CustomMatcherModel, C
return true;
}
private final ThreadLocal<PatternMatcher> myMatcher = new ThreadLocal<PatternMatcher>() {
@NotNull
@Override
protected PatternMatcher initialValue() {
return new Perl5Matcher();
}
};
PatternMatcher getMatcher() {
return myMatcher.get();
}
public static class ActionWrapper implements Comparable<ActionWrapper> {
@NotNull private final AnAction myAction;
@NotNull private final MatchMode myMode;