[go to action] IDEA-148456 Regression: Remote SSH External Tools no longer appearing...
[idea/community.git] / platform / lang-impl / src / com / intellij / ide / util / gotoByName / GotoActionItemProvider.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.ide.util.gotoByName;
17
18 import com.intellij.ide.DataManager;
19 import com.intellij.ide.SearchTopHitProvider;
20 import com.intellij.ide.actions.ApplyIntentionAction;
21 import com.intellij.ide.ui.OptionsTopHitProvider;
22 import com.intellij.ide.ui.search.ActionFromOptionDescriptorProvider;
23 import com.intellij.ide.ui.search.OptionDescription;
24 import com.intellij.ide.ui.search.SearchableOptionsRegistrar;
25 import com.intellij.ide.ui.search.SearchableOptionsRegistrarImpl;
26 import com.intellij.openapi.actionSystem.*;
27 import com.intellij.openapi.actionSystem.impl.ActionManagerImpl;
28 import com.intellij.openapi.progress.ProgressIndicator;
29 import com.intellij.openapi.progress.ProgressManager;
30 import com.intellij.openapi.project.Project;
31 import com.intellij.openapi.util.text.StringUtil;
32 import com.intellij.util.CollectConsumer;
33 import com.intellij.util.Function;
34 import com.intellij.util.Processor;
35 import com.intellij.util.containers.ContainerUtil;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 import java.util.*;
40
41 import static com.intellij.ide.util.gotoByName.GotoActionModel.*;
42
43 /**
44  * @author peter
45  */
46 @SuppressWarnings("TestOnlyProblems")
47 public class GotoActionItemProvider implements ChooseByNameItemProvider {
48   private final ActionManager myActionManager = ActionManager.getInstance();
49   protected final SearchableOptionsRegistrar myIndex = SearchableOptionsRegistrar.getInstance();
50   private final GotoActionModel myModel;
51
52   public GotoActionItemProvider(GotoActionModel model) {
53     myModel = model;
54   }
55
56   @NotNull
57   @Override
58   public List<String> filterNames(@NotNull ChooseByNameBase base, @NotNull String[] names, @NotNull String pattern) {
59     return Collections.emptyList(); // no common prefix insertion in goto action
60   }
61
62   @Override
63   public boolean filterElements(@NotNull final ChooseByNameBase base,
64                                 @NotNull final String pattern,
65                                 boolean everywhere,
66                                 @NotNull ProgressIndicator cancelled,
67                                 @NotNull final Processor<Object> consumer) {
68     return filterElements(pattern, everywhere, new Processor<MatchedValue>() {
69       @Override
70       public boolean process(MatchedValue value) {
71         return consumer.process(value);
72       }
73     });
74   }
75
76   public boolean filterElements(String pattern, boolean everywhere, Processor<MatchedValue> consumer) {
77     DataContext dataContext = DataManager.getInstance().getDataContext(myModel.getContextComponent());
78
79     if (!processAbbreviations(pattern, consumer, dataContext)) return false;
80     if (!processIntentions(pattern, consumer, dataContext)) return false;
81     if (!processActions(pattern, everywhere, consumer, dataContext)) return false;
82     if (!processTopHits(pattern, consumer, dataContext)) return false;
83     if (!processOptions(pattern, consumer, dataContext)) return false;
84
85     return true;
86   }
87
88   private boolean processAbbreviations(final String pattern, Processor<MatchedValue> consumer, DataContext context) {
89     List<String> actions = AbbreviationManager.getInstance().findActions(pattern);
90     if (actions.isEmpty()) return true;
91     List<ActionWrapper> wrappers = ContainerUtil.newArrayListWithCapacity(actions.size());
92     for (String actionId : actions) {
93       AnAction action = myActionManager.getAction(actionId);
94       wrappers.add(new ActionWrapper(action, myModel.myActionGroups.get(action), MatchMode.NAME, context));
95     }
96     return ContainerUtil.process(ContainerUtil.map(wrappers, new Function<ActionWrapper, MatchedValue>() {
97       @Override
98       public MatchedValue fun(@NotNull ActionWrapper w) {
99         return new MatchedValue(w, pattern) {
100           @Nullable
101           @Override
102           public String getValueText() {
103             return pattern;
104           }
105         };
106       }
107     }), consumer);
108   }
109
110   private static boolean processTopHits(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) {
111     Project project = CommonDataKeys.PROJECT.getData(dataContext);
112     final CollectConsumer<Object> collector = new CollectConsumer<Object>();
113     for (SearchTopHitProvider provider : SearchTopHitProvider.EP_NAME.getExtensions()) {
114       if (provider instanceof OptionsTopHitProvider.CoveredByToggleActions) continue;
115       if (provider instanceof OptionsTopHitProvider && !((OptionsTopHitProvider)provider).isEnabled(project)) continue;
116       if (provider instanceof OptionsTopHitProvider && !StringUtil.startsWith(pattern, "#")) {
117         String prefix = "#" + ((OptionsTopHitProvider)provider).getId() + " ";
118         provider.consumeTopHits(prefix + pattern, collector, project);
119       }
120       provider.consumeTopHits(pattern, collector, project);
121     }
122     final Collection<Object> result = collector.getResult();
123     final List<Comparable> c = new ArrayList<Comparable>();
124     for (Object o : result) {
125       if (o instanceof Comparable) {
126         c.add((Comparable)o);
127       }
128     }
129     return processItems(pattern, c, consumer);
130   }
131
132   private boolean processOptions(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) {
133     List<Comparable> options = ContainerUtil.newArrayList();
134     final Set<String> words = myIndex.getProcessedWords(pattern);
135     Set<OptionDescription> optionDescriptions = null;
136     final String actionManagerName = myActionManager.getComponentName();
137     for (String word : words) {
138       final Set<OptionDescription> descriptions = ((SearchableOptionsRegistrarImpl)myIndex).getAcceptableDescriptions(word);
139       if (descriptions != null) {
140         for (Iterator<OptionDescription> iterator = descriptions.iterator(); iterator.hasNext(); ) {
141           OptionDescription description = iterator.next();
142           if (actionManagerName.equals(description.getPath())) {
143             iterator.remove();
144           }
145         }
146         if (!descriptions.isEmpty()) {
147           if (optionDescriptions == null) {
148             optionDescriptions = descriptions;
149           }
150           else {
151             optionDescriptions.retainAll(descriptions);
152           }
153         }
154       } else {
155         optionDescriptions = null;
156         break;
157       }
158     }
159     if (optionDescriptions != null && !optionDescriptions.isEmpty()) {
160       Set<String> currentHits = new HashSet<String>();
161       for (Iterator<OptionDescription> iterator = optionDescriptions.iterator(); iterator.hasNext(); ) {
162         OptionDescription description = iterator.next();
163         final String hit = description.getHit();
164         if (hit == null || !currentHits.add(hit.trim())) {
165           iterator.remove();
166         }
167       }
168       for (OptionDescription description : optionDescriptions) {
169         for (ActionFromOptionDescriptorProvider converter : ActionFromOptionDescriptorProvider.EP.getExtensions()) {
170           AnAction action = converter.provide(description);
171           if (action != null) options.add(new ActionWrapper(action, null, MatchMode.NAME, dataContext));
172           options.add(description);
173         }
174       }
175     }
176     return processItems(pattern, options, consumer);
177   }
178
179   private boolean processActions(String pattern, boolean everywhere, Processor<MatchedValue> consumer, DataContext dataContext) {
180     List<AnAction> actions = ContainerUtil.newArrayList();
181     if (everywhere) {
182       for (String id : ((ActionManagerImpl)myActionManager).getActionIds()) {
183         ProgressManager.checkCanceled();
184         ContainerUtil.addIfNotNull(actions, myActionManager.getAction(id));
185       }
186     } else {
187       actions.addAll(myModel.myActionGroups.keySet());
188     }
189
190     List<ActionWrapper> actionWrappers = ContainerUtil.newArrayList();
191     for (AnAction action : actions) {
192       ProgressManager.checkCanceled();
193       MatchMode mode = myModel.actionMatches(pattern, action);
194       if (mode != MatchMode.NONE) {
195         actionWrappers.add(new ActionWrapper(action, myModel.myActionGroups.get(action), mode, dataContext));
196       }
197     }
198     return processItems(pattern, actionWrappers, consumer);
199   }
200
201   private boolean processIntentions(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) {
202     List<ActionWrapper> intentions = ContainerUtil.newArrayList();
203     for (String intentionText : myModel.myIntentions.keySet()) {
204       final ApplyIntentionAction intentionAction = myModel.myIntentions.get(intentionText);
205       if (myModel.actionMatches(pattern, intentionAction) != MatchMode.NONE) {
206         intentions.add(new ActionWrapper(intentionAction, intentionText, MatchMode.INTENTION, dataContext));
207       }
208     }
209     return processItems(pattern, intentions, consumer);
210   }
211
212   private static boolean processItems(final String pattern, Collection<? extends Comparable> items, Processor<MatchedValue> consumer) {
213     List<MatchedValue> matched = ContainerUtil.map(items, new Function<Comparable, MatchedValue>() {
214       @Override
215       public MatchedValue fun(Comparable comparable) {
216         return new MatchedValue(comparable, pattern);
217       }
218     });
219     Collections.sort(matched);
220     return ContainerUtil.process(matched, consumer);
221   }
222
223 }