1
0
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:
Andrey Vlasovskikh 2014-04-01 00:20:13 +04:00
parent 2f6d4c9ff2
commit 05436427ee
8 changed files with 310 additions and 0 deletions
src/com/maddyhome/idea/vim
test/org/jetbrains/plugins/ideavim/ex

View File

@ -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()));
}

View File

@ -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;

View File

@ -83,6 +83,7 @@ public class CommandParser {
new HistoryHandler();
new JoinLinesHandler();
new JumpsHandler();
new MapHandler();
new MarkHandler();
new MarksHandler();
new MoveTextHandler();

View 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;
}
}

View File

@ -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
*

View 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);
}
}
}
}

View File

@ -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

View 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);
}
}