diff --git a/chrome/content/vimperator/commands.js b/chrome/content/vimperator/commands.js
index 4d60a2f8..e48973f7 100644
--- a/chrome/content/vimperator/commands.js
+++ b/chrome/content/vimperator/commands.js
@@ -187,6 +187,7 @@ function Commands() //{{{
if (ex_commands[i].hasName(name))
return ex_commands[i];
}
+
return null;
}
@@ -505,6 +506,73 @@ function Commands() //{{{
"The special version :javascript! will open the javascript console of Firefox."
}
));
+ addDefaultCommand(new Command(["map"],
+ // 0 args -> list all maps
+ // 1 arg -> list the maps starting with args
+ // 2 args -> map arg1 to arg*
+ function(args)
+ {
+ if (args.length == 0)
+ {
+ vimperator.mappings.list(vimperator.modes.NORMAL);
+ return;
+ }
+
+ var matches = args.match(/^([^ ]+)(?:\s+(.+))?$/);
+ var [lhs, rhs] = [matches[1], matches[2]];
+
+ if (rhs)
+ {
+ if (/^:/.test(rhs))
+ {
+ vimperator.mappings.add(
+ new Map(vimperator.modes.NORMAL, [lhs], function() { execute(rhs); }, { rhs: rhs })
+ );
+ }
+ else
+ {
+ var map = vimperator.mappings.get(vimperator.modes.NORMAL, rhs);
+
+ // create a new Map for {lhs} with the same action as
+ // {rhs}...until we have feedkeys().
+ // NOTE: Currently only really useful for static use ie. from
+ // the RC file
+ if (map)
+ vimperator.mappings.add(
+ new Map(vimperator.modes.NORMAL, [lhs], map.action, { rhs: rhs })
+ );
+ else
+ vimperator.echoerr("E475: Invalid argument: " + "{rhs} must be a existing singular mapping");
+ }
+ }
+ else
+ {
+ // FIXME: no filtering for now
+ vimperator.mappings.list(vimperator.modes.NORMAL, lhs);
+ }
+ },
+ {
+ usage: ["map {lhs} {rhs}", "map {lhs}", "map"],
+ short_help: "Map the key sequence {lhs} to {rhs}",
+ help: ""
+ }
+ ));
+ addDefaultCommand(new Command(["mapc[lear]"],
+ function(args)
+ {
+ if (args.length > 0)
+ {
+ vimperator.echoerr("E474: Invalid argument");
+ return;
+ }
+
+ vimperator.mappings.removeAll(vimperator.modes.NORMAL);
+ },
+ {
+ short_help: "Remove all mappings",
+ help: ""
+ }
+ ));
addDefaultCommand(new Command(["ma[rk]"],
function(args)
{
@@ -891,6 +959,28 @@ function Commands() //{{{
help: "TODO."
}
));
+ addDefaultCommand(new Command(["unm[ap]"],
+ function(args)
+ {
+ if (args.length == 0)
+ {
+ vimperator.echoerr("E474: Invalid argument");
+ return;
+ }
+
+ var lhs = args;
+
+ if (vimperator.mappings.hasMap(vimperator.modes.NORMAL, lhs))
+ vimperator.mappings.remove(vimperator.modes.NORMAL, lhs);
+ else
+ vimperator.echoerr("E31: No such mapping");
+ },
+ {
+ usage: ["unm[ap] {lhs}"],
+ short_help: "Remove the mapping of {lhs}",
+ help: ""
+ }
+ ));
addDefaultCommand(new Command(["ve[rsion]"],
function(args, special)
{
diff --git a/chrome/content/vimperator/mappings.js b/chrome/content/vimperator/mappings.js
index db8992ae..9547c773 100644
--- a/chrome/content/vimperator/mappings.js
+++ b/chrome/content/vimperator/mappings.js
@@ -26,14 +26,14 @@ the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
}}} ***** END LICENSE BLOCK *****/
-function Map(mode, cmds, act, extra_info) //{{{
+function Map(mode, cmds, action, extra_info) //{{{
{
- if (!mode || (!cmds || !cmds.length) || !act)
+ if (!mode || (!cmds || !cmds.length) || !action)
return null;
this.mode = mode;
this.names = cmds;
- this.action = act;
+ this.action = action;
this.usage = [this.names[0]];
@@ -56,6 +56,8 @@ function Map(mode, cmds, act, extra_info) //{{{
this.help = extra_info.help || null;
this.short_help = extra_info.short_help || null;
+ this.rhs = extra_info.rhs || null;
+
// TODO: are these limited to HINTS mode?
// Only set for hints maps
this.cancel_mode = extra_info.cancel_mode || false;
@@ -64,6 +66,17 @@ function Map(mode, cmds, act, extra_info) //{{{
}
+Map.prototype.hasName = function(name)
+{
+ for (var i = 0; i < this.names.length; i++)
+ {
+ if (this.names[i] == name)
+ return true;
+ }
+
+ return false;
+}
+
// Since we will add many Map-objects, we add some functions as prototypes
// this will ensure we only have one copy of each function, not one for each object
Map.prototype.execute = function(motion, count, argument)
@@ -96,6 +109,7 @@ function Mappings() //{{{
////////////////////// PRIVATE SECTION /////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
+ // TODO: initialize empty map tables?
var main = []; // array of default Map() objects
var user = []; // array of objects created by :map
@@ -103,33 +117,56 @@ function Mappings() //{{{
{
if (!main[map.mode])
main[map.mode] = [];
+
main[map.mode].push(map);
}
- function getFrom(mode, cmd, stack)
+ function getMap(mode, cmd, stack)
{
- if (!stack || !stack[mode] || !stack[mode].length)
- return;
-
var substack = stack[mode];
- var stack_length = substack.length;
- for (var i = 0; i < stack_length; i++)
+
+ for (var i = 0; i < substack.length; i++)
{
for (var j = 0; j < substack[i].names.length; j++)
if (substack[i].names[j] == cmd)
return substack[i];
}
+
+ return null;
+ }
+
+ function removeMap(mode, cmd)
+ {
+ var maps = user[mode];
+ var names;
+
+ for (var i = 0; i < maps.length; i++)
+ {
+ names = maps[i].names;
+ for (var j = 0; j < names.length; j++)
+ {
+ if (names[j] == cmd)
+ {
+ names.splice(j, 1)
+
+ if (names.length == 0)
+ maps.splice(i, 1);
+
+ return;
+ }
+ }
+ }
}
function mappingsIterator(mode)
{
- var mappings;
+ var mappings = main[mode];
- // FIXME: initialize empty map tables
- if (user[mode])
- mappings = user[mode].concat(main[mode]);
- else
- mappings = main[mode]
+ //// FIXME: do we want to document user commands by default?
+ //if (user[mode])
+ // mappings = user[mode].concat(main[mode]);
+ //else
+ // mappings = main[mode]
for (var i = 0; i < mappings.length; i++)
yield mappings[i];
@@ -158,39 +195,46 @@ function Mappings() //{{{
return mappingsIterator(mode);
}
+ this.hasMap = function(mode, cmd)
+ {
+ var user_maps = user[mode];
+
+ for (var i = 0; i < user_maps.length; i++)
+ {
+ if (user_maps[i].names.indexOf(cmd) != -1)
+ return true;
+ }
+
+ return false;
+ }
+
this.add = function(map)
{
- if (!map)
- return false;
-
if (!user[map.mode])
user[map.mode] = [];
- user[map.mode].push(map);
+ for (var i = 0; i < map.names.length; i++)
+ removeMap(map.mode, map.names[i]);
- return true;
+ user[map.mode].push(map);
}
- // FIXME: this doesn't work
- this.remove = function(map)
+ this.remove = function(mode, cmd)
{
- var index;
+ removeMap(mode, cmd);
+ }
- if (!map || !(index = user[map.mode].indexOf(map)))
- return false;
-
- user[map.mode].splice(index, 1);
- return true;
+ this.removeAll = function(mode)
+ {
+ user[mode] = [];
}
this.get = function(mode, cmd)
{
- if (!mode || !cmd)
- return null;
+ var map = getMap(mode, cmd, user);
- var map = getFrom(mode, cmd, user);
if (!map)
- map = getFrom(mode, cmd, main);
+ map = getMap(mode, cmd, main);
return map;
}
@@ -201,9 +245,6 @@ function Mappings() //{{{
var mappings = [];
var matches = [];
- if (!mode || !cmd)
- return matches;
-
if (user[mode])
mappings = user[mode].concat(main[mode]);
else
@@ -222,6 +263,35 @@ function Mappings() //{{{
return matches;
}
+ // TODO: implement filtering
+ this.list = function(mode, filter)
+ {
+ var maps = user[mode];
+
+ if (!maps || maps.length == 0)
+ {
+ vimperator.echo("No mappings found");
+ return;
+ }
+
+ var list = "
| " + maps[i].names[j].replace(//g, ">") + " | " + if (maps[i].rhs) + list += "" + maps[i].rhs.replace(//g, ">") + " | " + list += "