Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

map arrays of keys rather than one string

This disambiguates the string "up", for instance.  We can be sure it is a
mapping for the up arrow key and not the `u` key followed by the `p` key.
  • Loading branch information...
commit e462f865d1eab13e5b1a9633fdfe2666a7e6166c 1 parent 405f59e
@misfo authored
View
2  Cakefile
@@ -3,10 +3,10 @@ CoffeeScript = require 'coffee-script'
sourceNames = [
# in dependency order
+ 'helpers'
'keymap'
'modes'
'jim'
- 'helpers'
'motions'
'operators'
'commands'
View
477 build/jim-ace.development.js
@@ -8,109 +8,98 @@
this.Jim = (function() {
function require(path) { return path[0] === '.' ? require[path] : window.require(path); }
+require['./helpers'] = (function() {
+ var exports = {}, module = {};
+ exports.Command = (function() {
+ function Command(count) {
+ this.count = count != null ? count : 1;
+ }
+ Command.prototype.isRepeatable = true;
+ Command.prototype.isComplete = function() {
+ if (this.constructor.followedBy) {
+ return this.followedBy;
+ } else {
+ return true;
+ }
+ };
+ return Command;
+})();
+exports.InputState = (function() {
+ function InputState() {
+ this.clear();
+ }
+ InputState.prototype.clear = function() {
+ this.command = null;
+ this.count = '';
+ this.keymap = null;
+ return this.operatorPending = null;
+ };
+ InputState.prototype.setCommand = function(commandClass) {
+ this.command = new commandClass(parseInt(this.count) || null);
+ return this.count = '';
+ };
+ InputState.prototype.setOperationMotion = function(motionClass) {
+ this.command.motion = new motionClass(parseInt(this.count) || null);
+ return this.command.motion.operation = this.command;
+ };
+ InputState.count = '';
+ return InputState;
+})();
+({
+ toString: function() {
+ return "TODO";
+ }
+});
+exports.repeatCountTimes = function(func) {
+ return function(jim) {
+ var timesLeft, _results;
+ timesLeft = this.count;
+ _results = [];
+ while (timesLeft--) {
+ _results.push(func.call(this, jim));
+ }
+ return _results;
+ };
+};
+ return module.exports || exports;
+})();
+
require['./keymap'] = (function() {
var exports = {}, module = {};
var Keymap;
-var __hasProp = Object.prototype.hasOwnProperty;
Keymap = (function() {
- var buildPartialCommandRegex;
+ var mapIntoObject;
function Keymap() {
- this.commands = {};
- this.motions = {};
- this.visualCommands = {};
- this.partialCommands = {};
- this.partialMotions = {};
- this.partialVisualCommands = {};
+ this.normal = {};
+ this.visual = {};
+ this.operatorPending = {};
}
+ mapIntoObject = function(object, keys, command) {
+ var key, _i, _len, _ref;
+ _ref = keys.slice(0, -1);
+ for (_i = 0, _len = _ref.length; _i < _len; _i++) {
+ key = _ref[_i];
+ object[key] || (object[key] = {});
+ object = object[key];
+ }
+ return object[keys[keys.length - 1]] = command;
+ };
Keymap.prototype.mapCommand = function(keys, commandClass) {
if (commandClass.prototype.exec) {
- this.commands[keys] = commandClass;
- if (keys.length === 2 && keys !== 'up') {
- this.partialCommands[keys[0]] = true;
- }
+ mapIntoObject(this.normal, keys, commandClass);
}
if (commandClass.prototype.visualExec) {
- this.visualCommands[keys] = commandClass;
- if (keys.length === 2 && keys !== 'up') {
- return this.partialVisualCommands[keys[0]] = true;
- }
+ return mapIntoObject(this.visual, keys, commandClass);
}
};
Keymap.prototype.mapMotion = function(keys, motionClass) {
- this.commands[keys] = motionClass;
- this.motions[keys] = motionClass;
- this.visualCommands[keys] = motionClass;
- if (keys.length === 2 && keys !== 'up') {
- this.partialMotions[keys[0]] = true;
- this.partialCommands[keys[0]] = true;
- return this.partialVisualCommands[keys[0]] = true;
- }
+ mapIntoObject(this.normal, keys, motionClass);
+ mapIntoObject(this.visual, keys, motionClass);
+ return mapIntoObject(this.operatorPending, keys, motionClass);
};
Keymap.prototype.mapOperator = function(keys, operatorClass) {
- this.commands[keys] = operatorClass;
- this.visualCommands[keys] = operatorClass;
- if (keys.length === 2 && keys !== 'up') {
- this.partialCommands[keys[0]] = true;
- return this.partialVisualCommands[keys[0]] = true;
- }
- };
- buildPartialCommandRegex = function(partialCommands) {
- var char, nothing;
- return RegExp("^([1-9]\\d*)?([" + (((function() {
- var _results;
- _results = [];
- for (char in partialCommands) {
- if (!__hasProp.call(partialCommands, char)) continue;
- nothing = partialCommands[char];
- _results.push(char);
- }
- return _results;
- })()).join('')) + "]?([\\s\\S]*))?$");
- };
- Keymap.prototype.commandFor = function(commandPart) {
- var beyondPartial, command, commandClass, count, _ref;
- this.partialCommandRegex || (this.partialCommandRegex = buildPartialCommandRegex(this.partialCommands));
- _ref = commandPart.match(this.partialCommandRegex), commandPart = _ref[0], count = _ref[1], command = _ref[2], beyondPartial = _ref[3];
- if (beyondPartial) {
- if (commandClass = this.commands[command]) {
- return new commandClass(parseInt(count) || null);
- } else {
- return false;
- }
- } else {
- return true;
- }
- };
- Keymap.prototype.motionFor = function(commandPart, operatorPending) {
- var LinewiseCommandMotion, beyondPartial, count, motion, motionClass, _ref;
- this.partialMotionRegex || (this.partialMotionRegex = buildPartialCommandRegex(this.partialMotions));
- _ref = commandPart.match(this.partialCommandRegex), commandPart = _ref[0], count = _ref[1], motion = _ref[2], beyondPartial = _ref[3];
- if (beyondPartial) {
- if (motion === operatorPending) {
- LinewiseCommandMotion = require('./motions').LinewiseCommandMotion;
- return new LinewiseCommandMotion(parseInt(count) || null);
- } else if (motionClass = this.motions[motion]) {
- return new motionClass(parseInt(count) || null);
- } else {
- return false;
- }
- } else {
- return true;
- }
- };
- Keymap.prototype.visualCommandFor = function(commandPart) {
- var beyondPartial, command, commandClass, count, _ref;
- this.partialVisualCommandRegex || (this.partialVisualCommandRegex = buildPartialCommandRegex(this.partialVisualCommands));
- _ref = commandPart.match(this.partialVisualCommandRegex), commandPart = _ref[0], count = _ref[1], command = _ref[2], beyondPartial = _ref[3];
- if (beyondPartial) {
- if (commandClass = this.visualCommands[command]) {
- return new commandClass(parseInt(count) || null);
- } else {
- return false;
- }
- } else {
- return true;
- }
+ mapIntoObject(this.normal, keys, operatorClass);
+ return mapIntoObject(this.visual, keys, operatorClass);
};
return Keymap;
})();
@@ -120,95 +109,115 @@ module.exports = Keymap;
require['./modes'] = (function() {
var exports = {}, module = {};
- var invalidCommand;
+ var LinewiseCommandMotion, invalidCommand;
+LinewiseCommandMotion = (function() {
+ function LinewiseCommandMotion(count) {
+ this.count = count != null ? count : 1;
+ }
+ LinewiseCommandMotion.prototype.linewise = true;
+ LinewiseCommandMotion.prototype.isComplete = function() {
+ return true;
+ };
+ LinewiseCommandMotion.prototype.exec = function(jim) {
+ var additionalLines, _results;
+ additionalLines = this.count - 1;
+ _results = [];
+ while (additionalLines--) {
+ _results.push(jim.adaptor.moveDown());
+ }
+ return _results;
+ };
+ return LinewiseCommandMotion;
+})();
invalidCommand = function(type) {
if (type == null) {
type = 'command';
}
- console.log("invalid " + type + ": " + this.commandPart);
+ console.log("invalid " + type + ": " + this.inputState);
return this.onEscape();
};
exports.normal = {
- onKeypress: function(keys) {
- var command, motion, regex, _ref, _ref2;
- this.commandPart = (this.commandPart || '') + keys;
- if (!this.command) {
- command = Jim.keymap.commandFor(this.commandPart);
- if (command === false) {
+ onKeypress: function(key) {
+ var commandClass, motionClass, regex, _ref, _ref2;
+ if (/^[1-9]$/.test(key) || (key === "0" && this.inputState.count.length)) {
+ this.inputState.count += key;
+ } else if (!this.inputState.command) {
+ commandClass = (this.inputState.keymap || Jim.keymap.normal)[key];
+ if (!commandClass) {
invalidCommand.call(this);
- } else if (command !== true) {
- if (command.isOperation) {
- this.operatorPending = this.commandPart.match(/[^\d]+$/)[0];
+ } else if (commandClass.prototype) {
+ this.inputState.setCommand(commandClass);
+ if (this.inputState.command.isOperation) {
+ this.inputState.operatorPending = key;
}
- this.command = command;
- this.commandPart = '';
+ } else if (commandClass) {
+ this.inputState.keymap = commandClass;
}
- } else if (this.command.constructor.followedBy) {
- if (this.command.constructor.followedBy.test(this.commandPart)) {
- this.command.followedBy = this.commandPart;
+ } else if (this.inputState.command.constructor.followedBy) {
+ if (this.inputState.command.constructor.followedBy.test(key)) {
+ this.inputState.command.followedBy = key;
} else {
- console.log("" + this.command + " didn't expect to be followed by \"" + this.commandPart + "\"");
+ console.log("" + this.inputState.command + " didn't expect to be followed by \"" + key + "\"");
}
- this.commandPart = '';
- } else if (this.command.isOperation) {
- if (regex = (_ref = this.command.motion) != null ? _ref.constructor.followedBy : void 0) {
- if (regex.test(this.commandPart)) {
- this.command.motion.followedBy = this.commandPart;
+ } else if (this.inputState.operatorPending) {
+ if (regex = (_ref = this.inputState.command.motion) != null ? _ref.constructor.followedBy : void 0) {
+ if (regex.test(key)) {
+ this.inputState.command.motion.followedBy = key;
} else {
- console.log("" + this.command + " didn't expect to be followed by \"" + this.commandPart + "\"");
+ console.log("" + this.inputState.command + " didn't expect to be followed by \"" + key + "\"");
}
} else {
- motion = Jim.keymap.motionFor(this.commandPart, this.operatorPending);
- if (motion === false) {
- invalidCommand.call(this, 'motion');
- } else if (motion !== true) {
- motion.operation = this.command;
- this.command.motion = motion;
- this.operatorPending = null;
- this.commandPart = '';
+ motionClass = key === this.inputState.operatorPending ? LinewiseCommandMotion : (this.inputState.keymap || Jim.keymap.operatorPending)[key];
+ if (!motionClass) {
+ invalidCommand.call(this);
+ } else if (motionClass.prototype) {
+ this.inputState.setOperationMotion(motionClass);
+ } else {
+ this.inputState.keymap = motion;
}
}
}
- if ((_ref2 = this.command) != null ? _ref2.isComplete() : void 0) {
- this.command.exec(this);
- if (this.command.isRepeatable) {
- this.lastCommand = this.command;
+ if ((_ref2 = this.inputState.command) != null ? _ref2.isComplete() : void 0) {
+ this.inputState.command.exec(this);
+ if (this.inputState.command.isRepeatable) {
+ this.lastCommand = this.inputState.command;
}
- return this.command = null;
+ return this.inputState.clear();
}
}
};
exports.visual = {
- onKeypress: function(newKeys) {
- var command, maxRow, minRow, wasBackwards, _ref, _ref2, _ref3;
- this.commandPart = (this.commandPart || '') + newKeys;
- if (!this.command) {
- command = Jim.keymap.visualCommandFor(this.commandPart);
- if (command === false) {
+ onKeypress: function(key) {
+ var commandClass, maxRow, minRow, wasBackwards, _ref, _ref2, _ref3;
+ if (/^[1-9]$/.test(key) || (key === "0" && this.inputState.count.length)) {
+ this.inputState.count += key;
+ } else if (!this.inputState.command) {
+ commandClass = (this.inputState.keymap || Jim.keymap.visual)[key];
+ if (!commandClass) {
invalidCommand.call(this);
- } else if (command !== true) {
- this.command = command;
- this.commandPart = '';
+ } else if (commandClass.prototype) {
+ this.inputState.setCommand(commandClass);
+ } else {
+ this.inputState.keymap = commandClass;
}
- } else if (this.command.constructor.followedBy) {
- if (this.command.constructor.followedBy.test(this.commandPart)) {
- this.command.followedBy = this.commandPart;
+ } else if (this.inputState.command.constructor.followedBy) {
+ if (this.inputState.command.constructor.followedBy.test(key)) {
+ this.inputState.command.followedBy = key;
} else {
- console.log("" + this.command + " didn't expect to be followed by \"" + this.commandPart + "\"");
+ console.log("" + this.inputState.command + " didn't expect to be followed by \"" + key + "\"");
}
- this.commandPart = '';
}
wasBackwards = this.adaptor.isSelectionBackwards();
- if (((_ref = this.command) != null ? _ref.isOperation : void 0) || ((_ref2 = this.command) != null ? _ref2.isComplete() : void 0)) {
- if (this.command.isRepeatable) {
- this.command.selectionSize = this.mode.name === 'visual' && this.mode.linewise ? ((_ref3 = this.adaptor.selectionRowRange(), minRow = _ref3[0], maxRow = _ref3[1], _ref3), {
+ if (((_ref = this.inputState.command) != null ? _ref.isOperation : void 0) || ((_ref2 = this.inputState.command) != null ? _ref2.isComplete() : void 0)) {
+ if (this.inputState.command.isRepeatable) {
+ this.inputState.command.selectionSize = this.mode.name === 'visual' && this.mode.linewise ? ((_ref3 = this.adaptor.selectionRowRange(), minRow = _ref3[0], maxRow = _ref3[1], _ref3), {
lines: (maxRow - minRow) + 1
}) : this.adaptor.characterwiseSelectionSize();
- this.command.linewise = this.mode.linewise;
- this.lastCommand = this.command;
+ this.inputState.command.linewise = this.mode.linewise;
+ this.lastCommand = this.inputState.command;
}
- this.command.visualExec(this);
- this.command = null;
+ this.inputState.command.visualExec(this);
+ this.inputState.clear();
}
if (this.mode.name === 'visual' && !this.mode.linewise) {
if (wasBackwards) {
@@ -238,17 +247,18 @@ exports.replace = {
require['./jim'] = (function() {
var exports = {}, module = {};
- var Jim, Keymap;
+ var InputState, Jim, Keymap;
var __hasProp = Object.prototype.hasOwnProperty;
+InputState = require('./helpers').InputState;
Keymap = require('./keymap');
Jim = (function() {
Jim.VERSION = '0.2.1-pre';
Jim.keymap = new Keymap;
function Jim(adaptor) {
this.adaptor = adaptor;
- this.command = null;
this.registers = {};
this.setMode('normal');
+ this.inputState = new InputState;
}
Jim.prototype.modes = require('./modes');
Jim.prototype.setMode = function(modeName, modeState) {
@@ -283,8 +293,7 @@ Jim = (function() {
};
Jim.prototype.onEscape = function() {
this.setMode('normal');
- this.command = null;
- this.commandPart = '';
+ this.inputState.clear();
return this.adaptor.clearSelection();
};
Jim.prototype.onKeypress = function(keys) {
@@ -303,39 +312,9 @@ module.exports = Jim;
return module.exports || exports;
})();
-require['./helpers'] = (function() {
- var exports = {}, module = {};
- exports.Command = (function() {
- function Command(count) {
- this.count = count != null ? count : 1;
- }
- Command.prototype.isRepeatable = true;
- Command.prototype.isComplete = function() {
- if (this.constructor.followedBy) {
- return this.followedBy;
- } else {
- return true;
- }
- };
- return Command;
-})();
-exports.repeatCountTimes = function(func) {
- return function(jim) {
- var timesLeft, _results;
- timesLeft = this.count;
- _results = [];
- while (timesLeft--) {
- _results.push(func.call(this, jim));
- }
- return _results;
- };
-};
- return module.exports || exports;
-})();
-
require['./motions'] = (function() {
var exports = {}, module = {};
- var Command, GoToFirstVisibleLine, GoToLastVisibleLine, GoToLine, GoToLineOrEnd, GoToMiddleLine, GoToNextChar, GoToPreviousChar, GoUpToNextChar, GoUpToPreviousChar, Jim, LinewiseCommandMotion, Motion, MoveBackBigWord, MoveBackWord, MoveDown, MoveLeft, MoveRight, MoveToBeginningOfLine, MoveToBigWordEnd, MoveToEndOfLine, MoveToFirstNonBlank, MoveToNextBigWord, MoveToNextWord, MoveToWordEnd, MoveUp, NearestWordSearch, NearestWordSearchBackwards, Search, SearchAgain, SearchAgainReverse, SearchBackwards, WORDRegex, lastWORDRegex, lastWordRegex, map, repeatCountTimes, wordRegex, _ref;
+ var Command, GoToFirstVisibleLine, GoToLastVisibleLine, GoToLine, GoToLineOrEnd, GoToMiddleLine, GoToNextChar, GoToPreviousChar, GoUpToNextChar, GoUpToPreviousChar, Jim, Motion, MoveBackBigWord, MoveBackWord, MoveDown, MoveLeft, MoveRight, MoveToBeginningOfLine, MoveToBigWordEnd, MoveToEndOfLine, MoveToFirstNonBlank, MoveToNextBigWord, MoveToNextWord, MoveToWordEnd, MoveUp, NearestWordSearch, NearestWordSearchBackwards, Search, SearchAgain, SearchAgainReverse, SearchBackwards, WORDRegex, lastWORDRegex, lastWordRegex, map, repeatCountTimes, wordRegex, _ref;
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
function ctor() { this.constructor = child; }
@@ -362,21 +341,7 @@ Motion = (function() {
};
return Motion;
})();
-LinewiseCommandMotion = (function() {
- __extends(LinewiseCommandMotion, Motion);
- function LinewiseCommandMotion() {
- LinewiseCommandMotion.__super__.constructor.apply(this, arguments);
- }
- LinewiseCommandMotion.prototype.linewise = true;
- LinewiseCommandMotion.prototype.exec = function(jim) {
- var additionalLines;
- if (additionalLines = this.count - 1) {
- return new MoveDown(additionalLines).exec(jim);
- }
- };
- return LinewiseCommandMotion;
-})();
-map('h', MoveLeft = (function() {
+map(['h'], MoveLeft = (function() {
__extends(MoveLeft, Motion);
function MoveLeft() {
MoveLeft.__super__.constructor.apply(this, arguments);
@@ -391,7 +356,7 @@ map('h', MoveLeft = (function() {
});
return MoveLeft;
})());
-map('j', MoveDown = (function() {
+map(['j'], MoveDown = (function() {
__extends(MoveDown, Motion);
function MoveDown() {
MoveDown.__super__.constructor.apply(this, arguments);
@@ -402,7 +367,7 @@ map('j', MoveDown = (function() {
});
return MoveDown;
})());
-map('k', MoveUp = (function() {
+map(['k'], MoveUp = (function() {
__extends(MoveUp, Motion);
function MoveUp() {
MoveUp.__super__.constructor.apply(this, arguments);
@@ -413,7 +378,7 @@ map('k', MoveUp = (function() {
});
return MoveUp;
})());
-map('l', MoveRight = (function() {
+map(['l'], MoveRight = (function() {
__extends(MoveRight, Motion);
function MoveRight() {
MoveRight.__super__.constructor.apply(this, arguments);
@@ -431,11 +396,11 @@ map('l', MoveRight = (function() {
});
return MoveRight;
})());
-map('left', MoveLeft);
-map('down', MoveDown);
-map('up', MoveUp);
-map('right', MoveRight);
-map('space', (function() {
+map(['left'], MoveLeft);
+map(['down'], MoveDown);
+map(['up'], MoveUp);
+map(['right'], MoveRight);
+map(['space'], (function() {
__extends(_Class, MoveRight);
function _Class() {
_Class.__super__.constructor.apply(this, arguments);
@@ -449,7 +414,7 @@ WORDRegex = function() {
wordRegex = function() {
return /(\w+)|([^\w\s]+)/g;
};
-map('e', MoveToWordEnd = (function() {
+map(['e'], MoveToWordEnd = (function() {
__extends(MoveToWordEnd, Motion);
function MoveToWordEnd() {
MoveToWordEnd.__super__.constructor.apply(this, arguments);
@@ -482,7 +447,7 @@ map('e', MoveToWordEnd = (function() {
});
return MoveToWordEnd;
})());
-map('E', MoveToBigWordEnd = (function() {
+map(['E'], MoveToBigWordEnd = (function() {
__extends(MoveToBigWordEnd, MoveToWordEnd);
function MoveToBigWordEnd() {
MoveToBigWordEnd.__super__.constructor.apply(this, arguments);
@@ -490,7 +455,7 @@ map('E', MoveToBigWordEnd = (function() {
MoveToBigWordEnd.prototype.bigWord = true;
return MoveToBigWordEnd;
})());
-map('w', MoveToNextWord = (function() {
+map(['w'], MoveToNextWord = (function() {
__extends(MoveToNextWord, Motion);
function MoveToNextWord() {
MoveToNextWord.__super__.constructor.apply(this, arguments);
@@ -532,7 +497,7 @@ map('w', MoveToNextWord = (function() {
};
return MoveToNextWord;
})());
-map('W', MoveToNextBigWord = (function() {
+map(['W'], MoveToNextBigWord = (function() {
__extends(MoveToNextBigWord, MoveToNextWord);
function MoveToNextBigWord() {
MoveToNextBigWord.__super__.constructor.apply(this, arguments);
@@ -542,7 +507,7 @@ map('W', MoveToNextBigWord = (function() {
})());
lastWORDRegex = RegExp("" + (WORDRegex().source) + "\\s*$");
lastWordRegex = RegExp("(" + (wordRegex().source) + ")\\s*$");
-map('b', MoveBackWord = (function() {
+map(['b'], MoveBackWord = (function() {
__extends(MoveBackWord, Motion);
function MoveBackWord() {
MoveBackWord.__super__.constructor.apply(this, arguments);
@@ -569,7 +534,7 @@ map('b', MoveBackWord = (function() {
});
return MoveBackWord;
})());
-map('B', MoveBackBigWord = (function() {
+map(['B'], MoveBackBigWord = (function() {
__extends(MoveBackBigWord, MoveBackWord);
function MoveBackBigWord() {
MoveBackBigWord.__super__.constructor.apply(this, arguments);
@@ -577,7 +542,7 @@ map('B', MoveBackBigWord = (function() {
MoveBackBigWord.prototype.bigWord = true;
return MoveBackBigWord;
})());
-map('0', MoveToBeginningOfLine = (function() {
+map(['0'], MoveToBeginningOfLine = (function() {
__extends(MoveToBeginningOfLine, Motion);
function MoveToBeginningOfLine() {
MoveToBeginningOfLine.__super__.constructor.apply(this, arguments);
@@ -588,7 +553,7 @@ map('0', MoveToBeginningOfLine = (function() {
};
return MoveToBeginningOfLine;
})());
-map('^', MoveToFirstNonBlank = (function() {
+map(['^'], MoveToFirstNonBlank = (function() {
__extends(MoveToFirstNonBlank, Motion);
function MoveToFirstNonBlank() {
MoveToFirstNonBlank.__super__.constructor.apply(this, arguments);
@@ -602,7 +567,7 @@ map('^', MoveToFirstNonBlank = (function() {
};
return MoveToFirstNonBlank;
})());
-map('$', MoveToEndOfLine = (function() {
+map(['$'], MoveToEndOfLine = (function() {
__extends(MoveToEndOfLine, Motion);
function MoveToEndOfLine() {
MoveToEndOfLine.__super__.constructor.apply(this, arguments);
@@ -617,7 +582,7 @@ map('$', MoveToEndOfLine = (function() {
};
return MoveToEndOfLine;
})());
-map('gg', GoToLine = (function() {
+map(['g', 'g'], GoToLine = (function() {
__extends(GoToLine, Motion);
function GoToLine() {
GoToLine.__super__.constructor.apply(this, arguments);
@@ -632,7 +597,7 @@ map('gg', GoToLine = (function() {
};
return GoToLine;
})());
-map('G', GoToLineOrEnd = (function() {
+map(['G'], GoToLineOrEnd = (function() {
__extends(GoToLineOrEnd, GoToLine);
function GoToLineOrEnd(count) {
this.count = count;
@@ -643,7 +608,7 @@ map('G', GoToLineOrEnd = (function() {
};
return GoToLineOrEnd;
})());
-map('H', GoToFirstVisibleLine = (function() {
+map(['H'], GoToFirstVisibleLine = (function() {
__extends(GoToFirstVisibleLine, Motion);
function GoToFirstVisibleLine() {
GoToFirstVisibleLine.__super__.constructor.apply(this, arguments);
@@ -656,7 +621,7 @@ map('H', GoToFirstVisibleLine = (function() {
};
return GoToFirstVisibleLine;
})());
-map('M', GoToMiddleLine = (function() {
+map(['M'], GoToMiddleLine = (function() {
__extends(GoToMiddleLine, Motion);
function GoToMiddleLine() {
GoToMiddleLine.__super__.constructor.apply(this, arguments);
@@ -671,7 +636,7 @@ map('M', GoToMiddleLine = (function() {
};
return GoToMiddleLine;
})());
-map('L', GoToLastVisibleLine = (function() {
+map(['L'], GoToLastVisibleLine = (function() {
__extends(GoToLastVisibleLine, Motion);
function GoToLastVisibleLine() {
GoToLastVisibleLine.__super__.constructor.apply(this, arguments);
@@ -684,7 +649,7 @@ map('L', GoToLastVisibleLine = (function() {
};
return GoToLastVisibleLine;
})());
-map('/', Search = (function() {
+map(['/'], Search = (function() {
__extends(Search, Motion);
function Search() {
Search.__super__.constructor.apply(this, arguments);
@@ -717,7 +682,7 @@ map('/', Search = (function() {
};
return Search;
})());
-map('?', SearchBackwards = (function() {
+map(['?'], SearchBackwards = (function() {
__extends(SearchBackwards, Search);
function SearchBackwards() {
SearchBackwards.__super__.constructor.apply(this, arguments);
@@ -725,7 +690,7 @@ map('?', SearchBackwards = (function() {
SearchBackwards.prototype.backwards = true;
return SearchBackwards;
})());
-map('*', NearestWordSearch = (function() {
+map(['*'], NearestWordSearch = (function() {
var nearestWord;
__extends(NearestWordSearch, Search);
function NearestWordSearch() {
@@ -764,7 +729,7 @@ map('*', NearestWordSearch = (function() {
};
return NearestWordSearch;
})());
-map('#', NearestWordSearchBackwards = (function() {
+map(['#'], NearestWordSearchBackwards = (function() {
__extends(NearestWordSearchBackwards, NearestWordSearch);
function NearestWordSearchBackwards() {
NearestWordSearchBackwards.__super__.constructor.apply(this, arguments);
@@ -772,7 +737,7 @@ map('#', NearestWordSearchBackwards = (function() {
NearestWordSearchBackwards.prototype.backwards = true;
return NearestWordSearchBackwards;
})());
-map('n', SearchAgain = (function() {
+map(['n'], SearchAgain = (function() {
__extends(SearchAgain, Motion);
function SearchAgain() {
SearchAgain.__super__.constructor.apply(this, arguments);
@@ -783,7 +748,7 @@ map('n', SearchAgain = (function() {
};
return SearchAgain;
})());
-map('N', SearchAgainReverse = (function() {
+map(['N'], SearchAgainReverse = (function() {
__extends(SearchAgainReverse, Motion);
function SearchAgainReverse() {
SearchAgainReverse.__super__.constructor.apply(this, arguments);
@@ -794,7 +759,7 @@ map('N', SearchAgainReverse = (function() {
};
return SearchAgainReverse;
})());
-map('f', GoToNextChar = (function() {
+map(['f'], GoToNextChar = (function() {
__extends(GoToNextChar, Motion);
function GoToNextChar() {
GoToNextChar.__super__.constructor.apply(this, arguments);
@@ -818,7 +783,7 @@ map('f', GoToNextChar = (function() {
};
return GoToNextChar;
})());
-map('t', GoUpToNextChar = (function() {
+map(['t'], GoUpToNextChar = (function() {
__extends(GoUpToNextChar, GoToNextChar);
function GoUpToNextChar() {
GoUpToNextChar.__super__.constructor.apply(this, arguments);
@@ -826,7 +791,7 @@ map('t', GoUpToNextChar = (function() {
GoUpToNextChar.prototype.beforeChar = true;
return GoUpToNextChar;
})());
-map('F', GoToPreviousChar = (function() {
+map(['F'], GoToPreviousChar = (function() {
__extends(GoToPreviousChar, Motion);
function GoToPreviousChar() {
GoToPreviousChar.__super__.constructor.apply(this, arguments);
@@ -850,7 +815,7 @@ map('F', GoToPreviousChar = (function() {
};
return GoToPreviousChar;
})());
-map('T', GoUpToPreviousChar = (function() {
+map(['T'], GoUpToPreviousChar = (function() {
__extends(GoUpToPreviousChar, GoToPreviousChar);
function GoUpToPreviousChar() {
GoUpToPreviousChar.__super__.constructor.apply(this, arguments);
@@ -859,13 +824,13 @@ map('T', GoUpToPreviousChar = (function() {
return GoUpToPreviousChar;
})());
module.exports = {
+ Motion: Motion,
GoToLine: GoToLine,
MoveDown: MoveDown,
MoveLeft: MoveLeft,
MoveRight: MoveRight,
MoveToEndOfLine: MoveToEndOfLine,
MoveToFirstNonBlank: MoveToFirstNonBlank,
- LinewiseCommandMotion: LinewiseCommandMotion,
MoveToNextBigWord: MoveToNextBigWord,
MoveToNextWord: MoveToNextWord,
MoveToBigWordEnd: MoveToBigWordEnd,
@@ -932,7 +897,7 @@ Operation = (function() {
};
return Operation;
})();
-map('c', Change = (function() {
+map(['c'], Change = (function() {
__extends(Change, Operation);
function Change() {
Change.__super__.constructor.apply(this, arguments);
@@ -956,7 +921,7 @@ map('c', Change = (function() {
Change.prototype.switchToMode = 'insert';
return Change;
})());
-map('d', Delete = (function() {
+map(['d'], Delete = (function() {
__extends(Delete, Operation);
function Delete() {
Delete.__super__.constructor.apply(this, arguments);
@@ -970,7 +935,7 @@ map('d', Delete = (function() {
};
return Delete;
})());
-map('y', Yank = (function() {
+map(['y'], Yank = (function() {
__extends(Yank, Operation);
function Yank() {
Yank.__super__.constructor.apply(this, arguments);
@@ -984,7 +949,7 @@ map('y', Yank = (function() {
};
return Yank;
})());
-map('>', Indent = (function() {
+map(['>'], Indent = (function() {
__extends(Indent, Operation);
function Indent() {
Indent.__super__.constructor.apply(this, arguments);
@@ -997,7 +962,7 @@ map('>', Indent = (function() {
};
return Indent;
})());
-map('<', Outdent = (function() {
+map(['<'], Outdent = (function() {
__extends(Outdent, Operation);
function Outdent() {
Outdent.__super__.constructor.apply(this, arguments);
@@ -1048,7 +1013,7 @@ ModeSwitch = (function() {
};
return ModeSwitch;
})();
-map('v', VisualSwitch = (function() {
+map(['v'], VisualSwitch = (function() {
__extends(VisualSwitch, Command);
function VisualSwitch() {
VisualSwitch.__super__.constructor.apply(this, arguments);
@@ -1075,7 +1040,7 @@ map('v', VisualSwitch = (function() {
};
return VisualSwitch;
})());
-map('V', VisualLinewiseSwitch = (function() {
+map(['V'], VisualLinewiseSwitch = (function() {
__extends(VisualLinewiseSwitch, Command);
function VisualLinewiseSwitch() {
VisualLinewiseSwitch.__super__.constructor.apply(this, arguments);
@@ -1106,7 +1071,7 @@ map('V', VisualLinewiseSwitch = (function() {
};
return VisualLinewiseSwitch;
})());
-map('i', Insert = (function() {
+map(['i'], Insert = (function() {
__extends(Insert, ModeSwitch);
function Insert() {
Insert.__super__.constructor.apply(this, arguments);
@@ -1125,7 +1090,7 @@ map('i', Insert = (function() {
};
return Insert;
})());
-map('a', InsertAfter = (function() {
+map(['a'], InsertAfter = (function() {
__extends(InsertAfter, Insert);
function InsertAfter() {
InsertAfter.__super__.constructor.apply(this, arguments);
@@ -1135,7 +1100,7 @@ map('a', InsertAfter = (function() {
};
return InsertAfter;
})());
-map('A', InsertAtEndOfLine = (function() {
+map(['A'], InsertAtEndOfLine = (function() {
__extends(InsertAtEndOfLine, Insert);
function InsertAtEndOfLine() {
InsertAtEndOfLine.__super__.constructor.apply(this, arguments);
@@ -1146,7 +1111,7 @@ map('A', InsertAtEndOfLine = (function() {
};
return InsertAtEndOfLine;
})());
-map('C', ChangeToEndOfLine = (function() {
+map(['C'], ChangeToEndOfLine = (function() {
__extends(ChangeToEndOfLine, Insert);
function ChangeToEndOfLine() {
ChangeToEndOfLine.__super__.constructor.apply(this, arguments);
@@ -1156,7 +1121,7 @@ map('C', ChangeToEndOfLine = (function() {
};
return ChangeToEndOfLine;
})());
-map('I', InsertBeforeFirstNonBlank = (function() {
+map(['I'], InsertBeforeFirstNonBlank = (function() {
__extends(InsertBeforeFirstNonBlank, Insert);
function InsertBeforeFirstNonBlank() {
InsertBeforeFirstNonBlank.__super__.constructor.apply(this, arguments);
@@ -1166,7 +1131,7 @@ map('I', InsertBeforeFirstNonBlank = (function() {
};
return InsertBeforeFirstNonBlank;
})());
-map('o', OpenLine = (function() {
+map(['o'], OpenLine = (function() {
__extends(OpenLine, Insert);
function OpenLine() {
OpenLine.__super__.constructor.apply(this, arguments);
@@ -1179,7 +1144,7 @@ map('o', OpenLine = (function() {
};
return OpenLine;
})());
-map('O', OpenLineAbove = (function() {
+map(['O'], OpenLineAbove = (function() {
__extends(OpenLineAbove, OpenLine);
function OpenLineAbove() {
OpenLineAbove.__super__.constructor.apply(this, arguments);
@@ -1187,7 +1152,7 @@ map('O', OpenLineAbove = (function() {
OpenLineAbove.prototype.above = true;
return OpenLineAbove;
})());
-map('s', ChangeChar = (function() {
+map(['s'], ChangeChar = (function() {
__extends(ChangeChar, Insert);
function ChangeChar() {
ChangeChar.__super__.constructor.apply(this, arguments);
@@ -1197,7 +1162,7 @@ map('s', ChangeChar = (function() {
};
return ChangeChar;
})());
-map('R', ReplaceSwitch = (function() {
+map(['R'], ReplaceSwitch = (function() {
__extends(ReplaceSwitch, ModeSwitch);
function ReplaceSwitch() {
ReplaceSwitch.__super__.constructor.apply(this, arguments);
@@ -1208,7 +1173,7 @@ map('R', ReplaceSwitch = (function() {
ReplaceSwitch.prototype.switchToMode = 'replace';
return ReplaceSwitch;
})());
-map('gJ', JoinLines = (function() {
+map(['g', 'J'], JoinLines = (function() {
__extends(JoinLines, Command);
function JoinLines() {
JoinLines.__super__.constructor.apply(this, arguments);
@@ -1235,7 +1200,7 @@ map('gJ', JoinLines = (function() {
};
return JoinLines;
})());
-map('J', JoinLinesNormalizingWhitespace = (function() {
+map(['J'], JoinLinesNormalizingWhitespace = (function() {
__extends(JoinLinesNormalizingWhitespace, JoinLines);
function JoinLinesNormalizingWhitespace() {
JoinLinesNormalizingWhitespace.__super__.constructor.apply(this, arguments);
@@ -1243,7 +1208,7 @@ map('J', JoinLinesNormalizingWhitespace = (function() {
JoinLinesNormalizingWhitespace.prototype.normalize = true;
return JoinLinesNormalizingWhitespace;
})());
-map('D', DeleteToEndOfLine = (function() {
+map(['D'], DeleteToEndOfLine = (function() {
__extends(DeleteToEndOfLine, Command);
function DeleteToEndOfLine() {
DeleteToEndOfLine.__super__.constructor.apply(this, arguments);
@@ -1253,7 +1218,7 @@ map('D', DeleteToEndOfLine = (function() {
};
return DeleteToEndOfLine;
})());
-map('p', Paste = (function() {
+map(['p'], Paste = (function() {
__extends(Paste, Command);
function Paste() {
Paste.__super__.constructor.apply(this, arguments);
@@ -1297,7 +1262,7 @@ map('p', Paste = (function() {
};
return Paste;
})());
-map('P', PasteBefore = (function() {
+map(['P'], PasteBefore = (function() {
__extends(PasteBefore, Paste);
function PasteBefore() {
PasteBefore.__super__.constructor.apply(this, arguments);
@@ -1305,7 +1270,7 @@ map('P', PasteBefore = (function() {
PasteBefore.prototype.before = true;
return PasteBefore;
})());
-map('r', ReplaceChar = (function() {
+map(['r'], ReplaceChar = (function() {
__extends(ReplaceChar, Command);
function ReplaceChar() {
ReplaceChar.__super__.constructor.apply(this, arguments);
@@ -1322,7 +1287,7 @@ map('r', ReplaceChar = (function() {
};
return ReplaceChar;
})());
-map('.', RepeatCommand = (function() {
+map(['.'], RepeatCommand = (function() {
__extends(RepeatCommand, Command);
function RepeatCommand() {
RepeatCommand.__super__.constructor.apply(this, arguments);
@@ -1362,7 +1327,7 @@ map('.', RepeatCommand = (function() {
};
return RepeatCommand;
})());
-map('u', Undo = (function() {
+map(['u'], Undo = (function() {
__extends(Undo, Command);
function Undo() {
Undo.__super__.constructor.apply(this, arguments);
@@ -1373,7 +1338,7 @@ map('u', Undo = (function() {
});
return Undo;
})());
-map('x', DeleteChar = (function() {
+map(['x'], DeleteChar = (function() {
__extends(DeleteChar, Command);
function DeleteChar() {
DeleteChar.__super__.constructor.apply(this, arguments);
@@ -1386,7 +1351,7 @@ map('x', DeleteChar = (function() {
};
return DeleteChar;
})());
-map('X', Backspace = (function() {
+map(['X'], Backspace = (function() {
__extends(Backspace, Command);
function Backspace() {
Backspace.__super__.constructor.apply(this, arguments);
@@ -1402,7 +1367,7 @@ map('X', Backspace = (function() {
};
return Backspace;
})());
-map('backspace', (function() {
+map(['backspace'], (function() {
__extends(_Class, MoveLeft);
function _Class() {
_Class.__super__.constructor.apply(this, arguments);
@@ -1413,7 +1378,7 @@ map('backspace', (function() {
};
return _Class;
})());
-map('delete', DeleteChar);
+map(['delete'], DeleteChar);
module.exports = {};
return module.exports || exports;
})();
View
46 src/commands.coffee
@@ -22,7 +22,7 @@ class ModeSwitch extends Command
# --------------------
# Switch to characterwise visual mode.
-map 'v', class VisualSwitch extends Command
+map ['v'], class VisualSwitch extends Command
isRepeatable: no
exec: (jim) ->
anchor = jim.adaptor.position()
@@ -36,7 +36,7 @@ map 'v', class VisualSwitch extends Command
jim.onEscape()
# Switch to linewise visual mode.
-map 'V', class VisualLinewiseSwitch extends Command
+map ['V'], class VisualLinewiseSwitch extends Command
isRepeatable: no
exec: (jim) ->
anchor = jim.adaptor.setLinewiseSelectionAnchor()
@@ -55,7 +55,7 @@ map 'V', class VisualLinewiseSwitch extends Command
# --------------------
# Insert before the char under the cursor.
-map 'i', class Insert extends ModeSwitch
+map ['i'], class Insert extends ModeSwitch
switchToMode: 'insert'
exec: (jim) ->
@beforeSwitch? jim
@@ -75,44 +75,44 @@ map 'i', class Insert extends ModeSwitch
jim.setMode @switchToMode
# Insert after the char under the cursor.
-map 'a', class InsertAfter extends Insert
+map ['a'], class InsertAfter extends Insert
beforeSwitch: (jim) -> jim.adaptor.moveRight true
# Insert at the end of the line.
-map 'A', class InsertAtEndOfLine extends Insert
+map ['A'], class InsertAtEndOfLine extends Insert
beforeSwitch: (jim) ->
new MoveToEndOfLine().exec jim
jim.adaptor.moveRight true
# Delete all remaining text on the line and insert in its place.
-map 'C', class ChangeToEndOfLine extends Insert
+map ['C'], class ChangeToEndOfLine extends Insert
beforeSwitch: (jim) ->
new DeleteToEndOfLine(@count).exec jim
# Insert before to first non-blank char of the line.
-map 'I', class InsertBeforeFirstNonBlank extends Insert
+map ['I'], class InsertBeforeFirstNonBlank extends Insert
beforeSwitch: (jim) -> new MoveToFirstNonBlank().exec jim
# Create a new line below the cursor and insert there.
-map 'o', class OpenLine extends Insert
+map ['o'], class OpenLine extends Insert
beforeSwitch: (jim) ->
row = jim.adaptor.row() + (if @above then 0 else 1)
jim.adaptor.insertNewLine row
jim.adaptor.moveTo row, 0
# Create a new line above the cursor and insert there.
-map 'O', class OpenLineAbove extends OpenLine
+map ['O'], class OpenLineAbove extends OpenLine
above: yes
# Replace the char under the cursor with an insert.
-map 's', class ChangeChar extends Insert
+map ['s'], class ChangeChar extends Insert
beforeSwitch: (jim) -> new DeleteChar(@count).exec jim
# Replace mode switch
# -------------------
-map 'R', class ReplaceSwitch extends ModeSwitch
+map ['R'], class ReplaceSwitch extends ModeSwitch
beforeSwitch: (jim) -> jim.adaptor.setOverwriteMode on
switchToMode: 'replace'
@@ -121,7 +121,7 @@ map 'R', class ReplaceSwitch extends ModeSwitch
# ----------------------
# Join a line with the line following it.
-map 'gJ', class JoinLines extends Command
+map ['g', 'J'], class JoinLines extends Command
exec: (jim) ->
timesLeft = Math.max(@count, 2) - 1
while timesLeft--
@@ -141,15 +141,15 @@ map 'gJ', class JoinLines extends Command
# Join a line with the line following it, ensuring that one space separates the
# content from the lines.
-map 'J', class JoinLinesNormalizingWhitespace extends JoinLines
+map ['J'], class JoinLinesNormalizingWhitespace extends JoinLines
normalize: yes
# Delete all remaining text on the line.
-map 'D', class DeleteToEndOfLine extends Command
+map ['D'], class DeleteToEndOfLine extends Command
exec: (jim) -> new Delete(1, new MoveToEndOfLine @count).exec jim
# Paste after the cursor. Paste after the line if pasting linewise register.
-map 'p', class Paste extends Command
+map ['p'], class Paste extends Command
exec: (jim) ->
return if not registerValue = jim.registers['"']
@@ -190,11 +190,11 @@ map 'p', class Paste extends Command
jim.setMode 'normal'
# Paste before the cursor. Paste before the line if pasting linewise register.
-map 'P', class PasteBefore extends Paste
+map ['P'], class PasteBefore extends Paste
before: yes
# Replace the char under the cursor with the char pressed after `r`.
-map 'r', class ReplaceChar extends Command
+map ['r'], class ReplaceChar extends Command
# Match `[\s\S]` so that it will match `\n` (windows' `\r\n`?)
@followedBy: /[\s\S]+/
exec: (jim) ->
@@ -210,7 +210,7 @@ map 'r', class ReplaceChar extends Command
# Repeat the last repeatable command.
-map '.', class RepeatCommand extends Command
+map ['.'], class RepeatCommand extends Command
isRepeatable: no
exec: (jim) ->
command = jim.lastCommand
@@ -248,17 +248,17 @@ map '.', class RepeatCommand extends Command
command.exec jim
# Undo the last command that modified the document.
-map 'u', class Undo extends Command
+map ['u'], class Undo extends Command
isRepeatable: no
exec: repeatCountTimes (jim) -> jim.adaptor.undo()
# Delete the char under the cursor.
-map 'x', class DeleteChar extends Command
+map ['x'], class DeleteChar extends Command
exec: (jim) -> new Delete(1, new MoveRight @count).exec jim
visualExec: (jim) -> Delete::visualExec jim
# Delete the char before the cursor.
-map 'X', class Backspace extends Command
+map ['X'], class Backspace extends Command
exec: (jim) -> new Delete(1, new MoveLeft @count).exec jim
visualExec: (jim) ->
del = new Delete(@count)
@@ -268,11 +268,11 @@ map 'X', class Backspace extends Command
# Move left in normal mode
# Delete selections in visual mode
-map 'backspace', class extends MoveLeft
+map ['backspace'], class extends MoveLeft
prevLine: yes
visualExec: (jim) -> Delete::visualExec jim
-map 'delete', DeleteChar
+map ['delete'], DeleteChar
# Exports
# -------
View
27 src/helpers.coffee
@@ -8,6 +8,33 @@ class exports.Command
isComplete: ->
if @constructor.followedBy then @followedBy else true
+class exports.InputState
+ constructor: ->
+ @clear()
+
+ clear: ->
+ @command = null
+ @count = ''
+ @keymap = null
+ @operatorPending = null
+
+ setCommand: (commandClass) ->
+ @command = new commandClass(parseInt(@count) or null)
+ @count = ''
+
+ setOperationMotion: (motionClass) ->
+ @command.motion = new motionClass(parseInt(@count) or null)
+
+ # Motions need a reference to the operation they're a part of since it
+ # sometimes changes the amount of text they move over (e.g. `cw`
+ # deletes less text than `dw`).
+ @command.motion.operation = @command
+
+ @count = ''
+
+ toString: ->
+ "TODO"
+
# A bunch of commands can just repeat an action however many times their `@count`
# specifies. For example `5x` does exactly the same thing as pressing `x` five times.
# This helper is used for that case.
View
8 src/jim.coffee
@@ -4,7 +4,8 @@
# `Command`s are passed an instance of `Jim` when they are executed which allows
# them to change Jim's state and manipulate the editor (through the `@adaptor`).
-Keymap = require './keymap'
+{InputState} = require './helpers'
+Keymap = require './keymap'
class Jim
@VERSION: '0.2.1-pre'
@@ -12,9 +13,9 @@ class Jim
@keymap: new Keymap
constructor: (@adaptor) ->
- @command = null
@registers = {}
@setMode 'normal'
+ @inputState = new InputState
modes: require './modes'
@@ -47,8 +48,7 @@ class Jim
# Pressing escape blows away all the state.
onEscape: ->
@setMode 'normal'
- @command = null
- @commandPart = '' # just in case...
+ @inputState.clear()
@adaptor.clearSelection()
# When a key is pressed, let the current mode figure out what to do about it.
View
129 src/keymap.coffee
@@ -6,133 +6,38 @@
class Keymap
constructor: ->
- @commands = {}
- @motions = {}
- @visualCommands = {}
-
- # Use some objects to de-duplicate repeated partial commands.
- @partialCommands = {}
- @partialMotions = {}
- @partialVisualCommands = {}
+ @normal = {}
+ @visual = {}
+ @operatorPending = {}
# Mapping commands
# ----------------
- # Map the `comandClass` to the `keys` sequence. Map it as a visual command as well
+ mapIntoObject = (object, keys, command) ->
+ for key in keys[0..-2]
+ object[key] or= {}
+ object = object[key]
+ object[keys[keys.length-1]] = command
+
+ # Map the `commandClass` to the `keys` sequence. Map it as a visual command as well
# if the class has a `::visualExec`.
mapCommand: (keys, commandClass) ->
if commandClass::exec
- @commands[keys] = commandClass
- if keys.length is 2 and keys isnt 'up'
- @partialCommands[keys[0]] = true
+ mapIntoObject @normal, keys, commandClass
if commandClass::visualExec
- @visualCommands[keys] = commandClass
- if keys.length is 2 and keys isnt 'up'
- @partialVisualCommands[keys[0]] = true
+ mapIntoObject @visual, keys, commandClass
# Map `motionClass` to the `keys` sequence.
mapMotion: (keys, motionClass) ->
- @commands[keys] = motionClass
- @motions[keys] = motionClass
- @visualCommands[keys] = motionClass
- if keys.length is 2 and keys isnt 'up'
- @partialMotions[keys[0]] = true
- @partialCommands[keys[0]] = true
- @partialVisualCommands[keys[0]] = true
+ mapIntoObject @normal, keys, motionClass
+ mapIntoObject @visual, keys, motionClass
+ mapIntoObject @operatorPending, keys, motionClass
# Map `operatorClass` to the `keys` sequence.
mapOperator: (keys, operatorClass) ->
- @commands[keys] = operatorClass
- @visualCommands[keys] = operatorClass
- if keys.length is 2 and keys isnt 'up'
- @partialCommands[keys[0]] = true
- @partialVisualCommands[keys[0]] = true
-
-
- # Finding commands in the Keymap
- # ------------------------------
- #
- # `commandFor`, `motionFor`, and `visualCommandFor` are defined for finding
- # their respective `Command` types. Each of these methods will return one of the
- # following:
- #
- # * `true` if the `commandPart` passed in is a valid *partial* command. For
- # example, `Keymap.getDefault().commandFor('g')` will return `true` because
- # it is the first part of what could be the valid command `gg`, among
- # others.
- # * `false` if the `commandPart` is not a valid partial *or* complete command.
- # * A `Command` if the `commandPart` is a valid, complete command. The
- # `Command` will have it's `count` populated if `commandPart` includes a
- # count.
-
- # Build a regex that will help us split up the `commandPart` in each of the
- # following methods. The regex will match any key sequence, splitting it into
- # the following captured groups:
- #
- # 1. The preceding count
- # 2. The command/motion/operator
- # 3. Any chars beyond a *partial* command/motion/operator. If this group
- # captures *anything*, we can stop accepting keystrokes for the command and
- # execute it if it's valid.
- buildPartialCommandRegex = (partialCommands) ->
- ///
- ^
- ([1-9]\d*)?
- (
- [#{(char for own char, nothing of partialCommands).join ''}]?
- ([\s\S]*)
- )?
- $
- ///
-
-
- # Find a normal mode command, which could be a motion, an operator, or a
- # "regular" normal mode command.
- commandFor: (commandPart) ->
- @partialCommandRegex or= buildPartialCommandRegex @partialCommands
- [commandPart, count, command, beyondPartial] = commandPart.match @partialCommandRegex
-
- if beyondPartial
- if commandClass = @commands[command]
- new commandClass(parseInt(count) or null)
- else
- false
- else
- true
-
- # Find a motion.
- motionFor: (commandPart, operatorPending) ->
- @partialMotionRegex or= buildPartialCommandRegex @partialMotions
- [commandPart, count, motion, beyondPartial] = commandPart.match @partialCommandRegex
-
- if beyondPartial
- if motion is operatorPending
-
- # If we're finding `cc`, `yy`, etc, we return a "fake" linewise command.
- {LinewiseCommandMotion} = require './motions'
- new LinewiseCommandMotion(parseInt(count) or null)
-
- else if motionClass = @motions[motion]
- new motionClass(parseInt(count) or null)
- else
- false
- else
- true
-
- # Find a visual mode command, which could be a motion, an operator, or a
- # "regular" visual mode command.
- visualCommandFor: (commandPart) ->
- @partialVisualCommandRegex or= buildPartialCommandRegex @partialVisualCommands
- [commandPart, count, command, beyondPartial] = commandPart.match @partialVisualCommandRegex
-
- if beyondPartial
- if commandClass = @visualCommands[command]
- new commandClass(parseInt(count) or null)
- else
- false
- else
- true
+ mapIntoObject @normal, keys, operatorClass
+ mapIntoObject @visual, keys, operatorClass
# Exports
View
147 src/modes.coffee
@@ -4,123 +4,134 @@
# keyboard handling is defined here.
#
# Each mode's `onkeypress` is executed in the context of an instance of `Jim`.
-# In normal and visual mode the current `@commandPart` is the current *part* of
-# the command that's being typed. For an operation, the operator is one *part*
-# and the motion is another. `@commandPart` can one of the following:
-#
-# * `{count}command`
-# * `{count}motion`
-# * `{count}operator`
-# * chars expected to follow a command (e.g. when `r` is pressed, the next
-# `@commandPart` will be the char that's used as the replacement)
+
+# Define an unmapped `Motion` that will be used for double operators (e.g. `cc`,
+# `2yy`, `3d4d`).
+class LinewiseCommandMotion
+ constructor: (@count = 1) ->
+ linewise: yes
+ isComplete: -> yes
+ exec: (jim) ->
+ additionalLines = @count - 1
+ jim.adaptor.moveDown() while additionalLines--
+
# Shame the user in the console for not knowing their Jim commands.
invalidCommand = (type = 'command') ->
- console.log "invalid #{type}: #{@commandPart}"
+ console.log "invalid #{type}: #{@inputState}"
@onEscape()
+
# Normal mode (a.k.a. "command mode")
# -----------------------------------
exports.normal =
- onKeypress: (keys) ->
- @commandPart = (@commandPart or '') + keys
+ onKeypress: (key) ->
+ if /^[1-9]$/.test(key) or (key is "0" and @inputState.count.length)
+ @inputState.count += key
- if not @command
- command = Jim.keymap.commandFor @commandPart
+ else if not @inputState.command
+ commandClass = (@inputState.keymap or Jim.keymap.normal)[key]
- if command is false
+ if not commandClass
invalidCommand.call this
- else if command isnt true
- if command.isOperation
+
+ else if commandClass.prototype
+ @inputState.setCommand commandClass
+
+ if @inputState.command.isOperation
# Hang onto the pending operator so that double-operators can
# recognized (`cc`, `yy`, etc).
- [@operatorPending] = @commandPart.match /[^\d]+$/
+ @inputState.operatorPending = key
+
+ else if commandClass
+ @inputState.keymap = commandClass
- @command = command
- @commandPart = ''
- else if @command.constructor.followedBy
+ else if @inputState.command.constructor.followedBy
# If we've got a command that expects a key to follow it, check if
- # `@commandPart` is what it's expecting.
- if @command.constructor.followedBy.test @commandPart
- @command.followedBy = @commandPart
+ # the key is what it's expecting.
+ if @inputState.command.constructor.followedBy.test key
+ @inputState.command.followedBy = key
else
- console.log "#{@command} didn't expect to be followed by \"#{@commandPart}\""
+ console.log "#{@inputState.command} didn't expect to be followed by \"#{key}\""
- @commandPart = ''
- else if @command.isOperation
- if regex = @command.motion?.constructor.followedBy
+ else if @inputState.operatorPending
+ if regex = @inputState.command.motion?.constructor.followedBy
# If we've got a motion that expects a key to follow it, check if
- # `@commandPart` is what it's expecting.
- if regex.test @commandPart
- @command.motion.followedBy = @commandPart
+ # the key is what it's expecting.
+ if regex.test key
+ @inputState.command.motion.followedBy = key
else
- console.log "#{@command} didn't expect to be followed by \"#{@commandPart}\""
+ console.log "#{@inputState.command} didn't expect to be followed by \"#{key}\""
else
- motion = Jim.keymap.motionFor @commandPart, @operatorPending
+ motionClass = if key is @inputState.operatorPending
+ LinewiseCommandMotion
+ else
+ (@inputState.keymap or Jim.keymap.operatorPending)[key]
+
+ if not motionClass
+ invalidCommand.call this
- if motion is false
- invalidCommand.call this, 'motion'
- else if motion isnt true
- # Motions need a reference to the operation they're a part of since it
- # sometimes changes the amount of text they move over (e.g. `cw`
- # deletes less text than `dw`).
- motion.operation = @command
+ else if motionClass.prototype
+ @inputState.setOperationMotion motionClass
- @command.motion = motion
- @operatorPending = null
- @commandPart = ''
+ else
+ @inputState.keymap = motion
# Execute the command if it's complete, otherwise wait for more keys.
- if @command?.isComplete()
- @command.exec this
- @lastCommand = @command if @command.isRepeatable
- @command = null
+ if @inputState.command?.isComplete()
+ @inputState.command.exec this
+ @lastCommand = @inputState.command if @inputState.command.isRepeatable
+ @inputState.clear()
# Visual mode
# -----------
exports.visual =
- onKeypress: (newKeys) ->
- @commandPart = (@commandPart or '') + newKeys
+ onKeypress: (key) ->
+ if /^[1-9]$/.test(key) or (key is "0" and @inputState.count.length)
+ @inputState.count += key
- if not @command
- command = Jim.keymap.visualCommandFor @commandPart
+ else if not @inputState.command
+ commandClass = (@inputState.keymap or Jim.keymap.visual)[key]
- if command is false
+ if not commandClass
invalidCommand.call this
- else if command isnt true
- @command = command
- @commandPart = ''
- else if @command.constructor.followedBy
+
+ else if commandClass.prototype
+ @inputState.setCommand commandClass
+
+ else
+ @inputState.keymap = commandClass
+
+ else if @inputState.command.constructor.followedBy
# If we've got a motion that expects a key to follow it, check if
- # `@commandPart` is what it's expecting.
- if @command.constructor.followedBy.test @commandPart
- @command.followedBy = @commandPart
+ # the key is what it's expecting.
+ if @inputState.command.constructor.followedBy.test key
+ @inputState.command.followedBy = key
else
- console.log "#{@command} didn't expect to be followed by \"#{@commandPart}\""
- @commandPart = ''
+ console.log "#{@inputState.command} didn't expect to be followed by \"#{key}\""
wasBackwards = @adaptor.isSelectionBackwards()
# Operations are always "complete" in visual mode.
- if @command?.isOperation or @command?.isComplete()
- if @command.isRepeatable
+ if @inputState.command?.isOperation or @inputState.command?.isComplete()
+ if @inputState.command.isRepeatable
# Save the selection's "size", which will be used if the command is
# repeated.
- @command.selectionSize = if @mode.name is 'visual' and @mode.linewise
+ @inputState.command.selectionSize = if @mode.name is 'visual' and @mode.linewise
[minRow, maxRow] = @adaptor.selectionRowRange()
lines: (maxRow - minRow) + 1
else
@adaptor.characterwiseSelectionSize()
- @command.linewise = @mode.linewise
+ @inputState.command.linewise = @mode.linewise
- @lastCommand = @command
+ @lastCommand = @inputState.command
- @command.visualExec this
- @command = null
+ @inputState.command.visualExec this
+ @inputState.clear()
# If we haven't changed out of characterwise visual mode and the direction
# of the selection changes, we have to make sure that the anchor character
View
77 src/motions.coffee
@@ -22,30 +22,21 @@ class Motion extends Command
visualExec: (jim) -> @exec jim
-# Define an unmapped `Motion` that will be used for double operators (e.g. `cc`,
-# `2yy`, `3d4d`).
-class LinewiseCommandMotion extends Motion
- linewise: yes
- exec: (jim) ->
- if additionalLines = @count - 1
- new MoveDown(additionalLines).exec jim
-
-
# Basic directional motions
# -------------------------
-map 'h', class MoveLeft extends Motion
+map ['h'], class MoveLeft extends Motion
exclusive: yes
exec: repeatCountTimes (jim) ->
if @prevLine and jim.adaptor.column() is 0
jim.adaptor.moveToEndOfPreviousLine()
else jim.adaptor.moveLeft()
-map 'j', class MoveDown extends Motion
+map ['j'], class MoveDown extends Motion
linewise: yes
exec: repeatCountTimes (jim) -> jim.adaptor.moveDown()
-map 'k', class MoveUp extends Motion
+map ['k'], class MoveUp extends Motion
linewise: yes
exec: repeatCountTimes (jim) -> jim.adaptor.moveUp()
-map 'l', class MoveRight extends Motion
+map ['l'], class MoveRight extends Motion
exclusive: yes
exec: repeatCountTimes (jim) ->
linelen = jim.adaptor.lineText().length - 1
@@ -54,12 +45,12 @@ map 'l', class MoveRight extends Motion
jim.adaptor.moveTo jim.adaptor.row() + 1, 0
else jim.adaptor.moveRight @operation?
-map 'left', MoveLeft
-map 'down', MoveDown
-map 'up', MoveUp
-map 'right', MoveRight
+map ['left'], MoveLeft
+map ['down'], MoveDown
+map ['up'], MoveUp
+map ['right'], MoveRight
-map 'space', class extends MoveRight
+map ['space'], class extends MoveRight
nextLine: yes
# Word motions
@@ -76,7 +67,7 @@ wordRegex = -> /(\w+)|([^\w\s]+)/g
# Move to the next end of a **word**.
-map 'e', class MoveToWordEnd extends Motion
+map ['e'], class MoveToWordEnd extends Motion
exec: repeatCountTimes (jim) ->
regex = if @bigWord then WORDRegex() else wordRegex()
line = jim.adaptor.lineText()
@@ -111,12 +102,12 @@ map 'e', class MoveToWordEnd extends Motion
jim.adaptor.moveTo row, column
# Move to the next end of a **WORD**.
-map 'E', class MoveToBigWordEnd extends MoveToWordEnd
+map ['E'], class MoveToBigWordEnd extends MoveToWordEnd
bigWord: yes
# Move to the next beginning of a **word**.
-map 'w', class MoveToNextWord extends Motion
+map ['w'], class MoveToNextWord extends Motion
exclusive: yes
exec: (jim) ->
timesLeft = @count
@@ -163,7 +154,7 @@ map 'w', class MoveToNextWord extends Motion
jim.adaptor.moveTo row, column
# Move to the next beginning of a **WORD**.
-map 'W', class MoveToNextBigWord extends MoveToNextWord
+map ['W'], class MoveToNextBigWord extends MoveToNextWord
bigWord: yes
@@ -172,7 +163,7 @@ lastWORDRegex = ///#{WORDRegex().source}\s*$///
lastWordRegex = ///(#{wordRegex().source})\s*$///
# Move to the last beginning of a **word**.
-map 'b', class MoveBackWord extends Motion
+map ['b'], class MoveBackWord extends Motion
exclusive: yes
exec: repeatCountTimes (jim) ->
regex = if @bigWord then lastWORDRegex else lastWordRegex
@@ -197,7 +188,7 @@ map 'b', class MoveBackWord extends Motion
jim.adaptor.moveTo row, column
# Move to the last beginning of a **WORD**.
-map 'B', class MoveBackBigWord extends MoveBackWord
+map ['B'], class MoveBackBigWord extends MoveBackWord
bigWord: yes
@@ -205,12 +196,12 @@ map 'B', class MoveBackBigWord extends MoveBackWord
# ------------------------
# Move to the first column on the line.
-map '0', class MoveToBeginningOfLine extends Motion
+map ['0'], class MoveToBeginningOfLine extends Motion
exclusive: yes
exec: (jim) -> jim.adaptor.moveTo jim.adaptor.row(), 0
# Move to the first non-blank character on the line.
-map '^', class MoveToFirstNonBlank extends Motion
+map ['^'], class MoveToFirstNonBlank extends Motion
exec: (jim) ->
row = jim.adaptor.row()
line = jim.adaptor.lineText row
@@ -218,7 +209,7 @@ map '^', class MoveToFirstNonBlank extends Motion
jim.adaptor.moveTo row, column
# Move to the last column on the line.
-map '$', class MoveToEndOfLine extends Motion
+map ['$'], class MoveToEndOfLine extends Motion
exec: (jim) ->
additionalLines = @count - 1
new MoveDown(additionalLines).exec jim if additionalLines
@@ -229,7 +220,7 @@ map '$', class MoveToEndOfLine extends Motion
# ------------
# Go to `{count}` line number or the first line.
-map 'gg', class GoToLine extends Motion
+map ['g', 'g'], class GoToLine extends Motion
linewise: yes
exec: (jim) ->
rowNumber = @count - 1
@@ -238,21 +229,21 @@ map 'gg', class GoToLine extends Motion
new MoveToFirstNonBlank().exec jim
# Go to `{count}` line number or the last line.
-map 'G', class GoToLineOrEnd extends GoToLine
+map ['G'], class GoToLineOrEnd extends GoToLine
constructor: (@count) ->
exec: (jim) ->
@count or= jim.adaptor.lastRow() + 1
super
# Go to the first line that's visible in the viewport.
-map 'H', class GoToFirstVisibleLine extends Motion
+map ['H'], class GoToFirstVisibleLine extends Motion
linewise: yes
exec: (jim) ->
line = jim.adaptor.firstFullyVisibleRow() + @count
new GoToLineOrEnd(line).exec jim
# Go to the middle line of the lines that exist and are visible in the viewport.
-map 'M', class GoToMiddleLine extends Motion
+map ['M'], class GoToMiddleLine extends Motion
linewise: yes
exec: (jim) ->
topRow = jim.adaptor.firstFullyVisibleRow()
@@ -261,7 +252,7 @@ map 'M', class GoToMiddleLine extends Motion
new GoToLineOrEnd(topRow + 1 + linesFromTop).exec jim
# Go to the last line of the lines that exist and are visible in the viewport.
-map 'L', class GoToLastVisibleLine extends Motion
+map ['L'], class GoToLastVisibleLine extends Motion
linewise: yes
exec: (jim) ->
line = jim.adaptor.lastFullyVisibleRow() + 2 - @count
@@ -272,7 +263,7 @@ map 'L', class GoToLastVisibleLine extends Motion
# --------------
# Prompt the user for a search term and search forward for it.
-map '/', class Search extends Motion
+map ['/'], class Search extends Motion
# Given that `jim.search` has already been set, search for the `{count}`'th
# occurrence of the search. Reverse `jim.search`'s direction if `reverse` is
# true.
@@ -289,11 +280,11 @@ map '/', class Search extends Motion
Search.runSearch jim, @count
# Prompt the user for a search term and search backwards for it.
-map '?', class SearchBackwards extends Search
+map ['?'], class SearchBackwards extends Search
backwards: yes
# Search fowards for the next occurrence of the nearest word.
-map '*', class NearestWordSearch extends Search
+map ['*'], class NearestWordSearch extends Search
getSearch: (jim) ->
[searchString, charsAhead] = nearestWord jim
@@ -333,17 +324,17 @@ map '*', class NearestWordSearch extends Search
[leftMatch[0] + rightMatch[0], charsAhead]
# Search backwards for the next occurrence of the nearest word.
-map '#', class NearestWordSearchBackwards extends NearestWordSearch
+map ['#'], class NearestWordSearchBackwards extends NearestWordSearch
backwards: yes
# Repeat the last search.
-map 'n', class SearchAgain extends Motion
+map ['n'], class SearchAgain extends Motion
exclusive: yes
exec: (jim) -> Search.runSearch jim, @count
# Repeat the last search, reversing the direction.
-map 'N', class SearchAgainReverse extends Motion
+map ['N'], class SearchAgainReverse extends Motion
exclusive: yes
exec: (jim) -> Search.runSearch jim, @count, true
@@ -355,7 +346,7 @@ map 'N', class SearchAgainReverse extends Motion
# they are executed this character is stored as the command's `@followedBy`.
# Go to the next `@followedBy` char on the line.
-map 'f', class GoToNextChar extends Motion
+map ['f'], class GoToNextChar extends Motion
@followedBy: /./
exec: (jim) ->
timesLeft = @count ? 1
@@ -369,12 +360,12 @@ map 'f', class GoToNextChar extends Motion
jim.adaptor.moveTo row, column + columnsRight
# Go to the char before the next `@followedBy` char on the line.
-map 't', class GoUpToNextChar extends GoToNextChar
+map ['t'], class GoUpToNextChar extends GoToNextChar
beforeChar: yes
# Go to the previous `@followedBy` char on the line.
-map 'F', class GoToPreviousChar extends Motion
+map ['F'], class GoToPreviousChar extends Motion
@followedBy: /./
exec: (jim) ->
timesLeft = @count ? 1
@@ -388,13 +379,13 @@ map 'F', class GoToPreviousChar extends Motion
jim.adaptor.moveTo row, targetColumn
# Go to the char after the previous `@followedBy` char on the line.
-map 'T', class GoUpToPreviousChar extends GoToPreviousChar
+map ['T'], class GoUpToPreviousChar extends GoToPreviousChar
beforeChar: yes
# Exports
# -------
module.exports = {
- GoToLine, MoveDown, MoveLeft, MoveRight, MoveToEndOfLine, MoveToFirstNonBlank, LinewiseCommandMotion,
+ Motion, GoToLine, MoveDown, MoveLeft, MoveRight, MoveToEndOfLine, MoveToFirstNonBlank,
MoveToNextBigWord, MoveToNextWord, MoveToBigWordEnd, MoveToWordEnd
}
View
10 src/operators.coffee
@@ -44,7 +44,7 @@ class Operation extends Command
# Change the selected text or the text that `@motion` moves over (i.e. delete
# the text and switch to insert mode).
-map 'c', class Change extends Operation
+map ['c'], class Change extends Operation
visualExec: (jim) ->
super
@@ -70,26 +70,26 @@ map 'c', class Change extends Operation
switchToMode: 'insert'
# Delete the selection or the text that `@motion` moves over.
-map 'd', class Delete extends Operation
+map ['d'], class Delete extends Operation
operate: (jim) ->
jim.deleteSelection @motion?.exclusive, @linewise
new MoveToFirstNonBlank().exec jim if @linewise
# Yank into a register the selection or the text that `@motion` moves over.
-map 'y', class Yank extends Operation
+map ['y'], class Yank extends Operation
operate: (jim) ->
jim.yankSelection @motion?.exclusive, @linewise
jim.adaptor.moveTo @startingPosition... if @startingPosition
# Indent the lines in the selection or the text that `@motion` moves over.
-map '>', class Indent extends Operation
+map ['>'], class Indent extends Operation
operate: (jim) ->
[minRow, maxRow] = jim.adaptor.selectionRowRange()
jim.adaptor.indentSelection()
new GoToLine(minRow + 1).exec jim
# Outdent the lines in the selection or the text that `@motion` moves over.
-map '<', class Outdent extends Operation
+map ['<'], class Outdent extends Operation
operate: (jim) ->
[minRow, maxRow] = jim.adaptor.selectionRowRange()
jim.adaptor.outdentSelection()
Please sign in to comment.
Something went wrong with that request. Please try again.