mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-07-24 02:59:02 +02:00
583 lines
23 KiB
Java
583 lines
23 KiB
Java
/*
|
|
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
|
* Copyright (C) 2003-2022 The IdeaVim authors
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package com.maddyhome.idea.vim.group;
|
|
|
|
import com.google.common.collect.ImmutableList;
|
|
import com.intellij.codeInsight.lookup.impl.LookupImpl;
|
|
import com.intellij.openapi.actionSystem.*;
|
|
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
|
|
import com.intellij.openapi.actionSystem.ex.ActionUtil;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.components.PersistentStateComponent;
|
|
import com.intellij.openapi.components.State;
|
|
import com.intellij.openapi.components.Storage;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.editor.Editor;
|
|
import com.intellij.openapi.keymap.Keymap;
|
|
import com.intellij.openapi.keymap.KeymapManager;
|
|
import com.intellij.openapi.keymap.ex.KeymapManagerEx;
|
|
import com.maddyhome.idea.vim.EventFacade;
|
|
import com.maddyhome.idea.vim.VimPlugin;
|
|
import com.maddyhome.idea.vim.action.ComplicatedKeysAction;
|
|
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
|
|
import com.maddyhome.idea.vim.api.VimKeyGroup;
|
|
import com.maddyhome.idea.vim.common.*;
|
|
import com.maddyhome.idea.vim.ex.ExOutputModel;
|
|
import com.maddyhome.idea.vim.extension.VimExtensionHandler;
|
|
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
|
import com.maddyhome.idea.vim.helper.HelperKt;
|
|
import com.maddyhome.idea.vim.helper.StringHelper;
|
|
import com.maddyhome.idea.vim.key.*;
|
|
import com.maddyhome.idea.vim.newapi.IjVimActionsInitiator;
|
|
import com.maddyhome.idea.vim.api.VimActionsInitiator;
|
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression;
|
|
import kotlin.Pair;
|
|
import kotlin.text.StringsKt;
|
|
import org.jdom.Element;
|
|
import org.jetbrains.annotations.NonNls;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.event.KeyEvent;
|
|
import java.util.List;
|
|
import java.util.*;
|
|
|
|
import static com.maddyhome.idea.vim.helper.StringHelper.toKeyNotation;
|
|
import static java.util.stream.Collectors.joining;
|
|
import static java.util.stream.Collectors.toList;
|
|
|
|
/**
|
|
* @author vlan
|
|
*/
|
|
@State(name = "VimKeySettings", storages = {@Storage(value = "$APP_CONFIG$/vim_settings.xml")})
|
|
public class KeyGroup implements PersistentStateComponent<Element>, VimKeyGroup {
|
|
public static final @NonNls String SHORTCUT_CONFLICTS_ELEMENT = "shortcut-conflicts";
|
|
private static final @NonNls String SHORTCUT_CONFLICT_ELEMENT = "shortcut-conflict";
|
|
private static final @NonNls String OWNER_ATTRIBUTE = "owner";
|
|
private static final String TEXT_ELEMENT = "text";
|
|
|
|
private static final Logger logger = Logger.getInstance(KeyGroup.class);
|
|
|
|
// It should be ShortcutOwnerInfo, but we use Object to keep the compatibility with easymotion
|
|
private final @NotNull Map<KeyStroke, Object> shortcutConflicts = new LinkedHashMap<>();
|
|
private final @NotNull Set<RequiredShortcut> requiredShortcutKeys = new HashSet<>(300);
|
|
private final @NotNull Map<MappingMode, CommandPartNode<VimActionsInitiator>> keyRoots = new EnumMap<>(MappingMode.class);
|
|
private final @NotNull Map<MappingMode, KeyMapping> keyMappings = new EnumMap<>(MappingMode.class);
|
|
private @Nullable OperatorFunction operatorFunction = null;
|
|
|
|
void registerRequiredShortcutKeys(@NotNull Editor editor) {
|
|
EventFacade.getInstance()
|
|
.registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(requiredShortcutKeys),
|
|
editor.getComponent());
|
|
}
|
|
|
|
public void registerShortcutsForLookup(@NotNull LookupImpl lookup) {
|
|
EventFacade.getInstance()
|
|
.registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(requiredShortcutKeys),
|
|
lookup.getComponent(), lookup);
|
|
}
|
|
|
|
void unregisterShortcutKeys(@NotNull Editor editor) {
|
|
EventFacade.getInstance().unregisterCustomShortcutSet(VimShortcutKeyAction.getInstance(), editor.getComponent());
|
|
}
|
|
|
|
public boolean showKeyMappings(@NotNull Set<MappingMode> modes, @NotNull Editor editor) {
|
|
List<Pair<EnumSet<MappingMode>, MappingInfo>> rows = getKeyMappingRows(modes);
|
|
final StringBuilder builder = new StringBuilder();
|
|
for (Pair<EnumSet<MappingMode>, MappingInfo> row : rows) {
|
|
MappingInfo mappingInfo = row.getSecond();
|
|
builder.append(StringsKt.padEnd(getModesStringCode(row.getFirst()), 2, ' '));
|
|
builder.append(" ");
|
|
builder.append(StringsKt.padEnd(toKeyNotation(mappingInfo.getFromKeys()), 11, ' '));
|
|
builder.append(" ");
|
|
builder.append(mappingInfo.isRecursive() ? " " : "*");
|
|
builder.append(" ");
|
|
builder.append(mappingInfo.getPresentableString());
|
|
builder.append("\n");
|
|
}
|
|
ExOutputModel.getInstance(editor).output(builder.toString());
|
|
return true;
|
|
}
|
|
|
|
public void removeKeyMapping(@NotNull MappingOwner owner) {
|
|
Arrays.stream(MappingMode.values()).map(this::getKeyMapping).forEach(o -> o.delete(owner));
|
|
unregisterKeyMapping(owner);
|
|
}
|
|
|
|
public void removeKeyMapping(@NotNull Set<MappingMode> modes, @NotNull List<KeyStroke> keys) {
|
|
modes.stream().map(this::getKeyMapping).forEach(o -> o.delete(keys));
|
|
}
|
|
|
|
public void removeKeyMapping(@NotNull Set<MappingMode> modes) {
|
|
modes.stream().map(this::getKeyMapping).forEach(KeyMapping::delete);
|
|
}
|
|
|
|
public void putKeyMapping(@NotNull Set<MappingMode> modes,
|
|
@NotNull List<KeyStroke> fromKeys,
|
|
@NotNull MappingOwner owner,
|
|
@NotNull VimExtensionHandler extensionHandler,
|
|
boolean recursive) {
|
|
modes.stream().map(this::getKeyMapping).forEach(o -> o.put(fromKeys, owner, extensionHandler, recursive));
|
|
registerKeyMapping(fromKeys, owner);
|
|
}
|
|
|
|
public void putKeyMapping(@NotNull Set<MappingMode> modes,
|
|
@NotNull List<KeyStroke> fromKeys,
|
|
@NotNull MappingOwner owner,
|
|
@NotNull List<KeyStroke> toKeys,
|
|
boolean recursive) {
|
|
modes.stream().map(this::getKeyMapping).forEach(o -> o.put(fromKeys, toKeys, owner, recursive));
|
|
registerKeyMapping(fromKeys, owner);
|
|
}
|
|
|
|
public void putKeyMapping(@NotNull Set<MappingMode> modes,
|
|
@NotNull List<KeyStroke> fromKeys,
|
|
@NotNull MappingOwner owner,
|
|
@NotNull Expression toExpr,
|
|
@NotNull String originalString,
|
|
boolean recursive) {
|
|
modes.stream().map(this::getKeyMapping).forEach(o -> o.put(fromKeys, toExpr, owner, originalString, recursive));
|
|
registerKeyMapping(fromKeys, owner);
|
|
}
|
|
|
|
public boolean hasmapto(@NotNull MappingMode mode, @NotNull List<KeyStroke> toKeys) {
|
|
return this.getKeyMapping(mode).hasmapto(toKeys);
|
|
}
|
|
|
|
public List<Pair<List<KeyStroke>, MappingInfo>> getMapTo(@NotNull MappingMode mode, @NotNull List<KeyStroke> toKeys) {
|
|
return this.getKeyMapping(mode).getMapTo(toKeys);
|
|
}
|
|
|
|
public List<Pair<List<KeyStroke>, MappingInfo>> getKeyMappingByOwner(@NotNull MappingOwner owner) {
|
|
return Arrays.stream(MappingMode.values()).map(this::getKeyMapping).flatMap(o -> o.getByOwner(owner).stream())
|
|
.collect(toList());
|
|
}
|
|
|
|
private void unregisterKeyMapping(MappingOwner owner) {
|
|
final int oldSize = requiredShortcutKeys.size();
|
|
requiredShortcutKeys.removeIf(requiredShortcut -> requiredShortcut.getOwner().equals(owner));
|
|
if (requiredShortcutKeys.size() != oldSize) {
|
|
for (Editor editor : HelperKt.localEditors()) {
|
|
unregisterShortcutKeys(editor);
|
|
registerRequiredShortcutKeys(editor);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void registerKeyMapping(@NotNull List<KeyStroke> fromKeys, MappingOwner owner) {
|
|
final int oldSize = requiredShortcutKeys.size();
|
|
for (KeyStroke key : fromKeys) {
|
|
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
|
|
requiredShortcutKeys.add(new RequiredShortcut(key, owner));
|
|
}
|
|
}
|
|
if (requiredShortcutKeys.size() != oldSize) {
|
|
for (Editor editor : HelperKt.localEditors()) {
|
|
unregisterShortcutKeys(editor);
|
|
registerRequiredShortcutKeys(editor);
|
|
}
|
|
}
|
|
}
|
|
|
|
public @Nullable OperatorFunction getOperatorFunction() {
|
|
return operatorFunction;
|
|
}
|
|
|
|
public void setOperatorFunction(@NotNull OperatorFunction function) {
|
|
operatorFunction = function;
|
|
}
|
|
|
|
public void saveData(@NotNull Element element) {
|
|
final Element conflictsElement = new Element(SHORTCUT_CONFLICTS_ELEMENT);
|
|
for (Map.Entry<KeyStroke, Object> entry : shortcutConflicts.entrySet()) {
|
|
final ShortcutOwner owner;
|
|
Object myValue = entry.getValue();
|
|
ShortcutOwnerInfo value;
|
|
if (myValue instanceof ShortcutOwnerInfo) {
|
|
value = (ShortcutOwnerInfo)myValue;
|
|
} else if (myValue instanceof ShortcutOwner) {
|
|
value = new ShortcutOwnerInfo.AllModes((ShortcutOwner)myValue);
|
|
} else {
|
|
value = new ShortcutOwnerInfo.AllModes(ShortcutOwner.VIM);
|
|
}
|
|
if (value instanceof ShortcutOwnerInfo.AllModes) {
|
|
owner = ((ShortcutOwnerInfo.AllModes)value).getOwner();
|
|
}
|
|
else if (value instanceof ShortcutOwnerInfo.PerMode) {
|
|
owner = null;
|
|
}
|
|
else {
|
|
throw new RuntimeException();
|
|
}
|
|
if (owner != null && owner != ShortcutOwner.UNDEFINED) {
|
|
final Element conflictElement = new Element(SHORTCUT_CONFLICT_ELEMENT);
|
|
conflictElement.setAttribute(OWNER_ATTRIBUTE, owner.getOwnerName());
|
|
final Element textElement = new Element(TEXT_ELEMENT);
|
|
StringHelper.setSafeXmlText(textElement, entry.getKey().toString());
|
|
conflictElement.addContent(textElement);
|
|
conflictsElement.addContent(conflictElement);
|
|
}
|
|
}
|
|
element.addContent(conflictsElement);
|
|
}
|
|
|
|
public void readData(@NotNull Element element) {
|
|
final Element conflictsElement = element.getChild(SHORTCUT_CONFLICTS_ELEMENT);
|
|
if (conflictsElement != null) {
|
|
final java.util.List<Element> conflictElements = conflictsElement.getChildren(SHORTCUT_CONFLICT_ELEMENT);
|
|
for (Element conflictElement : conflictElements) {
|
|
final String ownerValue = conflictElement.getAttributeValue(OWNER_ATTRIBUTE);
|
|
ShortcutOwner owner = ShortcutOwner.UNDEFINED;
|
|
try {
|
|
owner = ShortcutOwner.fromString(ownerValue);
|
|
}
|
|
catch (IllegalArgumentException ignored) {
|
|
}
|
|
final Element textElement = conflictElement.getChild(TEXT_ELEMENT);
|
|
if (textElement != null) {
|
|
final String text = StringHelper.getSafeXmlText(textElement);
|
|
if (text != null) {
|
|
final KeyStroke keyStroke = KeyStroke.getKeyStroke(text);
|
|
if (keyStroke != null) {
|
|
shortcutConflicts.put(keyStroke, new ShortcutOwnerInfo.AllModes(owner));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public @NotNull List<AnAction> getKeymapConflicts(@NotNull KeyStroke keyStroke) {
|
|
final KeymapManagerEx keymapManager = KeymapManagerEx.getInstanceEx();
|
|
final Keymap keymap = keymapManager.getActiveKeymap();
|
|
final KeyboardShortcut shortcut = new KeyboardShortcut(keyStroke, null);
|
|
final Map<String, ? extends List<KeyboardShortcut>> conflicts = keymap.getConflicts("", shortcut);
|
|
final List<AnAction> actions = new ArrayList<>();
|
|
for (String actionId : conflicts.keySet()) {
|
|
final AnAction action = ActionManagerEx.getInstanceEx().getAction(actionId);
|
|
if (action != null) {
|
|
actions.add(action);
|
|
}
|
|
}
|
|
return actions;
|
|
}
|
|
|
|
public @NotNull Map<KeyStroke, ShortcutOwnerInfo> getShortcutConflicts() {
|
|
final Set<RequiredShortcut> requiredShortcutKeys = this.requiredShortcutKeys;
|
|
final Map<KeyStroke, Object> savedConflicts = getSavedShortcutConflicts();
|
|
final Map<KeyStroke, ShortcutOwnerInfo> results = new HashMap<>();
|
|
for (RequiredShortcut requiredShortcut : requiredShortcutKeys) {
|
|
KeyStroke keyStroke = requiredShortcut.getKeyStroke();
|
|
if (!VimShortcutKeyAction.VIM_ONLY_EDITOR_KEYS.contains(keyStroke)) {
|
|
final List<AnAction> conflicts = getKeymapConflicts(keyStroke);
|
|
if (!conflicts.isEmpty()) {
|
|
final Object owner = savedConflicts.get(keyStroke);
|
|
ShortcutOwnerInfo result;
|
|
if (owner != null) {
|
|
if (owner instanceof ShortcutOwnerInfo) {
|
|
result = (ShortcutOwnerInfo)owner;
|
|
}
|
|
else if (owner instanceof ShortcutOwner) {
|
|
result = new ShortcutOwnerInfo.AllModes((ShortcutOwner)owner);
|
|
}
|
|
else {
|
|
result = ShortcutOwnerInfo.allUndefined;
|
|
}
|
|
}
|
|
else {
|
|
result = ShortcutOwnerInfo.allUndefined;
|
|
}
|
|
results.put(keyStroke, result);
|
|
}
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
public @NotNull Map<KeyStroke, Object> getSavedShortcutConflicts() {
|
|
return shortcutConflicts;
|
|
}
|
|
|
|
public @NotNull KeyMapping getKeyMapping(@NotNull MappingMode mode) {
|
|
KeyMapping mapping = keyMappings.get(mode);
|
|
if (mapping == null) {
|
|
mapping = new KeyMapping();
|
|
keyMappings.put(mode, mapping);
|
|
}
|
|
return mapping;
|
|
}
|
|
|
|
public void resetKeyMappings() {
|
|
keyMappings.clear();
|
|
}
|
|
|
|
/**
|
|
* Returns the root of the key mapping for the given mapping mode
|
|
*
|
|
* @param mappingMode The mapping mode
|
|
* @return The key mapping tree root
|
|
*/
|
|
@Override
|
|
public @NotNull CommandPartNode<VimActionsInitiator> getKeyRoot(@NotNull MappingMode mappingMode) {
|
|
return keyRoots.computeIfAbsent(mappingMode, (key) -> new RootNode<>());
|
|
}
|
|
|
|
@Override
|
|
public @NotNull KeyMappingLayer getKeyMappingLayer(@NotNull MappingMode mode) {
|
|
return getKeyMapping(mode);
|
|
}
|
|
|
|
/**
|
|
* Registers a shortcut that is handled by KeyHandler#handleKey directly, rather than by an action
|
|
*
|
|
* <p>
|
|
* Digraphs are handled directly by KeyHandler#handleKey instead of via an action, but we need to still make sure the
|
|
* shortcuts are registered, or the key handler won't see them
|
|
* </p>
|
|
*
|
|
* @param keyStroke The shortcut to register
|
|
*/
|
|
public void registerShortcutWithoutAction(KeyStroke keyStroke, MappingOwner owner) {
|
|
registerRequiredShortcut(Collections.singletonList(keyStroke), owner);
|
|
}
|
|
|
|
public void unregisterCommandActions() {
|
|
requiredShortcutKeys.clear();
|
|
keyRoots.clear();
|
|
if (identityChecker != null) identityChecker.clear();
|
|
if (prefixes != null) prefixes.clear();
|
|
}
|
|
|
|
public void registerCommandAction(@NotNull VimActionsInitiator actionHolder) {
|
|
IjVimActionsInitiator holder = (IjVimActionsInitiator)actionHolder;
|
|
|
|
if (!VimPlugin.getPluginId().equals(holder.getBean().getPluginDescriptor().getPluginId())) {
|
|
logger.error("IdeaVim doesn't accept contributions to `vimActions` extension points. " +
|
|
"Please create a plugin using `VimExtension`. " +
|
|
"Plugin to blame: " +
|
|
holder.getBean().getPluginDescriptor().getPluginId());
|
|
return;
|
|
}
|
|
|
|
Set<List<KeyStroke>> actionKeys = holder.getBean().getParsedKeys();
|
|
if (actionKeys == null) {
|
|
final EditorActionHandlerBase action = actionHolder.getInstance();
|
|
if (action instanceof ComplicatedKeysAction) {
|
|
actionKeys = ((ComplicatedKeysAction)action).getKeyStrokesSet();
|
|
}
|
|
else {
|
|
throw new RuntimeException("Cannot register action: " + action.getClass().getName());
|
|
}
|
|
}
|
|
|
|
Set<MappingMode> actionModes = holder.getBean().getParsedModes();
|
|
if (actionModes == null) {
|
|
throw new RuntimeException("Cannot register action: " + holder.getBean().getImplementation());
|
|
}
|
|
|
|
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
|
if (identityChecker == null) {
|
|
identityChecker = new HashMap<>();
|
|
prefixes = new HashMap<>();
|
|
}
|
|
for (List<KeyStroke> keys : actionKeys) {
|
|
checkCommand(actionModes, actionHolder.getInstance(), keys);
|
|
}
|
|
}
|
|
|
|
for (List<KeyStroke> keyStrokes : actionKeys) {
|
|
registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.INSTANCE);
|
|
|
|
for (MappingMode mappingMode : actionModes) {
|
|
Node<VimActionsInitiator> node = getKeyRoot(mappingMode);
|
|
NodesKt.addLeafs(node, keyStrokes, actionHolder);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
|
|
for (KeyStroke key : keys) {
|
|
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
|
|
requiredShortcutKeys.add(new RequiredShortcut(key, owner));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void checkCommand(@NotNull Set<MappingMode> mappingModes,
|
|
EditorActionHandlerBase action,
|
|
List<KeyStroke> keys) {
|
|
for (MappingMode mappingMode : mappingModes) {
|
|
checkIdentity(mappingMode, action.getId(), keys);
|
|
}
|
|
checkCorrectCombination(action, keys);
|
|
}
|
|
|
|
private void checkIdentity(MappingMode mappingMode, String actName, List<KeyStroke> keys) {
|
|
Set<List<KeyStroke>> keySets = Objects.requireNonNull(identityChecker).computeIfAbsent(mappingMode, k -> new HashSet<>());
|
|
if (keySets.contains(keys)) {
|
|
throw new RuntimeException(
|
|
"This keymap already exists: " + mappingMode + " keys: " + keys + " action:" + actName);
|
|
}
|
|
keySets.add(keys);
|
|
}
|
|
|
|
private void checkCorrectCombination(EditorActionHandlerBase action, List<KeyStroke> keys) {
|
|
for (Map.Entry<List<KeyStroke>, String> entry : Objects.requireNonNull(prefixes).entrySet()) {
|
|
List<KeyStroke> prefix = entry.getKey();
|
|
if (prefix.size() == keys.size()) continue;
|
|
int shortOne = Math.min(prefix.size(), keys.size());
|
|
int i;
|
|
for (i = 0; i < shortOne; i++) {
|
|
if (!prefix.get(i).equals(keys.get(i))) break;
|
|
}
|
|
|
|
List<String> actionExceptions = Arrays
|
|
.asList("VimInsertDeletePreviousWordAction", "VimInsertAfterCursorAction", "VimInsertBeforeCursorAction",
|
|
"VimFilterVisualLinesAction", "VimAutoIndentMotionAction");
|
|
if (i == shortOne && !actionExceptions.contains(action.getId()) && !actionExceptions.contains(entry.getValue())) {
|
|
throw new RuntimeException("Prefix found! " +
|
|
keys +
|
|
" in command " +
|
|
action.getId() +
|
|
" is the same as " +
|
|
prefix.stream().map(Object::toString).collect(joining(", ")) +
|
|
" in " +
|
|
entry.getValue());
|
|
}
|
|
}
|
|
prefixes.put(keys, action.getId());
|
|
}
|
|
|
|
private @Nullable Map<MappingMode, Set<List<KeyStroke>>> identityChecker;
|
|
private @Nullable Map<List<KeyStroke>, String> prefixes;
|
|
|
|
public static @NotNull ShortcutSet toShortcutSet(@NotNull Collection<RequiredShortcut> requiredShortcuts) {
|
|
final List<Shortcut> shortcuts = new ArrayList<>();
|
|
for (RequiredShortcut key : requiredShortcuts) {
|
|
shortcuts.add(new KeyboardShortcut(key.getKeyStroke(), null));
|
|
}
|
|
return new CustomShortcutSet(shortcuts.toArray(new Shortcut[0]));
|
|
}
|
|
|
|
private static @NotNull List<Pair<EnumSet<MappingMode>, MappingInfo>> getKeyMappingRows(@NotNull Set<MappingMode> modes) {
|
|
final Map<ImmutableList<KeyStroke>, EnumSet<MappingMode>> actualModes = new HashMap<>();
|
|
for (MappingMode mode : modes) {
|
|
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
|
|
for (List<KeyStroke> fromKeys : mapping) {
|
|
final ImmutableList<KeyStroke> key = ImmutableList.copyOf(fromKeys);
|
|
final EnumSet<MappingMode> value = actualModes.get(key);
|
|
final EnumSet<MappingMode> newValue;
|
|
if (value != null) {
|
|
newValue = value.clone();
|
|
newValue.add(mode);
|
|
}
|
|
else {
|
|
newValue = EnumSet.of(mode);
|
|
}
|
|
actualModes.put(key, newValue);
|
|
}
|
|
}
|
|
final List<Pair<EnumSet<MappingMode>, MappingInfo>> rows = new ArrayList<>();
|
|
for (Map.Entry<ImmutableList<KeyStroke>, EnumSet<MappingMode>> entry : actualModes.entrySet()) {
|
|
final ArrayList<KeyStroke> fromKeys = new ArrayList<>(entry.getKey());
|
|
final EnumSet<MappingMode> mappingModes = entry.getValue();
|
|
if (!mappingModes.isEmpty()) {
|
|
final MappingMode mode = mappingModes.iterator().next();
|
|
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
|
|
final MappingInfo mappingInfo = mapping.get(fromKeys);
|
|
if (mappingInfo != null) {
|
|
rows.add(new Pair<>(mappingModes, mappingInfo));
|
|
}
|
|
}
|
|
}
|
|
rows.sort(Comparator.comparing(Pair<EnumSet<MappingMode>, MappingInfo>::getSecond));
|
|
return rows;
|
|
}
|
|
|
|
private static @NotNull @NonNls String getModesStringCode(@NotNull Set<MappingMode> modes) {
|
|
if (modes.equals(MappingMode.NVO)) {
|
|
return "";
|
|
}
|
|
else if (modes.contains(MappingMode.INSERT)) {
|
|
return "i";
|
|
}
|
|
else if (modes.contains(MappingMode.NORMAL)) {
|
|
return "n";
|
|
}
|
|
// TODO: Add more codes
|
|
return "";
|
|
}
|
|
|
|
public @NotNull List<AnAction> getActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
|
|
final List<AnAction> results = new ArrayList<>();
|
|
results.addAll(getLocalActions(component, keyStroke));
|
|
results.addAll(getKeymapActions(keyStroke));
|
|
return results;
|
|
}
|
|
|
|
private static @NotNull List<AnAction> getLocalActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
|
|
final List<AnAction> results = new ArrayList<>();
|
|
final KeyboardShortcut keyStrokeShortcut = new KeyboardShortcut(keyStroke, null);
|
|
for (Component c = component; c != null; c = c.getParent()) {
|
|
if (c instanceof JComponent) {
|
|
final List<AnAction> actions = ActionUtil.getActions((JComponent)c);
|
|
for (AnAction action : actions) {
|
|
if (action instanceof VimShortcutKeyAction) {
|
|
continue;
|
|
}
|
|
final Shortcut[] shortcuts = action.getShortcutSet().getShortcuts();
|
|
for (Shortcut shortcut : shortcuts) {
|
|
if (shortcut.isKeyboard() && shortcut.startsWith(keyStrokeShortcut) && !results.contains(action)) {
|
|
results.add(action);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
private static @NotNull List<AnAction> getKeymapActions(@NotNull KeyStroke keyStroke) {
|
|
final List<AnAction> results = new ArrayList<>();
|
|
final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
|
|
for (String id : keymap.getActionIds(keyStroke)) {
|
|
final AnAction action = ActionManager.getInstance().getAction(id);
|
|
if (action != null) {
|
|
results.add(action);
|
|
}
|
|
}
|
|
return results;
|
|
}
|
|
|
|
@Nullable
|
|
@Override
|
|
public Element getState() {
|
|
@NonNls Element element = new Element("key");
|
|
saveData(element);
|
|
return element;
|
|
}
|
|
|
|
@Override
|
|
public void loadState(@NotNull Element state) {
|
|
readData(state);
|
|
}
|
|
}
|