2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.keymap.impl.ui;
18 import com.intellij.icons.AllIcons;
19 import com.intellij.ide.DataManager;
20 import com.intellij.ide.actionMacro.ActionMacro;
21 import com.intellij.ide.plugins.IdeaPluginDescriptor;
22 import com.intellij.ide.plugins.PluginManagerCore;
23 import com.intellij.ide.ui.search.SearchUtil;
24 import com.intellij.openapi.actionSystem.*;
25 import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
26 import com.intellij.openapi.actionSystem.ex.QuickList;
27 import com.intellij.openapi.diagnostic.Logger;
28 import com.intellij.openapi.extensions.Extensions;
29 import com.intellij.openapi.extensions.PluginId;
30 import com.intellij.openapi.keymap.KeyMapBundle;
31 import com.intellij.openapi.keymap.Keymap;
32 import com.intellij.openapi.keymap.KeymapExtension;
33 import com.intellij.openapi.keymap.ex.KeymapManagerEx;
34 import com.intellij.openapi.keymap.impl.ActionShortcutRestrictions;
35 import com.intellij.openapi.keymap.impl.KeymapImpl;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.util.Condition;
38 import com.intellij.openapi.util.registry.Registry;
39 import com.intellij.openapi.util.text.StringUtil;
40 import com.intellij.util.containers.ContainerUtil;
41 import org.jetbrains.annotations.NonNls;
42 import org.jetbrains.annotations.Nullable;
45 import javax.swing.tree.DefaultMutableTreeNode;
48 public class ActionsTreeUtil {
49 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.keymap.impl.ui.ActionsTreeUtil");
51 public static final String MAIN_MENU_TITLE = KeyMapBundle.message("main.menu.action.title");
52 public static final String MAIN_TOOLBAR = KeyMapBundle.message("main.toolbar.title");
53 public static final String EDITOR_POPUP = KeyMapBundle.message("editor.popup.menu.title");
55 public static final String EDITOR_TAB_POPUP = KeyMapBundle.message("editor.tab.popup.menu.title");
56 public static final String FAVORITES_POPUP = KeyMapBundle.message("favorites.popup.title");
57 public static final String PROJECT_VIEW_POPUP = KeyMapBundle.message("project.view.popup.menu.title");
58 public static final String COMMANDER_POPUP = KeyMapBundle.message("commender.view.popup.menu.title");
59 public static final String J2EE_POPUP = KeyMapBundle.message("j2ee.view.popup.menu.title");
62 private static final String EDITOR_PREFIX = "Editor";
63 @NonNls private static final String TOOL_ACTION_PREFIX = "Tool_";
65 private ActionsTreeUtil() {
68 public static Map<String, String> createPluginActionsMap() {
69 Set<PluginId> visited = ContainerUtil.newHashSet();
70 Map<String, String> result = ContainerUtil.newHashMap();
71 for (IdeaPluginDescriptor descriptor : PluginManagerCore.getPlugins()) {
72 PluginId id = descriptor.getPluginId();
74 if (PluginManagerCore.CORE_PLUGIN_ID.equals(id.getIdString())) continue;
75 for (String actionId : ActionManagerEx.getInstanceEx().getPluginActions(id)) {
76 result.put(actionId, descriptor.getName());
79 for (PluginId id : PluginId.getRegisteredIds().values()) {
80 if (visited.contains(id)) continue;
81 for (String actionId : ActionManagerEx.getInstanceEx().getPluginActions(id)) {
82 result.put(actionId, id.getIdString());
88 private static Group createPluginsActionsGroup(Condition<AnAction> filtered) {
89 Group pluginsGroup = new Group(KeyMapBundle.message("plugins.group.title"), null, null);
90 final KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
91 ActionManagerEx managerEx = ActionManagerEx.getInstanceEx();
92 final List<IdeaPluginDescriptor> plugins = new ArrayList<>();
93 Collections.addAll(plugins, PluginManagerCore.getPlugins());
94 Collections.sort(plugins, (o1, o2) -> o1.getName().compareTo(o2.getName()));
96 List<PluginId> collected = new ArrayList<>();
97 for (IdeaPluginDescriptor plugin : plugins) {
98 collected.add(plugin.getPluginId());
100 if (plugin.getName().equals("IDEA CORE")) {
104 pluginGroup = new Group(plugin.getName(), null, null);
106 final String[] pluginActions = managerEx.getPluginActions(plugin.getPluginId());
107 if (pluginActions == null || pluginActions.length == 0) {
110 Arrays.sort(pluginActions, (o1, o2) -> getTextToCompare(o1).compareTo(getTextToCompare(o2)));
111 for (String pluginAction : pluginActions) {
112 if (keymapManager.getBoundActions().contains(pluginAction)) continue;
113 final AnAction anAction = managerEx.getActionOrStub(pluginAction);
114 if (filtered == null || filtered.value(anAction)) {
115 pluginGroup.addActionId(pluginAction);
118 if (pluginGroup.getSize() > 0) {
119 pluginsGroup.addGroup(pluginGroup);
123 for (PluginId pluginId : PluginId.getRegisteredIds().values()) {
124 if (collected.contains(pluginId)) continue;
125 Group pluginGroup = new Group(pluginId.getIdString(), null, null);
126 final String[] pluginActions = managerEx.getPluginActions(pluginId);
127 if (pluginActions == null || pluginActions.length == 0) {
130 for (String pluginAction : pluginActions) {
131 if (keymapManager.getBoundActions().contains(pluginAction)) continue;
132 final AnAction anAction = managerEx.getActionOrStub(pluginAction);
133 if (filtered == null || filtered.value(anAction)) {
134 pluginGroup.addActionId(pluginAction);
137 if (pluginGroup.getSize() > 0) {
138 pluginsGroup.addGroup(pluginGroup);
145 private static Group createMainMenuGroup(Condition<AnAction> filtered) {
146 Group group = new Group(MAIN_MENU_TITLE, IdeActions.GROUP_MAIN_MENU, AllIcons.Nodes.KeymapMainMenu);
147 ActionGroup mainMenuGroup = (ActionGroup)ActionManager.getInstance().getActionOrStub(IdeActions.GROUP_MAIN_MENU);
148 fillGroupIgnorePopupFlag(mainMenuGroup, group, filtered);
153 private static Condition<AnAction> wrapFilter(@Nullable final Condition<AnAction> filter, final Keymap keymap, final ActionManager actionManager) {
154 final ActionShortcutRestrictions shortcutRestrictions = ActionShortcutRestrictions.getInstance();
156 if (action == null) return false;
157 final String id = action instanceof ActionStub ? ((ActionStub)action).getId() : actionManager.getId(action);
159 if (!Registry.is("keymap.show.alias.actions")) {
160 String binding = getActionBinding(keymap, id);
161 boolean bound = binding != null
162 && actionManager.getAction(binding) != null // do not hide bound action, that miss the 'bound-with'
163 && !hasAssociatedShortcutsInHierarchy(id, keymap); // do not hide bound actions when they are redefined
168 if (!shortcutRestrictions.getForActionId(id).allowChanging) {
173 return filter == null || filter.value(action);
177 private static boolean hasAssociatedShortcutsInHierarchy(String id, Keymap keymap) {
178 while (keymap != null) {
179 if (((KeymapImpl)keymap).hasOwnActionId(id)) return true;
180 keymap = keymap.getParent();
185 private static void fillGroupIgnorePopupFlag(ActionGroup actionGroup, Group group, Condition<AnAction> filtered) {
186 AnAction[] mainMenuTopGroups = actionGroup instanceof DefaultActionGroup
187 ? ((DefaultActionGroup)actionGroup).getChildActionsOrStubs()
188 : actionGroup.getChildren(null);
189 for (AnAction action : mainMenuTopGroups) {
190 if (!(action instanceof ActionGroup)) continue;
191 Group subGroup = createGroup((ActionGroup)action, false, filtered);
192 if (subGroup.getSize() > 0) {
193 group.addGroup(subGroup);
198 public static Group createGroup(ActionGroup actionGroup, boolean ignore, Condition<AnAction> filtered) {
199 return createGroup(actionGroup, getName(actionGroup), null, null, ignore, filtered);
202 private static String getName(AnAction action) {
203 final String name = action.getTemplatePresentation().getText();
204 if (name != null && !name.isEmpty()) {
208 final String id = action instanceof ActionStub ? ((ActionStub)action).getId() : ActionManager.getInstance().getId(action);
212 if (action instanceof DefaultActionGroup) {
213 final DefaultActionGroup group = (DefaultActionGroup)action;
214 if (group.getChildrenCount() == 0) return "Empty group";
215 final AnAction[] children = group.getChildActionsOrStubs();
216 for (AnAction child : children) {
217 if (!(child instanceof Separator)) {
218 return "group." + getName(child);
221 return "Empty unnamed group";
223 return action.getClass().getName();
227 public static Group createGroup(ActionGroup actionGroup,
232 Condition<AnAction> filtered) {
233 return createGroup(actionGroup, groupName, icon, openIcon, ignore, filtered, true);
236 public static Group createGroup(ActionGroup actionGroup, String groupName, Icon icon, Icon openIcon, boolean ignore, Condition<AnAction> filtered,
237 boolean normalizeSeparators) {
238 ActionManager actionManager = ActionManager.getInstance();
239 Group group = new Group(groupName, actionManager.getId(actionGroup), icon);
240 AnAction[] children = actionGroup instanceof DefaultActionGroup
241 ? ((DefaultActionGroup)actionGroup).getChildActionsOrStubs()
242 : actionGroup.getChildren(null);
244 for (AnAction action : children) {
245 if (action == null) {
246 LOG.error(groupName + " contains null actions");
249 if (action instanceof ActionGroup) {
250 Group subGroup = createGroup((ActionGroup)action, getName(action), null, null, ignore, filtered, normalizeSeparators);
251 if (subGroup.getSize() > 0) {
252 if (!ignore && !((ActionGroup)action).isPopup()) {
253 group.addAll(subGroup);
256 group.addGroup(subGroup);
259 else if (filtered == null || filtered.value(action)) {
260 group.addGroup(subGroup);
263 else if (action instanceof Separator) {
264 group.addSeparator();
267 String id = action instanceof ActionStub ? ((ActionStub)action).getId() : actionManager.getId(action);
269 if (id.startsWith(TOOL_ACTION_PREFIX)) continue;
270 if (filtered == null || filtered.value(action)) {
271 group.addActionId(id);
276 if (normalizeSeparators) group.normalizeSeparators();
280 private static Group createEditorActionsGroup(Condition<AnAction> filtered) {
281 ActionManager actionManager = ActionManager.getInstance();
282 DefaultActionGroup editorGroup = (DefaultActionGroup)actionManager.getActionOrStub(IdeActions.GROUP_EDITOR);
283 ArrayList<String> ids = new ArrayList<>();
285 addEditorActions(filtered, editorGroup, ids);
287 Collections.sort(ids);
288 Group group = new Group(KeyMapBundle.message("editor.actions.group.title"), IdeActions.GROUP_EDITOR, AllIcons.Nodes.KeymapEditor
290 for (String id : ids) {
291 group.addActionId(id);
298 private static String getActionBinding(final Keymap keymap, final String id) {
299 if (keymap == null) return null;
301 Keymap parent = keymap.getParent();
302 String result = ((KeymapImpl)keymap).getActionBinding(id);
303 if (result == null && parent != null) {
304 result = ((KeymapImpl)parent).getActionBinding(id);
309 private static void addEditorActions(final Condition<AnAction> filtered,
310 final DefaultActionGroup editorGroup,
311 final ArrayList<String> ids) {
312 AnAction[] editorActions = editorGroup.getChildActionsOrStubs();
313 final ActionManager actionManager = ActionManager.getInstance();
314 for (AnAction editorAction : editorActions) {
315 if (editorAction instanceof DefaultActionGroup) {
316 addEditorActions(filtered, (DefaultActionGroup) editorAction, ids);
319 String actionId = editorAction instanceof ActionStub ? ((ActionStub)editorAction).getId() : actionManager.getId(editorAction);
320 if (actionId == null) continue;
321 if (filtered == null || filtered.value(editorAction)) {
328 private static Group createExtensionGroup(Condition<AnAction> filtered, final Project project, KeymapExtension provider) {
329 return (Group) provider.createGroup(filtered, project);
332 private static Group createMacrosGroup(Condition<AnAction> filtered) {
333 final ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
334 String[] ids = actionManager.getActionIds(ActionMacro.MACRO_ACTION_PREFIX);
336 Group group = new Group(KeyMapBundle.message("macros.group.title"), null, null);
337 for (String id : ids) {
338 if (filtered == null || filtered.value(actionManager.getActionOrStub(id))) {
339 group.addActionId(id);
345 private static Group createQuickListsGroup(final Condition<AnAction> filtered, final String filter, final boolean forceFiltering, final QuickList[] quickLists) {
346 Arrays.sort(quickLists, (l1, l2) -> l1.getActionId().compareTo(l2.getActionId()));
348 Group group = new Group(KeyMapBundle.message("quick.lists.group.title"), null, null);
349 for (QuickList quickList : quickLists) {
350 if (filtered != null && filtered.value(ActionManagerEx.getInstanceEx().getAction(quickList.getActionId()))) {
351 group.addQuickList(quickList);
352 } else if (SearchUtil.isComponentHighlighted(quickList.getName(), filter, forceFiltering, null)) {
353 group.addQuickList(quickList);
354 } else if (filtered == null && StringUtil.isEmpty(filter)) {
355 group.addQuickList(quickList);
362 private static Group createOtherGroup(Condition<AnAction> filtered, Group addedActions, final Keymap keymap) {
363 addedActions.initIds();
364 ArrayList<String> result = new ArrayList<>();
366 if (keymap != null) {
367 String[] actionIds = keymap.getActionIds();
368 for (String id : actionIds) {
369 if (id.startsWith(EDITOR_PREFIX)) {
370 AnAction action = ActionManager.getInstance().getActionOrStub("$" + id.substring(6));
371 if (action != null) continue;
374 if (!id.startsWith(QuickList.QUICK_LIST_PREFIX) && !addedActions.containsId(id)) {
380 // add all registered actions
381 final ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
382 final KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
383 String[] registeredActionIds = actionManager.getActionIds("");
384 for (String id : registeredActionIds) {
385 final AnAction actionOrStub = actionManager.getActionOrStub(id);
386 if (actionOrStub instanceof ActionGroup && !((ActionGroup)actionOrStub).canBePerformed(DataManager.getInstance().getDataContext())) {
389 if (id.startsWith(QuickList.QUICK_LIST_PREFIX) || addedActions.containsId(id) || result.contains(id)) {
393 if (keymapManager.getBoundActions().contains(id)) continue;
398 filterOtherActionsGroup(result);
400 ContainerUtil.quickSort(result, (id1, id2) -> getTextToCompare(id1).compareToIgnoreCase(getTextToCompare(id2)));
402 Group group = new Group(KeyMapBundle.message("other.group.title"), AllIcons.Nodes.KeymapOther);
403 for (String id : result) {
404 if (filtered == null || filtered.value(actionManager.getActionOrStub(id))) group.addActionId(id);
409 private static String getTextToCompare(String id) {
410 AnAction action = ActionManager.getInstance().getActionOrStub(id);
411 if (action == null) {
414 String text = action.getTemplatePresentation().getText();
415 return text != null ? text : id;
418 private static void filterOtherActionsGroup(ArrayList<String> actions) {
419 filterOutGroup(actions, IdeActions.GROUP_GENERATE);
420 filterOutGroup(actions, IdeActions.GROUP_NEW);
421 filterOutGroup(actions, IdeActions.GROUP_CHANGE_SCHEME);
424 private static void filterOutGroup(ArrayList<String> actions, String groupId) {
425 if (groupId == null) {
426 throw new IllegalArgumentException();
428 ActionManager actionManager = ActionManager.getInstance();
429 AnAction action = actionManager.getActionOrStub(groupId);
430 if (action instanceof DefaultActionGroup) {
431 DefaultActionGroup group = (DefaultActionGroup)action;
432 AnAction[] children = group.getChildActionsOrStubs();
433 for (AnAction child : children) {
434 String childId = child instanceof ActionStub ? ((ActionStub)child).getId() : actionManager.getId(child);
435 if (childId == null) {
439 if (child instanceof DefaultActionGroup) {
440 filterOutGroup(actions, childId);
443 actions.remove(childId);
449 public static DefaultMutableTreeNode createNode(Group group) {
450 DefaultMutableTreeNode node = new DefaultMutableTreeNode(group);
451 for (Object child : group.getChildren()) {
452 if (child instanceof Group) {
453 DefaultMutableTreeNode childNode = createNode((Group)child);
457 LOG.assertTrue(child != null);
458 node.add(new DefaultMutableTreeNode(child));
464 public static Group createMainGroup(final Project project, final Keymap keymap, final QuickList[] quickLists) {
465 return createMainGroup(project, keymap, quickLists, null, false, null);
468 public static Group createMainGroup(final Project project,
470 final QuickList[] quickLists,
472 final boolean forceFiltering,
473 final Condition<AnAction> filtered) {
474 final Condition<AnAction> wrappedFilter = wrapFilter(filtered, keymap, ActionManager.getInstance());
475 Group mainGroup = new Group(KeyMapBundle.message("all.actions.group.title"), null, null);
476 mainGroup.addGroup(createEditorActionsGroup(wrappedFilter));
477 mainGroup.addGroup(createMainMenuGroup(wrappedFilter));
478 for (KeymapExtension extension : Extensions.getExtensions(KeymapExtension.EXTENSION_POINT_NAME)) {
479 final Group group = createExtensionGroup(wrappedFilter, project, extension);
481 mainGroup.addGroup(group);
484 mainGroup.addGroup(createMacrosGroup(wrappedFilter));
485 mainGroup.addGroup(createQuickListsGroup(wrappedFilter, filter, forceFiltering, quickLists));
486 mainGroup.addGroup(createPluginsActionsGroup(wrappedFilter));
487 mainGroup.addGroup(createOtherGroup(wrappedFilter, mainGroup, keymap));
488 if (!StringUtil.isEmpty(filter) || filtered != null) {
489 final ArrayList list = mainGroup.getChildren();
490 for (Iterator i = list.iterator(); i.hasNext();) {
491 final Object o = i.next();
492 if (o instanceof Group) {
493 final Group group = (Group)o;
494 if (group.getSize() == 0) {
495 if (!SearchUtil.isComponentHighlighted(group.getName(), filter, forceFiltering, null)) {
505 public static Condition<AnAction> isActionFiltered(final String filter, final boolean force) {
507 if (filter == null) return true;
508 if (action == null) return false;
509 final String insensitiveFilter = filter.toLowerCase();
510 ArrayList<String> options = new ArrayList<>();
511 options.add(action.getTemplatePresentation().getText());
512 options.add(action.getTemplatePresentation().getDescription());
513 options.add(action instanceof ActionStub ? ((ActionStub)action).getId() : ActionManager.getInstance().getId(action));
514 options.addAll(AbbreviationManager.getInstance().getAbbreviations(ActionManager.getInstance().getId(action)));
516 for (String text : options) {
518 final String lowerText = text.toLowerCase();
520 if (SearchUtil.isComponentHighlighted(lowerText, insensitiveFilter, force, null)) {
523 else if (lowerText.contains(insensitiveFilter)) {
532 public static Condition<AnAction> isActionFiltered(final ActionManager actionManager,
534 final Shortcut shortcut) {
536 if (shortcut == null) return true;
537 if (action == null) return false;
538 final Shortcut[] actionShortcuts =
539 keymap.getShortcuts(action instanceof ActionStub ? ((ActionStub)action).getId() : actionManager.getId(action));
540 for (Shortcut actionShortcut : actionShortcuts) {
541 if (actionShortcut != null && actionShortcut.startsWith(shortcut)) {
549 public static Condition<AnAction> isActionFiltered(final ActionManager actionManager,
551 final Shortcut shortcut,
553 final boolean force) {
554 return filter != null && filter.length() > 0 ? isActionFiltered(filter, force) :
555 shortcut != null ? isActionFiltered(actionManager, keymap, shortcut) : null;