1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-22 08:27:58 +01:00

Add operator mode per Vim. Wrap most editor commands in transactions. And stuff. Closes issue #439.

This commit is contained in:
Kris Maglione
2011-08-05 16:50:00 -04:00
parent 55f5f9292b
commit f502617a84
7 changed files with 201 additions and 193 deletions

View File

@@ -1688,12 +1688,12 @@ var Buffer = Module("buffer", {
function () { ex.stop(); });
// scrolling
mappings.add([modes.COMMAND], ["j", "<Down>", "<C-e>", "<scroll-down-line>"],
mappings.add([modes.NORMAL], ["j", "<Down>", "<C-e>", "<scroll-down-line>"],
"Scroll document down",
function (args) { buffer.scrollVertical("lines", Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.COMMAND], ["k", "<Up>", "<C-y>", "<scroll-up-line>"],
mappings.add([modes.NORMAL], ["k", "<Up>", "<C-y>", "<scroll-up-line>"],
"Scroll document up",
function (args) { buffer.scrollVertical("lines", -Math.max(args.count, 1)); },
{ count: true });
@@ -1703,30 +1703,30 @@ var Buffer = Module("buffer", {
function (args) { buffer.scrollHorizontal("columns", -Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.COMMAND], dactyl.has("mail") ? ["l", "<scroll-right-column>"] : ["l", "<Right>", "<scroll-right-column>"],
mappings.add([modes.NORMAL], dactyl.has("mail") ? ["l", "<scroll-right-column>"] : ["l", "<Right>", "<scroll-right-column>"],
"Scroll document to the right",
function (args) { buffer.scrollHorizontal("columns", Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.COMMAND], ["0", "^", "<scroll-begin>"],
mappings.add([modes.NORMAL], ["0", "^", "<scroll-begin>"],
"Scroll to the absolute left of the document",
function () { buffer.scrollToPercent(0, null); });
mappings.add([modes.COMMAND], ["$", "<scroll-end>"],
mappings.add([modes.NORMAL], ["$", "<scroll-end>"],
"Scroll to the absolute right of the document",
function () { buffer.scrollToPercent(100, null); });
mappings.add([modes.COMMAND], ["gg", "<Home>", "<scroll-top>"],
mappings.add([modes.NORMAL], ["gg", "<Home>", "<scroll-top>"],
"Go to the top of the document",
function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 0); },
{ count: true });
mappings.add([modes.COMMAND], ["G", "<End>", "<scroll-bottom>"],
mappings.add([modes.NORMAL], ["G", "<End>", "<scroll-bottom>"],
"Go to the end of the document",
function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 100); },
{ count: true });
mappings.add([modes.COMMAND], ["%", "<scroll-percent>"],
mappings.add([modes.NORMAL], ["%", "<scroll-percent>"],
"Scroll to {count} percent of the document",
function (args) {
dactyl.assert(args.count > 0 && args.count <= 100);
@@ -1734,22 +1734,22 @@ var Buffer = Module("buffer", {
},
{ count: true });
mappings.add([modes.COMMAND], ["<C-d>", "<scroll-down>"],
mappings.add([modes.NORMAL], ["<C-d>", "<scroll-down>"],
"Scroll window downwards in the buffer",
function (args) { buffer._scrollByScrollSize(args.count, true); },
{ count: true });
mappings.add([modes.COMMAND], ["<C-u>", "<scroll-up>"],
mappings.add([modes.NORMAL], ["<C-u>", "<scroll-up>"],
"Scroll window upwards in the buffer",
function (args) { buffer._scrollByScrollSize(args.count, false); },
{ count: true });
mappings.add([modes.COMMAND], ["<C-b>", "<PageUp>", "<S-Space>", "<scroll-up-page>"],
mappings.add([modes.NORMAL], ["<C-b>", "<PageUp>", "<S-Space>", "<scroll-up-page>"],
"Scroll up a full page",
function (args) { buffer.scrollVertical("pages", -Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.COMMAND], ["<Space>"],
mappings.add([modes.NORMAL], ["<Space>"],
"Scroll down a full page",
function (args) {
if (isinstance(content.document.activeElement, [HTMLInputElement, HTMLButtonElement]))
@@ -1758,7 +1758,7 @@ var Buffer = Module("buffer", {
},
{ count: true });
mappings.add([modes.COMMAND], ["<C-f>", "<PageDown>", "<scroll-down-page>"],
mappings.add([modes.NORMAL], ["<C-f>", "<PageDown>", "<scroll-down-page>"],
"Scroll down a full page",
function (args) { buffer.scrollVertical("pages", Math.max(args.count, 1)); },
{ count: true });
@@ -1969,7 +1969,7 @@ var Buffer = Module("buffer", {
try {
config.browser.docShell.QueryInterface(Ci.nsIDocCharset).charset = val;
PlacesUtils.history.setCharsetForURI(getWebNavigation().currentURI, val);
getWebNavigation().reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
window.getWebNavigation().reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
}
catch (e) { dactyl.reportError(e); }
return null;

View File

@@ -92,68 +92,6 @@ var Editor = Module("editor", {
}
},
// cmd = y, d, c
// motion = b, 0, gg, G, etc.
selectMotion: function selectMotion(cmd, motion, count) {
// XXX: better as a precondition
if (count == null)
count = 1;
if (cmd == motion) {
motion = "j";
count--;
}
if (modes.main != modes.VISUAL)
modes.push(modes.VISUAL);
switch (motion) {
case "j":
this.executeCommand("cmd_beginLine", 1);
this.executeCommand("cmd_selectLineNext", count + 1);
break;
case "k":
this.executeCommand("cmd_beginLine", 1);
this.executeCommand("cmd_lineNext", 1);
this.executeCommand("cmd_selectLinePrevious", count + 1);
break;
case "h":
this.executeCommand("cmd_selectCharPrevious", count);
break;
case "l":
this.executeCommand("cmd_selectCharNext", count);
break;
case "e":
case "w":
this.executeCommand("cmd_selectWordNext", count);
break;
case "b":
this.executeCommand("cmd_selectWordPrevious", count);
break;
case "0":
case "^":
this.executeCommand("cmd_selectBeginLine", 1);
break;
case "$":
this.executeCommand("cmd_selectEndLine", 1);
break;
case "gg":
this.executeCommand("cmd_endLine", 1);
this.executeCommand("cmd_selectTop", 1);
this.executeCommand("cmd_selectBeginLine", 1);
break;
case "G":
this.executeCommand("cmd_beginLine", 1);
this.executeCommand("cmd_selectBottom", 1);
this.executeCommand("cmd_selectEndLine", 1);
break;
default:
dactyl.beep();
return;
}
},
// This function will move/select up to given "pos"
// Simple setSelectionRange() would be better, but we want to maintain the correct
// order of selectionStart/End (a Gecko bug always makes selectionStart <= selectionEnd)
@@ -427,11 +365,27 @@ var Editor = Module("editor", {
}, {
mappings: function () {
Map.types["editor"] = {
preExecute: function preExecute(args) {
Editor.getEditor(null).beginTransaction();
},
postExecute: function preExecute(args) {
Editor.getEditor(null).endTransaction();
},
};
Map.types["operator"] = {
postExecute: function preExecute(args) {
if (modes.main == modes.OPERATOR)
modes.pop();
},
};
// add mappings for commands like h,j,k,l,etc. in CARET, VISUAL and TEXT_EDIT mode
function addMovementMap(keys, description, hasCount, caretModeMethod, caretModeArg, textEditCommand, visualTextEditCommand) {
let extraInfo = {};
if (hasCount)
extraInfo.count = true;
let extraInfo = {
count: !!hasCount,
type: "operator"
};
function caretExecute(arg, again) {
function fixSelection() {
@@ -486,7 +440,7 @@ var Editor = Module("editor", {
},
extraInfo);
mappings.add([modes.TEXT_EDIT], keys, description,
mappings.add([modes.OPERATOR], keys, description,
function ({ count }) {
if (!count)
count = 1;
@@ -503,7 +457,8 @@ var Editor = Module("editor", {
commands.forEach(function (cmd)
editor.executeCommand(cmd, 1));
modes.push(modes.INSERT);
});
},
{ type: "editor" });
}
function selectPreviousLine() {
@@ -582,33 +537,53 @@ var Editor = Module("editor", {
addBeginInsertModeMap(["S"], ["cmd_deleteToEndOfLine", "cmd_deleteToBeginningOfLine"], "Delete the current line and start insert");
addBeginInsertModeMap(["C"], ["cmd_deleteToEndOfLine"], "Delete from the cursor to the end of the line and start insert");
function addMotionMap(key, desc, cmd, mode) {
mappings.add([modes.TEXT_EDIT], [key],
function addMotionMap(key, desc, select, cmd, mode) {
mappings.add([modes.OPERATOR], [key],
desc,
function ({ count, motion }) {
editor.selectMotion(key, motion, Math.max(count, 1));
if (callable(cmd))
cmd.call(events, Editor.getEditor(null));
else {
editor.executeCommand(cmd, 1);
modes.pop(modes.TEXT_EDIT);
function ({ count, motion, command }) {
modes.push(modes.OPERATOR, null, {
leave: function leave(stack) {
if (stack.push)
return;
try {
editor.beginTransaction();
let range = RangeFind.union(start, sel.getRangeAt(0));
sel.removeAllRanges();
sel.addRange(select ? range : start);
cmd(editor, range);
}
finally {
editor.endTransaction();
}
modes.delay(function () {
if (mode)
modes.push(mode);
});
}
});
let editor = Editor.getEditor(null);
let sel = editor.selection;
let start = sel.getRangeAt(0).cloneRange();
},
{ count: true, motion: true });
{ count: true, type: "motion" });
}
addMotionMap("d", "Delete motion", "cmd_delete");
addMotionMap("c", "Change motion", "cmd_delete", modes.INSERT);
addMotionMap("y", "Yank motion", "cmd_copy");
addMotionMap("d", "Delete motion", true, function (editor) { editor.cut(); });
addMotionMap("c", "Change motion", true, function (editor) { editor.cut(); }, modes.INSERT);
addMotionMap("y", "Yank motion", false, function (editor, range) { dactyl.clipboardWrite(util.domToString(range)) });
mappings.add([modes.INPUT],
["<C-w>"], "Delete previous word",
let bind = function bind(names, description, action, params)
mappings.add([modes.INPUT], names, description,
action, update({ type: "editor" }, params));
bind(["<C-w>"], "Delete previous word",
function () { editor.executeCommand("cmd_deleteWordBackward", 1); });
mappings.add([modes.INPUT],
["<C-u>"], "Delete until beginning of current line",
bind(["<C-u>"], "Delete until beginning of current line",
function () {
// Deletes the whole line. What the hell.
// editor.executeCommand("cmd_deleteToBeginningOfLine", 1);
@@ -618,36 +593,28 @@ var Editor = Module("editor", {
editor.executeCommand("cmd_delete", 1);
});
mappings.add([modes.INPUT],
["<C-k>"], "Delete until end of current line",
bind(["<C-k>"], "Delete until end of current line",
function () { editor.executeCommand("cmd_deleteToEndOfLine", 1); });
mappings.add([modes.INPUT],
["<C-a>"], "Move cursor to beginning of current line",
bind(["<C-a>"], "Move cursor to beginning of current line",
function () { editor.executeCommand("cmd_beginLine", 1); });
mappings.add([modes.INPUT],
["<C-e>"], "Move cursor to end of current line",
bind(["<C-e>"], "Move cursor to end of current line",
function () { editor.executeCommand("cmd_endLine", 1); });
mappings.add([modes.INPUT],
["<C-h>"], "Delete character to the left",
bind(["<C-h>"], "Delete character to the left",
function () { events.feedkeys("<BS>", true); });
mappings.add([modes.INPUT],
["<C-d>"], "Delete character to the right",
bind(["<C-d>"], "Delete character to the right",
function () { editor.executeCommand("cmd_deleteCharForward", 1); });
mappings.add([modes.INPUT],
["<S-Insert>"], "Insert clipboard/selection",
bind(["<S-Insert>"], "Insert clipboard/selection",
function () { editor.pasteClipboard(); });
mappings.add([modes.INPUT, modes.TEXT_EDIT],
["<C-i>"], "Edit text field with an external editor",
bind(["<C-i>"], "Edit text field with an external editor",
function () { editor.editFieldExternally(); });
mappings.add([modes.INPUT],
["<C-t>"], "Edit text field in Vi mode",
bind(["<C-t>"], "Edit text field in Vi mode",
function () {
dactyl.assert(dactyl.focusedElement);
dactyl.assert(!editor.isTextEdit);
@@ -673,6 +640,10 @@ var Editor = Module("editor", {
["<C-]>", "<C-5>"], "Expand Insert mode abbreviation",
function () { editor.expandAbbreviation(modes.INSERT); });
let bind = function bind(names, description, action, params)
mappings.add([modes.TEXT_EDIT], names, description,
action, update({ type: "editor" }, params));
// text edit mode
mappings.add([modes.TEXT_EDIT],
["u"], "Undo changes",
@@ -690,8 +661,7 @@ var Editor = Module("editor", {
},
{ count: true });
mappings.add([modes.TEXT_EDIT],
["D"], "Delete the characters under the cursor until the end of the line",
bind(["D"], "Delete the characters under the cursor until the end of the line",
function () { editor.executeCommand("cmd_deleteToEndOfLine"); });
mappings.add([modes.TEXT_EDIT],
@@ -711,13 +681,11 @@ var Editor = Module("editor", {
editor.executeCommand("cmd_linePrevious", 1);
});
mappings.add([modes.TEXT_EDIT],
["X"], "Delete character to the left",
bind(["X"], "Delete character to the left",
function (args) { editor.executeCommand("cmd_deleteCharBackward", Math.max(args.count, 1)); },
{ count: true });
mappings.add([modes.TEXT_EDIT],
["x"], "Delete character to the right",
bind(["x"], "Delete character to the right",
function (args) { editor.executeCommand("cmd_deleteCharForward", Math.max(args.count, 1)); },
{ count: true });
@@ -764,8 +732,7 @@ var Editor = Module("editor", {
dactyl.clipboardWrite(buffer.currentWord, true);
});
mappings.add([modes.VISUAL, modes.TEXT_EDIT],
["p"], "Paste clipboard contents",
bind(["p"], "Paste clipboard contents",
function ({ count }) {
dactyl.assert(!editor.isCaret);
editor.executeCommand("cmd_paste", count || 1);
@@ -773,42 +740,50 @@ var Editor = Module("editor", {
},
{ count: true });
let bind = function bind(names, description, action, params)
mappings.add([modes.OPERATOR], names, description,
action, update({ type: "editor" }, params));
// finding characters
mappings.add([modes.TEXT_EDIT, modes.VISUAL],
["f"], "Move to a character on the current line after the cursor",
function offset(backward, before, pos) {
if (!backward && modes.main != modes.TEXT_EDIT)
pos += 1;
if (before)
pos += backward ? +1 : -1;
return pos;
}
bind(["f"], "Move to a character on the current line after the cursor",
function ({ arg, count }) {
let pos = editor.findChar(arg, Math.max(count, 1));
if (pos >= 0)
editor.moveToPosition(pos, true, modes.main == modes.VISUAL);
editor.moveToPosition(offset(false, false, pos), true, modes.main == modes.VISUAL);
},
{ arg: true, count: true });
{ arg: true, count: true, type: "operator" });
mappings.add([modes.TEXT_EDIT, modes.VISUAL],
["F"], "Move to a character on the current line before the cursor",
bind(["F"], "Move to a character on the current line before the cursor",
function ({ arg, count }) {
let pos = editor.findChar(arg, Math.max(count, 1), true);
if (pos >= 0)
editor.moveToPosition(pos, false, modes.main == modes.VISUAL);
editor.moveToPosition(offset(true, false, pos), false, modes.main == modes.VISUAL);
},
{ arg: true, count: true });
{ arg: true, count: true, type: "operator" });
mappings.add([modes.TEXT_EDIT, modes.VISUAL],
["t"], "Move before a character on the current line",
bind(["t"], "Move before a character on the current line",
function ({ arg, count }) {
let pos = editor.findChar(arg, Math.max(count, 1));
if (pos >= 0)
editor.moveToPosition(pos - 1, true, modes.main == modes.VISUAL);
editor.moveToPosition(offset(false, true, pos), true, modes.main == modes.VISUAL);
},
{ arg: true, count: true });
{ arg: true, count: true, type: "operator" });
mappings.add([modes.TEXT_EDIT, modes.VISUAL],
["T"], "Move before a character on the current line, backwards",
bind(["T"], "Move before a character on the current line, backwards",
function ({ arg, count }) {
let pos = editor.findChar(arg, Math.max(count, 1), true);
if (pos >= 0)
editor.moveToPosition(pos + 1, false, modes.main == modes.VISUAL);
editor.moveToPosition(offset(true, true, pos), false, modes.main == modes.VISUAL);
},
{ arg: true, count: true });
{ arg: true, count: true, type: "operator" });
// text edit and visual mode
mappings.add([modes.TEXT_EDIT, modes.VISUAL],
@@ -834,7 +809,7 @@ var Editor = Module("editor", {
},
{ count: true });
function bind() mappings.add.apply(mappings,
let bind = function bind() mappings.add.apply(mappings,
[[modes.AUTOCOMPLETE]].concat(Array.slice(arguments)))
bind(["<Esc>"], "Return to Insert mode",

View File

@@ -17,7 +17,7 @@
* *action*.
* @param {string} description A short one line description of the key mapping.
* @param {function} action The action invoked by each key sequence.
* @param {Object} extraInfo An optional extra configuration hash. The
* @param {Object} info An optional extra configuration hash. The
* following properties are supported.
* arg - see {@link Map#arg}
* count - see {@link Map#count}
@@ -29,7 +29,7 @@
* @private
*/
var Map = Class("Map", {
init: function (modes, keys, description, action, extraInfo) {
init: function (modes, keys, description, action, info) {
this.id = ++Map.id;
this.modes = modes;
this._keys = keys;
@@ -38,8 +38,11 @@ var Map = Class("Map", {
Object.freeze(this.modes);
if (extraInfo)
this.update(extraInfo);
if (info) {
if (Set.has(Map.types, info.type))
this.update(Map.types[info.type]);
this.update(info);
}
},
name: Class.memoize(function () this.names[0]),
@@ -69,12 +72,24 @@ var Map = Class("Map", {
* as an argument.
*/
motion: false,
/** @property {boolean} Whether the RHS of the mapping should expand mappings recursively. */
noremap: false,
/** @property {function(object)} A function to be executed before this mapping. */
preExecute: function preExecute(args) {},
/** @property {function(object)} A function to be executed after this mapping. */
postExecute: function postExecute(args) {},
/** @property {boolean} Whether any output from the mapping should be echoed on the command line. */
silent: false,
/** @property {string} The literal RHS expansion of this mapping. */
rhs: null,
/** @property {string} The type of this mapping. */
type: "",
/**
* @property {boolean} Specifies whether this is a user mapping. User
* mappings may be created by plugins, or directly by users. Users and
@@ -118,6 +133,7 @@ var Map = Class("Map", {
dactyl.assert(!this.executing, _("map.recursive", args.command));
try {
this.preExecute(args);
this.executing = true;
var res = repeat();
}
@@ -127,12 +143,15 @@ var Map = Class("Map", {
}
finally {
this.executing = false;
this.postExecute(args);
}
return res;
}
}, {
id: 0
id: 0,
types: {}
});
var MapHive = Class("MapHive", Contexts.Hive, {

View File

@@ -57,11 +57,16 @@ var Modes = Module("modes", {
description: "Active when nothing is focused",
bases: [this.COMMAND]
});
this.addMode("OPERATOR", {
char: "o",
description: "Mappings which move the cursor",
bases: []
});
this.addMode("VISUAL", {
char: "v",
description: "Active when text is selected",
display: function () "VISUAL" + (this._extended & modes.LINE ? " LINE" : ""),
bases: [this.COMMAND],
bases: [this.COMMAND, this.OPERATOR],
ownsFocus: true
}, {
leave: function (stack, newMode) {
@@ -97,7 +102,7 @@ var Modes = Module("modes", {
this.addMode("TEXT_EDIT", {
char: "t",
description: "Vim-like editing of input elements",
bases: [this.COMMAND],
bases: [this.OPERATOR, this.COMMAND],
ownsFocus: true
}, {
onKeyPress: function (eventList) {
@@ -489,7 +494,7 @@ var Modes = Module("modes", {
init: function init(name, options, params) {
if (options.bases)
util.assert(options.bases.every(function (m) m instanceof this, this.constructor),
_("mode.invalidBases"), true);
_("mode.invalidBases"), false);
this.update({
id: 1 << Modes.Mode._id++,
@@ -647,7 +652,7 @@ var Modes = Module("modes", {
options.add(["showmode", "smd"],
"Show the current mode in the command line when it matches this expression",
"stringlist", "caret,output_multiline,!normal,base",
"stringlist", "caret,output_multiline,!normal,base,operator",
opts);
},
prefs: function initPrefs() {

View File

@@ -56,6 +56,7 @@
<dt>i</dt> <dd>Insert mode: When interacting with text fields on a website</dd>
<dt>t</dt> <dd>Text Edit mode: When editing text fields in Vim-like Normal mode</dd>
<dt>c</dt> <dd>Command Line mode: When typing into the &dactyl.appName; command line</dd>
<dt>o</dt> <dd>Operator mode: When moving the cursor</dd>
</dl>
<p>

View File

@@ -774,7 +774,14 @@ var RangeFind = Class("RangeFind", {
}
return true;
},
selectNodePath: ["a", "xhtml:a", "*[@onclick]"].map(function (p) "ancestor-or-self::" + p).join(" | ")
selectNodePath: ["a", "xhtml:a", "*[@onclick]"].map(function (p) "ancestor-or-self::" + p).join(" | "),
union: function union(a, b) {
let start = a.compareBoundaryPoints(a.START_TO_START, b) < 0 ? a : b;
let end = a.compareBoundaryPoints(a.END_TO_END, b) > 0 ? a : b;
let res = start.cloneRange();
res.setEnd(end.startContainer, end.endOffset);
return res;
}
});
} catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }

View File

@@ -74,6 +74,7 @@
• Mapping changes:
- It's now possible to map keys in many more modes, including
Hint, Multi-line Output, and Menu. [b4]
- Added Operator mode for motion maps, per Vim. [b8]
- Added site-specific mapping groups and related command
changes. [b6]
- Added 'timeout' and 'timeoutlen' options. [b6]