mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-01-14 01:42:47 +01:00
Very initial key mapping support
This commit is contained in:
parent
2f6d4c9ff2
commit
05436427ee
src/com/maddyhome/idea/vim
test/org/jetbrains/plugins/ideavim/ex
@ -22,6 +22,7 @@ import com.intellij.openapi.actionSystem.ActionManager;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
@ -43,6 +44,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -100,11 +103,21 @@ public class KeyHandler {
|
||||
* @param context The data context
|
||||
*/
|
||||
public void handleKey(@NotNull Editor editor, @NotNull KeyStroke key, @NotNull DataContext context) {
|
||||
handleKey(editor, key, context, true);
|
||||
}
|
||||
|
||||
public void handleKey(@NotNull Editor editor, @NotNull KeyStroke key, @NotNull DataContext context,
|
||||
boolean allowKeyMappings) {
|
||||
VimPlugin.clearError();
|
||||
// All the editor actions should be performed with top level editor!!!
|
||||
// Be careful: all the EditorActionHandler implementation should correctly process InjectedEditors
|
||||
editor = InjectedLanguageUtil.getTopLevelEditor(editor);
|
||||
final CommandState editorState = CommandState.getInstance(editor);
|
||||
|
||||
if (allowKeyMappings && handleKeyMapping(editor, key, context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean isRecording = editorState.isRecording();
|
||||
boolean shouldRecord = true;
|
||||
// If this is a "regular" character keystroke, get the character
|
||||
@ -206,6 +219,61 @@ public class KeyHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleKeyMapping(@NotNull final Editor editor, @NotNull KeyStroke key,
|
||||
@NotNull final DataContext context) {
|
||||
final CommandState commandState = CommandState.getInstance(editor);
|
||||
commandState.stopMappingTimer();
|
||||
|
||||
final List<KeyStroke> mappingKeys = commandState.getMappingKeys();
|
||||
final List<KeyStroke> fromKeys = new ArrayList<KeyStroke>(mappingKeys);
|
||||
fromKeys.add(key);
|
||||
|
||||
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(commandState.getMappingMode());
|
||||
final List<KeyStroke> toKeys = mapping.get(fromKeys);
|
||||
|
||||
if (mapping.isPrefix(fromKeys)) {
|
||||
mappingKeys.add(key);
|
||||
commandState.startMappingTimer(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
mappingKeys.clear();
|
||||
for (KeyStroke keyStroke : fromKeys) {
|
||||
handleKey(editor, keyStroke, context, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
else if (toKeys != null) {
|
||||
mappingKeys.clear();
|
||||
final Application application = ApplicationManager.getApplication();
|
||||
final Runnable handleMappedKeys = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (KeyStroke keyStroke : toKeys) {
|
||||
// TODO: Don't allow key mapping for non-recursive mappings
|
||||
handleKey(editor, keyStroke, context, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (application.isUnitTestMode()) {
|
||||
handleMappedKeys.run();
|
||||
}
|
||||
else {
|
||||
application.invokeLater(handleMappedKeys);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
final List<KeyStroke> unhandledKeys = new ArrayList<KeyStroke>(mappingKeys);
|
||||
mappingKeys.clear();
|
||||
for (KeyStroke keyStroke : unhandledKeys) {
|
||||
handleKey(editor, keyStroke, context, false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEditorReset(@NotNull Editor editor, @NotNull KeyStroke key, @NotNull final DataContext context) {
|
||||
if (state != State.COMMAND && count == 0 && currentArg == Argument.Type.NONE && currentCmd.size() == 0 &&
|
||||
VimPlugin.getRegister().getCurrentRegister() == RegisterGroup.REGISTER_DEFAULT) {
|
||||
@ -513,6 +581,8 @@ public class KeyHandler {
|
||||
count = 0;
|
||||
keys = new ArrayList<KeyStroke>();
|
||||
CommandState editorState = CommandState.getInstance(editor);
|
||||
editorState.stopMappingTimer();
|
||||
editorState.getMappingKeys().clear();
|
||||
editorState.setCurrentNode(VimPlugin.getKey().getKeyRoot(editorState.getMappingMode()));
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,10 @@ import com.maddyhome.idea.vim.option.Options;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
public class CommandState {
|
||||
@ -37,10 +41,14 @@ public class CommandState {
|
||||
@NotNull private State myDefaultState = new State(Mode.COMMAND, SubMode.NONE, MappingMode.NORMAL);
|
||||
@Nullable private Command myCommand;
|
||||
@NotNull private ParentNode myCurrentNode = VimPlugin.getKey().getKeyRoot(getMappingMode());
|
||||
@NotNull private final List<KeyStroke> myMappingKeys = new ArrayList<KeyStroke>();
|
||||
@NotNull private final Timer myMappingTimer;
|
||||
private int myFlags;
|
||||
private boolean myIsRecording = false;
|
||||
|
||||
private CommandState() {
|
||||
myMappingTimer = new Timer(0, null);
|
||||
myMappingTimer.setRepeats(false);
|
||||
myStates.push(new State(Mode.COMMAND, SubMode.NONE, MappingMode.NORMAL));
|
||||
}
|
||||
|
||||
@ -117,6 +125,25 @@ public class CommandState {
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<KeyStroke> getMappingKeys() {
|
||||
return myMappingKeys;
|
||||
}
|
||||
|
||||
public void startMappingTimer(@NotNull ActionListener actionListener) {
|
||||
// TODO: Read the 'timeoutlen' option
|
||||
myMappingTimer.setInitialDelay(3000);
|
||||
for (ActionListener listener : myMappingTimer.getActionListeners()) {
|
||||
myMappingTimer.removeActionListener(listener);
|
||||
}
|
||||
myMappingTimer.addActionListener(actionListener);
|
||||
myMappingTimer.start();
|
||||
}
|
||||
|
||||
public void stopMappingTimer() {
|
||||
myMappingTimer.stop();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getStatusString(int pos) {
|
||||
State state;
|
||||
|
@ -83,6 +83,7 @@ public class CommandParser {
|
||||
new HistoryHandler();
|
||||
new JoinLinesHandler();
|
||||
new JumpsHandler();
|
||||
new MapHandler();
|
||||
new MarkHandler();
|
||||
new MarksHandler();
|
||||
new MoveTextHandler();
|
||||
|
85
src/com/maddyhome/idea/vim/ex/handler/MapHandler.java
Normal file
85
src/com/maddyhome/idea/vim/ex/handler/MapHandler.java
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2014 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.ex.handler;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.command.MappingMode;
|
||||
import com.maddyhome.idea.vim.ex.CommandHandler;
|
||||
import com.maddyhome.idea.vim.ex.CommandName;
|
||||
import com.maddyhome.idea.vim.ex.ExCommand;
|
||||
import com.maddyhome.idea.vim.ex.ExException;
|
||||
import com.maddyhome.idea.vim.key.KeyMapping;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
public class MapHandler extends CommandHandler {
|
||||
public static final Pattern RE_MAP_ARGUMENTS = Pattern.compile("([^ ]+) +(.+)");
|
||||
|
||||
public static Map<String, Set<MappingMode>> MAPPING_MODE_NAMES = ImmutableMap.<String, Set<MappingMode>>builder()
|
||||
.put("nmap", MappingMode.N)
|
||||
.build();
|
||||
|
||||
public MapHandler() {
|
||||
super(new CommandName[]{
|
||||
new CommandName("map", ""),
|
||||
new CommandName("nm", "ap")
|
||||
// TODO: Add other mapping commands
|
||||
}, RANGE_FORBIDDEN | ARGUMENT_OPTIONAL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(@NotNull Editor editor, @NotNull DataContext context,
|
||||
@NotNull ExCommand cmd) throws ExException {
|
||||
final Set<MappingMode> modes = MAPPING_MODE_NAMES.get(cmd.getCommand());
|
||||
if (modes != null) {
|
||||
final String argument = cmd.getArgument();
|
||||
if (argument.isEmpty()) {
|
||||
// TODO: Show key mapping table
|
||||
throw new UnsupportedOperationException(String.format("Key mapping table is not implemented yet"));
|
||||
}
|
||||
else {
|
||||
final Matcher matcher = RE_MAP_ARGUMENTS.matcher(argument);
|
||||
if (matcher.matches()) {
|
||||
final List<KeyStroke> leftKeys = parseKeys(matcher.group(1));
|
||||
final List<KeyStroke> rightKeys = parseKeys(matcher.group(2));
|
||||
for (MappingMode mode : modes) {
|
||||
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
|
||||
mapping.put(leftKeys, rightKeys);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@ public class KeyGroup {
|
||||
@NotNull private Map<KeyStroke, ShortcutOwner> shortcutConflicts = new LinkedHashMap<KeyStroke, ShortcutOwner>();
|
||||
@NotNull private Set<KeyStroke> requiredShortcutKeys = new HashSet<KeyStroke>();
|
||||
@NotNull private HashMap<MappingMode, RootNode> keyRoots = new HashMap<MappingMode, RootNode>();
|
||||
@NotNull private Map<MappingMode, KeyMapping> keyMappings = new HashMap<MappingMode, KeyMapping>();
|
||||
|
||||
public void saveData(@NotNull Element element) {
|
||||
final Element conflictsElement = new Element(SHORTCUT_CONFLICTS_ELEMENT);
|
||||
@ -119,6 +120,16 @@ public class KeyGroup {
|
||||
return requiredShortcutKeys;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public KeyMapping getKeyMapping(@NotNull MappingMode mode) {
|
||||
KeyMapping mapping = keyMappings.get(mode);
|
||||
if (mapping == null) {
|
||||
mapping = new KeyMapping();
|
||||
keyMappings.put(mode, mapping);
|
||||
}
|
||||
return mapping;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root of the key mapping for the given mapping mode
|
||||
*
|
||||
|
86
src/com/maddyhome/idea/vim/key/KeyMapping.java
Normal file
86
src/com/maddyhome/idea/vim/key/KeyMapping.java
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* IdeaVim - Vim emulator for IDEs based on the IntelliJ platform
|
||||
* Copyright (C) 2003-2014 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.key;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
public class KeyMapping {
|
||||
@NotNull private Map<ImmutableList<KeyStroke>, List<KeyStroke>> myKeys = new HashMap<ImmutableList<KeyStroke>, List<KeyStroke>>();
|
||||
@NotNull private Map<ImmutableList<KeyStroke>, Integer> myPrefixes = new HashMap<ImmutableList<KeyStroke>, Integer>();
|
||||
|
||||
@Nullable
|
||||
public List<KeyStroke> get(@NotNull List<KeyStroke> keys) {
|
||||
return myKeys.get(ImmutableList.copyOf(keys));
|
||||
}
|
||||
|
||||
public void put(@NotNull List<KeyStroke> keys, @NotNull List<KeyStroke> value) {
|
||||
myKeys.put(ImmutableList.copyOf(keys), value);
|
||||
List<KeyStroke> prefix = new ArrayList<KeyStroke>();
|
||||
final int prefixLength = keys.size() - 1;
|
||||
for (int i = 0; i < prefixLength; i++) {
|
||||
prefix.add(keys.get(i));
|
||||
increment(ImmutableList.copyOf(prefix));
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(@NotNull List<KeyStroke> keys) {
|
||||
myKeys.remove(ImmutableList.copyOf(keys));
|
||||
List<KeyStroke> prefix = new ArrayList<KeyStroke>();
|
||||
final int prefixLength = keys.size() - 1;
|
||||
for (int i = 0; i < prefixLength; i++) {
|
||||
prefix.add(keys.get(i));
|
||||
decrement(ImmutableList.copyOf(prefix));
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isPrefix(@NotNull List<KeyStroke> keys) {
|
||||
return myPrefixes.get(ImmutableList.copyOf(keys)) != null;
|
||||
}
|
||||
|
||||
private void increment(@NotNull ImmutableList<KeyStroke> prefix) {
|
||||
Integer count = myPrefixes.get(prefix);
|
||||
if (count == null) {
|
||||
count = 0;
|
||||
}
|
||||
myPrefixes.put(prefix, count + 1);
|
||||
}
|
||||
|
||||
private void decrement(@NotNull ImmutableList<KeyStroke> prefix) {
|
||||
final Integer count = myPrefixes.get(prefix);
|
||||
if (count != null) {
|
||||
if (count <= 1) {
|
||||
myPrefixes.remove(prefix);
|
||||
}
|
||||
else {
|
||||
myPrefixes.put(prefix, count - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,16 @@
|
||||
* |v| {@link com.maddyhome.idea.vim.action.motion.visual.VisualToggleCharacterModeAction}
|
||||
* |characterwise-visual|
|
||||
*
|
||||
*
|
||||
* 5. Ex commands
|
||||
*
|
||||
* tag handler
|
||||
* ---------------------------------------------------------------------------------------------------------------------
|
||||
*
|
||||
* |:map| {@link com.maddyhome.idea.vim.ex.handler.MapHandler}
|
||||
* |:nmap|
|
||||
*
|
||||
*
|
||||
* @see :help index.
|
||||
*
|
||||
* @author vlan
|
||||
|
20
test/org/jetbrains/plugins/ideavim/ex/MapCommandTest.java
Normal file
20
test/org/jetbrains/plugins/ideavim/ex/MapCommandTest.java
Normal file
@ -0,0 +1,20 @@
|
||||
package org.jetbrains.plugins.ideavim.ex;
|
||||
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase;
|
||||
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
public class MapCommandTest extends VimTestCase {
|
||||
public void testMapKtoJ() {
|
||||
configureByText("<caret>foo\n" +
|
||||
"bar\n");
|
||||
typeText(commandToKeys("nmap k j"));
|
||||
assertOffset(0);
|
||||
assertPluginError(false);
|
||||
typeText(parseKeys("k"));
|
||||
assertOffset(4);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user