5090a0a51c27d45b61eaa9f030789ae72b90d714
[idea/community.git] / platform / lang-impl / src / com / intellij / ide / util / gotoByName / GotoActionItemProvider.java
1 /*
2  * Copyright 2000-2015 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.Comparing;
32 import com.intellij.openapi.util.text.StringUtil;
33 import com.intellij.util.CollectConsumer;
34 import com.intellij.util.Function;
35 import com.intellij.util.Processor;
36 import com.intellij.util.containers.ContainerUtil;
37 import gnu.trove.THashSet;
38 import gnu.trove.TObjectHashingStrategy;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
41
42 import java.util.*;
43
44 import static com.intellij.ide.util.gotoByName.GotoActionModel.*;
45
46 /**
47  * @author peter
48  */
49 public class GotoActionItemProvider implements ChooseByNameItemProvider {
50   private static final TObjectHashingStrategy<AnAction> ACTION_OBJECT_HASHING_STRATEGY = new TObjectHashingStrategy<AnAction>() {
51     @Override
52     public int computeHashCode(AnAction object) {
53       return object.getClass().hashCode();
54     }
55
56     @Override
57     public boolean equals(AnAction o1, AnAction o2) {
58       return Comparing.equal(o1.getClass(), o2.getClass());
59     }
60   };
61   private final ActionManager myActionManager = ActionManager.getInstance();
62   protected final SearchableOptionsRegistrar myIndex = SearchableOptionsRegistrar.getInstance();
63   private final GotoActionModel myModel;
64
65   public GotoActionItemProvider(GotoActionModel model) {
66     myModel = model;
67   }
68
69   @NotNull
70   @Override
71   public List<String> filterNames(@NotNull ChooseByNameBase base, @NotNull String[] names, @NotNull String pattern) {
72     return Collections.emptyList(); // no common prefix insertion in goto action
73   }
74
75   @Override
76   public boolean filterElements(@NotNull final ChooseByNameBase base,
77                                 @NotNull final String pattern,
78                                 boolean everywhere,
79                                 @NotNull ProgressIndicator cancelled,
80                                 @NotNull final Processor<Object> consumer) {
81     return filterElements(pattern, everywhere, new Processor<MatchedValue>() {
82       @Override
83       public boolean process(MatchedValue value) {
84         return consumer.process(value);
85       }
86     });
87   }
88
89   public boolean filterElements(String pattern, boolean everywhere, Processor<MatchedValue> consumer) {
90     DataContext dataContext = DataManager.getInstance().getDataContext(myModel.getContextComponent());
91
92     if (!processAbbreviations(pattern, consumer, dataContext)) return false;
93     if (!processIntentions(pattern, consumer, dataContext)) return false;
94     if (!processActions(pattern, everywhere, consumer, dataContext)) return false;
95     if (!processTopHits(pattern, consumer, dataContext)) return false;
96     if (!processOptions(pattern, consumer, dataContext)) return false;
97
98     return true;
99   }
100
101   private boolean processAbbreviations(final String pattern, Processor<MatchedValue> consumer, DataContext context) {
102     List<String> actions = AbbreviationManager.getInstance().findActions(pattern);
103     if (actions.isEmpty()) return true;
104     List<ActionWrapper> wrappers = ContainerUtil.newArrayListWithCapacity(actions.size());
105     for (String actionId : actions) {
106       AnAction action = myActionManager.getAction(actionId);
107       wrappers.add(new ActionWrapper(action, myModel.myActionGroups.get(action), MatchMode.NAME, context));
108     }
109     return ContainerUtil.process(ContainerUtil.map(wrappers, new Function<ActionWrapper, MatchedValue>() {
110       @Override
111       public MatchedValue fun(@NotNull ActionWrapper w) {
112         return new MatchedValue(w, pattern) {
113           @Nullable
114           @Override
115           public String getValueText() {
116             return pattern;
117           }
118         };
119       }
120     }), consumer);
121   }
122
123   private static boolean processTopHits(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) {
124     Project project = CommonDataKeys.PROJECT.getData(dataContext);
125     final CollectConsumer<Object> collector = new CollectConsumer<Object>();
126     for (SearchTopHitProvider provider : SearchTopHitProvider.EP_NAME.getExtensions()) {
127       if (provider instanceof OptionsTopHitProvider.CoveredByToggleActions) continue;
128       if (provider instanceof OptionsTopHitProvider && !((OptionsTopHitProvider)provider).isEnabled(project)) continue;
129       if (provider instanceof OptionsTopHitProvider && !StringUtil.startsWith(pattern, "#")) {
130         String prefix = "#" + ((OptionsTopHitProvider)provider).getId() + " ";
131         provider.consumeTopHits(prefix + pattern, collector, project);
132       }
133       provider.consumeTopHits(pattern, collector, project);
134     }
135     final Collection<Object> result = collector.getResult();
136     final List<Comparable> c = new ArrayList<Comparable>();
137     for (Object o : result) {
138       if (o instanceof Comparable) {
139         c.add((Comparable)o);
140       }
141     }
142     return processItems(pattern, c, consumer);
143   }
144
145   private boolean processOptions(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) {
146     List<Comparable> options = ContainerUtil.newArrayList();
147     final Set<String> words = myIndex.getProcessedWords(pattern);
148     Set<OptionDescription> optionDescriptions = null;
149     final String actionManagerName = myActionManager.getComponentName();
150     for (String word : words) {
151       final Set<OptionDescription> descriptions = ((SearchableOptionsRegistrarImpl)myIndex).getAcceptableDescriptions(word);
152       if (descriptions != null) {
153         for (Iterator<OptionDescription> iterator = descriptions.iterator(); iterator.hasNext(); ) {
154           OptionDescription description = iterator.next();
155           if (actionManagerName.equals(description.getPath())) {
156             iterator.remove();
157           }
158         }
159         if (!descriptions.isEmpty()) {
160           if (optionDescriptions == null) {
161             optionDescriptions = descriptions;
162           }
163           else {
164             optionDescriptions.retainAll(descriptions);
165           }
166         }
167       } else {
168         optionDescriptions = null;
169         break;
170       }
171     }
172     if (optionDescriptions != null && !optionDescriptions.isEmpty()) {
173       Set<String> currentHits = new HashSet<String>();
174       for (Iterator<OptionDescription> iterator = optionDescriptions.iterator(); iterator.hasNext(); ) {
175         OptionDescription description = iterator.next();
176         final String hit = description.getHit();
177         if (hit == null || !currentHits.add(hit.trim())) {
178           iterator.remove();
179         }
180       }
181       for (OptionDescription description : optionDescriptions) {
182         for (ActionFromOptionDescriptorProvider converter : ActionFromOptionDescriptorProvider.EP.getExtensions()) {
183           AnAction action = converter.provide(description);
184           if (action != null) options.add(new ActionWrapper(action, null, MatchMode.NAME, dataContext));
185           options.add(description);
186         }
187       }
188     }
189     return processItems(pattern, options, consumer);
190   }
191
192   private boolean processActions(String pattern, boolean everywhere, Processor<MatchedValue> consumer, DataContext dataContext) {
193     Set<AnAction> actions = new THashSet<AnAction>(ACTION_OBJECT_HASHING_STRATEGY);
194     if (everywhere) {
195       for (String id : ((ActionManagerImpl)myActionManager).getActionIds()) {
196         ProgressManager.checkCanceled();
197         ContainerUtil.addIfNotNull(actions, myActionManager.getAction(id));
198       }
199     } else {
200       actions.addAll(myModel.myActionGroups.keySet());
201     }
202
203     List<ActionWrapper> actionWrappers = ContainerUtil.newArrayList();
204     for (AnAction action : actions) {
205       ProgressManager.checkCanceled();
206       MatchMode mode = myModel.actionMatches(pattern, action);
207       if (mode != MatchMode.NONE) {
208         actionWrappers.add(new ActionWrapper(action, myModel.myActionGroups.get(action), mode, dataContext));
209       }
210     }
211     return processItems(pattern, actionWrappers, consumer);
212   }
213
214   private boolean processIntentions(String pattern, Processor<MatchedValue> consumer, DataContext dataContext) {
215     List<ActionWrapper> intentions = ContainerUtil.newArrayList();
216     for (String intentionText : myModel.myIntentions.keySet()) {
217       final ApplyIntentionAction intentionAction = myModel.myIntentions.get(intentionText);
218       if (myModel.actionMatches(pattern, intentionAction) != MatchMode.NONE) {
219         intentions.add(new ActionWrapper(intentionAction, intentionText, MatchMode.INTENTION, dataContext));
220       }
221     }
222     return processItems(pattern, intentions, consumer);
223   }
224
225   private static boolean processItems(final String pattern, Collection<? extends Comparable> items, Processor<MatchedValue> consumer) {
226     List<MatchedValue> matched = ContainerUtil.map(items, new Function<Comparable, MatchedValue>() {
227       @Override
228       public MatchedValue fun(Comparable comparable) {
229         return new MatchedValue(comparable, pattern);
230       }
231     });
232     Collections.sort(matched);
233     return ContainerUtil.process(matched, consumer);
234   }
235
236 }