allow binding multiple commands to same key

This commit is contained in:
nightwing 2014-07-22 03:20:43 +04:00
commit c673190bf7
6 changed files with 122 additions and 105 deletions

View file

@ -2,13 +2,12 @@ define(function(require, exports, module) {
"use strict"; "use strict";
var oop = require("../lib/oop"); var oop = require("../lib/oop");
var HashHandler = require("../keyboard/hash_handler").HashHandler; var MultiHashHandler = require("../keyboard/hash_handler").MultiHashHandler;
var EventEmitter = require("../lib/event_emitter").EventEmitter; var EventEmitter = require("../lib/event_emitter").EventEmitter;
/** /**
* @class CommandManager * @class CommandManager
* *
*
**/ **/
/** /**
@ -19,20 +18,27 @@ var EventEmitter = require("../lib/event_emitter").EventEmitter;
**/ **/
var CommandManager = function(platform, commands) { var CommandManager = function(platform, commands) {
HashHandler.call(this, commands, platform); MultiHashHandler.call(this, commands, platform);
this.byName = this.commands; this.byName = this.commands;
this.setDefaultHandler("exec", function(e) { this.setDefaultHandler("exec", function(e) {
return e.command.exec(e.editor, e.args || {}); return e.command.exec(e.editor, e.args || {});
}); });
}; };
oop.inherits(CommandManager, HashHandler); oop.inherits(CommandManager, MultiHashHandler);
(function() { (function() {
oop.implement(this, EventEmitter); oop.implement(this, EventEmitter);
this.exec = function(command, editor, args) { this.exec = function(command, editor, args) {
if (Array.isArray(command)) {
for (var i = command.length; i--; ) {
if (this.exec(command[i], editor, args)) return true;
}
return false;
}
if (typeof command === 'string') if (typeof command === 'string')
command = this.commands[command]; command = this.commands[command];
@ -43,10 +49,10 @@ oop.inherits(CommandManager, HashHandler);
return false; return false;
var e = {editor: editor, command: command, args: args}; var e = {editor: editor, command: command, args: args};
var retvalue = this._emit("exec", e); e.returnValue = this._emit("exec", e);
this._signal("afterExec", e); this._signal("afterExec", e);
return retvalue === false ? false : true; return e.returnValue === false ? false : true;
}; };
this.toggleRecording = function(editor) { this.toggleRecording = function(editor) {

View file

@ -114,30 +114,8 @@ var Editor = function(renderer, session) {
function last(a) {return a[a.length - 1]} function last(a) {return a[a.length - 1]}
this.selections = []; this.selections = [];
this.commands.on("exec", function(e) { this.commands.on("exec", this.startOperation.bind(this), true);
this.startOperation(e); this.commands.on("afterExec", this.endOperation.bind(this), true);
var command = e.command;
if (command.aceCommandGroup == "fileJump") {
var prev = this.prevOp;
if (!prev || prev.command.aceCommandGroup != "fileJump") {
this.lastFileJumpPos = last(this.selections);
}
} else {
this.lastFileJumpPos = null;
}
}.bind(this), true);
this.commands.on("afterExec", function(e) {
var command = e.command;
if (command.aceCommandGroup == "fileJump") {
if (this.lastFileJumpPos && !this.curOp.selectionChanged) {
this.selection.fromJSON(this.lastFileJumpPos);
}
}
this.endOperation(e);
}.bind(this), true);
this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this)); this.$opResetTimer = lang.delayedCall(this.endOperation.bind(this));
@ -172,18 +150,16 @@ var Editor = function(renderer, session) {
scrollTop: this.renderer.scrollTop scrollTop: this.renderer.scrollTop
}; };
var command = this.curOp.command; // this.selections.push(this.selection.toJSON());
if (command && command.scrollIntoView)
this.$blockScrolling++;
this.selections.push(this.selection.toJSON());
}; };
this.endOperation = function() { this.endOperation = function(e) {
if (this.curOp) { if (this.curOp) {
if (e && e.returnValue === false)
return this.curOp = null;
var command = this.curOp.command; var command = this.curOp.command;
if (command && command.scrollIntoView) { if (command && command.scrollIntoView) {
this.$blockScrolling--;
switch (command.scrollIntoView) { switch (command.scrollIntoView) {
case "center": case "center":
this.renderer.scrollCursorIntoView(null, 0.5); this.renderer.scrollCursorIntoView(null, 0.5);
@ -254,19 +230,19 @@ var Editor = function(renderer, session) {
* @param {String} keyboardHandler The new key handler * @param {String} keyboardHandler The new key handler
* *
**/ **/
this.setKeyboardHandler = function(keyboardHandler) { this.setKeyboardHandler = function(keyboardHandler, cb) {
if (!keyboardHandler) { if (keyboardHandler && typeof keyboardHandler === "string") {
this.keyBinding.setKeyboardHandler(null);
} else if (typeof keyboardHandler === "string") {
this.$keybindingId = keyboardHandler; this.$keybindingId = keyboardHandler;
var _self = this; var _self = this;
config.loadModule(["keybinding", keyboardHandler], function(module) { config.loadModule(["keybinding", keyboardHandler], function(module) {
if (_self.$keybindingId == keyboardHandler) if (_self.$keybindingId == keyboardHandler)
_self.keyBinding.setKeyboardHandler(module && module.handler); _self.keyBinding.setKeyboardHandler(module && module.handler);
cb && cb();
}); });
} else { } else {
this.$keybindingId = null; this.$keybindingId = null;
this.keyBinding.setKeyboardHandler(keyboardHandler); this.keyBinding.setKeyboardHandler(keyboardHandler);
cb && cb();
} }
}; };
@ -925,9 +901,8 @@ var Editor = function(renderer, session) {
this.insert(e.text, true); this.insert(e.text, true);
}; };
this.execCommand = function(command, args) { this.execCommand = function(command, args) {
this.commands.exec(command, this, args); return this.commands.exec(command, this, args);
}; };
/** /**

View file

@ -80,9 +80,7 @@ exports.snippetCompleter = snippetCompleter;
var expandSnippet = { var expandSnippet = {
name: "expandSnippet", name: "expandSnippet",
exec: function(editor) { exec: function(editor) {
var success = snippetManager.expandWithTab(editor); return snippetManager.expandWithTab(editor);
if (!success)
editor.execCommand("indent");
}, },
bindKey: "Tab" bindKey: "Tab"
}; };

View file

@ -64,7 +64,7 @@ exports.handler.attach = function(editor) {
initialized = true; initialized = true;
dom.importCssString('\ dom.importCssString('\
.emacs-mode .ace_cursor{\ .emacs-mode .ace_cursor{\
border: 2px rgba(50,250,50,0.8) solid!important;\ border: 1px rgba(50,250,50,0.8) solid!important;\
-moz-box-sizing: border-box!important;\ -moz-box-sizing: border-box!important;\
-webkit-box-sizing: border-box!important;\ -webkit-box-sizing: border-box!important;\
box-sizing: border-box!important;\ box-sizing: border-box!important;\
@ -195,6 +195,8 @@ exports.handler.onPaste = function(e, editor) {
}; };
exports.handler.bindKey = function(key, command) { exports.handler.bindKey = function(key, command) {
if (typeof key == "object")
key = key[this.platform];
if (!key) if (!key)
return; return;

View file

@ -33,37 +33,25 @@ define(function(require, exports, module) {
var keyUtil = require("../lib/keys"); var keyUtil = require("../lib/keys");
var useragent = require("../lib/useragent"); var useragent = require("../lib/useragent");
var KEY_MODS = keyUtil.KEY_MODS;
function HashHandler(config, platform) { function HashHandler(config, platform) {
this.platform = platform || (useragent.isMac ? "mac" : "win"); this.platform = platform || (useragent.isMac ? "mac" : "win");
this.commands = {}; this.commands = {};
this.commandKeyBinding = {}; this.commandKeyBinding = {};
// todo remove this after a while
if (this.__defineGetter__ && this.__defineSetter__ && typeof console != "undefined" && console.error) {
var warned = false;
var warn = function() {
if (!warned) {
warned = true;
console.error("commmandKeyBinding has too many m's. use commandKeyBinding");
}
};
this.__defineGetter__("commmandKeyBinding", function() {
warn();
return this.commandKeyBinding;
});
this.__defineSetter__("commmandKeyBinding", function(val) {
warn();
return this.commandKeyBinding = val;
});
} else {
this.commmandKeyBinding = this.commandKeyBinding;
}
this.addCommands(config); this.addCommands(config);
}; this.$singleCommand = true;
}
function MultiHashHandler(config, platform) {
HashHandler.call(this, config, platform);
this.$singleCommand = false;
}
MultiHashHandler.prototype = HashHandler.prototype;
(function() { (function() {
this.addCommand = function(command) { this.addCommand = function(command) {
if (this.commands[command.name]) if (this.commands[command.name])
@ -75,37 +63,76 @@ function HashHandler(config, platform) {
this._buildKeyHash(command); this._buildKeyHash(command);
}; };
this.removeCommand = function(command) { this.removeCommand = function(command, keepCommand) {
var name = (typeof command === 'string' ? command : command.name); var name = command && (typeof command === 'string' ? command : command.name);
command = this.commands[name]; command = this.commands[name];
delete this.commands[name]; if (!keepCommand)
delete this.commands[name];
// exhaustive search is brute force but since removeCommand is // exhaustive search is brute force but since removeCommand is
// not a performance critical operation this should be OK // not a performance critical operation this should be OK
var ckb = this.commandKeyBinding; var ckb = this.commandKeyBinding;
for (var hashId in ckb) { for (var keyId in ckb) {
for (var key in ckb[hashId]) { var cmdGroup = ckb[keyId];
if (ckb[hashId][key] == command) if (cmdGroup == command) {
delete ckb[hashId][key]; delete ckb[keyId];
} else if (Array.isArray(cmdGroup)) {
var i = cmdGroup.indexOf(command);
if (i != -1) {
cmdGroup.splice(i, 1);
if (cmdGroup.length == 1)
ckb[keyId] = cmdGroup[0];
}
} }
} }
}; };
this.bindKey = function(key, command) { this.bindKey = function(key, command, asDefault) {
if(!key) if (typeof key == "object")
key = key[this.platform];
if (!key)
return; return;
if (typeof command == "function") { if (typeof command == "function")
this.addCommand({exec: command, bindKey: key, name: command.name || key}); return this.addCommand({exec: command, bindKey: key, name: command.name || key});
return;
}
var ckb = this.commandKeyBinding;
key.split("|").forEach(function(keyPart) { key.split("|").forEach(function(keyPart) {
var binding = this.parseKeys(keyPart, command); var chain = "";
var hashId = binding.hashId; if (keyPart.indexOf(" ") != -1) {
(ckb[hashId] || (ckb[hashId] = {}))[binding.key] = command; var parts = keyPart.split(/\s+/);
keyPart = parts.pop();
parts.forEach(function(keyPart) {
var binding = this.parseKeys(keyPart);
var id = KEY_MODS[binding.hashId] + binding.key;
chain += (chain ? " " : "") + id;
this._addCommandToBinding(chain, "chainKeys");
}, this);
chain += " ";
}
var binding = this.parseKeys(keyPart);
var id = KEY_MODS[binding.hashId] + binding.key;
this._addCommandToBinding(chain + id, command, asDefault);
}, this); }, this);
}; };
this._addCommandToBinding = function(keyId, command, asDefault) {
var ckb = this.commandKeyBinding, i;
if (!command) {
delete ckb[keyId];
} else if (!ckb[keyId] || this.$singleCommand) {
ckb[keyId] = command;
} else {
if (!Array.isArray(ckb[keyId])) {
ckb[keyId] = [ckb[keyId]];
} else if ((i = ckb[keyId].indexOf(command)) != -1) {
ckb[keyId].splice(i, 1);
}
if (asDefault || command.isDefault)
ckb[keyId].unshift(command);
else
ckb[keyId].push(command);
}
};
this.addCommands = function(commands) { this.addCommands = function(commands) {
commands && Object.keys(commands).forEach(function(name) { commands && Object.keys(commands).forEach(function(name) {
@ -142,21 +169,12 @@ function HashHandler(config, platform) {
}; };
this._buildKeyHash = function(command) { this._buildKeyHash = function(command) {
var binding = command.bindKey; this.bindKey(command.bindKey, command);
if (!binding)
return;
var key = typeof binding == "string" ? binding: binding[this.platform];
this.bindKey(key, command);
}; };
// accepts keys in the form ctrl+Enter or ctrl-Enter // accepts keys in the form ctrl+Enter or ctrl-Enter
// keys without modifiers or shift only // keys without modifiers or shift only
this.parseKeys = function(keys) { this.parseKeys = function(keys) {
// todo support keychains
if (keys.indexOf(" ") != -1)
keys = keys.split(/\s+/).pop();
var parts = keys.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function(x){return x}); var parts = keys.toLowerCase().split(/[\-\+]([\-\+])?/).filter(function(x){return x});
var key = parts.pop(); var key = parts.pop();
@ -173,7 +191,7 @@ function HashHandler(config, platform) {
var modifier = keyUtil.KEY_MODS[parts[i]]; var modifier = keyUtil.KEY_MODS[parts[i]];
if (modifier == null) { if (modifier == null) {
if (typeof console != "undefined") if (typeof console != "undefined")
console.error("invalid modifier " + parts[i] + " in " + keys); console.error("invalid modifier " + parts[i] + " in " + keys);
return false; return false;
} }
hashId |= modifier; hashId |= modifier;
@ -182,17 +200,32 @@ function HashHandler(config, platform) {
}; };
this.findKeyCommand = function findKeyCommand(hashId, keyString) { this.findKeyCommand = function findKeyCommand(hashId, keyString) {
var ckbr = this.commandKeyBinding; var key = KEY_MODS[hashId] + keyString;
return ckbr[hashId] && ckbr[hashId][keyString]; return this.commandKeyBinding[key];
}; };
this.handleKeyboard = function(data, hashId, keyString, keyCode) { this.handleKeyboard = function(data, hashId, keyString, keyCode) {
return { var key = KEY_MODS[hashId] + keyString;
command: this.findKeyCommand(hashId, keyString) var command = this.commandKeyBinding[key];
}; if (data.$keyChain) {
data.$keyChain += " " + key;
command = this.commandKeyBinding[data.$keyChain] || command;
}
if (command) {
if (command == "chainKeys" || command[command.length - 1] == "chainKeys") {
data.$keyChain = data.$keyChain || key;
return {command: "null"};
}
}
if (data.$keyChain && keyCode > 0)
data.$keyChain = "";
return {command: command};
}; };
}).call(HashHandler.prototype) }).call(HashHandler.prototype);
exports.HashHandler = HashHandler; exports.HashHandler = HashHandler;
exports.MultiHashHandler = MultiHashHandler;
}); });

View file

@ -143,6 +143,9 @@ var Keys = (function() {
} }
})(); })();
ret.KEY_MODS[0] = "";
ret.KEY_MODS[-1] = "input";
return ret; return ret;
})(); })();
oop.mixin(exports, Keys); oop.mixin(exports, Keys);