mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2025-12-23 09:08:37 +01:00
Register support with a crude kill ring.
This commit is contained in:
@@ -1759,8 +1759,7 @@ var ItemList = Class("ItemList", {
|
||||
set visible(val) this.container.collapsed = !val,
|
||||
|
||||
get activeGroups() this.context.contextList
|
||||
.filter(function (c) c.message || c.incomplete
|
||||
|| c.hasItems && c.items.length)
|
||||
.filter(function (c) c.message || c.incomplete || c.items.length)
|
||||
.map(this.getGroup, this),
|
||||
|
||||
get selected() let (g = this.selectedGroup) g && g.selectedIdx != null &&
|
||||
|
||||
@@ -339,16 +339,20 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
|
||||
* This is same as Firefox's readFromClipboard function, but is needed for
|
||||
* apps like Thunderbird which do not provide it.
|
||||
*
|
||||
* @param {string} which Which clipboard to write to. Either
|
||||
* "global" or "selection". If not provided, both clipboards are
|
||||
* updated.
|
||||
* @optional
|
||||
* @returns {string}
|
||||
*/
|
||||
clipboardRead: function clipboardRead(getClipboard) {
|
||||
clipboardRead: function clipboardRead(which) {
|
||||
try {
|
||||
const { clipboard } = services;
|
||||
|
||||
let transferable = services.Transferable();
|
||||
transferable.addDataFlavor("text/unicode");
|
||||
|
||||
let source = clipboard[getClipboard || !clipboard.supportsSelectionClipboard() ?
|
||||
let source = clipboard[which == "global" || !clipboard.supportsSelectionClipboard() ?
|
||||
"kGlobalClipboard" : "kSelectionClipboard"];
|
||||
clipboard.getData(transferable, source);
|
||||
|
||||
@@ -375,7 +379,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), {
|
||||
* @optional
|
||||
*/
|
||||
clipboardWrite: function clipboardWrite(str, verbose, which) {
|
||||
if (!which)
|
||||
if (which == null)
|
||||
services.clipboardHelper.copyString(str);
|
||||
else if (which == "selection" && !services.clipboard.supportsSelectionClipboard())
|
||||
return;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
// http://developer.mozilla.org/en/docs/Editor_Embedding_Guide
|
||||
|
||||
/** @instance editor */
|
||||
var Editor = Module("editor", {
|
||||
var Editor = Module("editor", XPCOM(Ci.nsIEditActionListener, ModuleBase), {
|
||||
init: function init(elem) {
|
||||
if (elem)
|
||||
this.element = elem;
|
||||
@@ -26,6 +26,97 @@ var Editor = Module("editor", {
|
||||
});
|
||||
},
|
||||
|
||||
signals: {
|
||||
"mappings.willExecute": function mappings_willExecute(map) {
|
||||
if (this.currentRegister && !(this._currentMap && this._wait == util.yielders)) {
|
||||
this._currentMap = map;
|
||||
this._wait = util.yielders;
|
||||
}
|
||||
},
|
||||
"mappings.executed": function mappings_executed(map) {
|
||||
if (this._currentMap == map) {
|
||||
this.currentRegister = null;
|
||||
this._wait = util.yielders;
|
||||
this._currentMap = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
get registers() storage.newMap("registers", { privateData: true, store: true }),
|
||||
get registerRing() storage.newArray("register-ring", { privateData: true, store: true }),
|
||||
|
||||
// Fixme: Move off this object.
|
||||
currentRegister: null,
|
||||
|
||||
defaultRegister: "*",
|
||||
|
||||
selectionRegisters: {
|
||||
"*": "selection",
|
||||
"+": "global"
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the value of the register *name*.
|
||||
*
|
||||
* @param {string|number} name The name of the register to get.
|
||||
* @returns {string|null}
|
||||
* @see #setRegister
|
||||
*/
|
||||
getRegister: function getRegister(name) {
|
||||
if (name == null)
|
||||
name = editor.currentRegister || editor.defaultRegister;
|
||||
|
||||
if (name == '"')
|
||||
name = 0;
|
||||
if (name == "_")
|
||||
var res = null;
|
||||
else if (Set.has(this.selectionRegisters, name))
|
||||
res = { text: dactyl.clipboardRead(this.selectionRegisters[name]) || "" };
|
||||
else if (!/^[0-9]$/.test(name))
|
||||
res = this.registers.get(name);
|
||||
else
|
||||
res = this.registerRing.get(name);
|
||||
|
||||
return res != null ? res.text : res;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the value of register *name* to value. The following
|
||||
* registers have special semantics:
|
||||
*
|
||||
* * - Tied to the PRIMARY selection value on X11 systems.
|
||||
* + - Tied to the primary global clipboard.
|
||||
* _ - The null register. Never has any value.
|
||||
* " - Equivalent to 0.
|
||||
* 0-9 - These act as a kill ring. Setting any of them pushes the
|
||||
* values of higher numbered registers up one slot.
|
||||
*
|
||||
* @param {string|number} name The name of the register to set.
|
||||
* @param {string|Range|Selection|Node} value The value to save to
|
||||
* the register.
|
||||
*/
|
||||
setRegister: function setRegister(name, value) {
|
||||
if (name == null)
|
||||
name = editor.currentRegister || editor.defaultRegister;
|
||||
|
||||
if (isinstance(value, [Ci.nsIDOMRange, Ci.nsIDOMNode, Ci.nsISelection]))
|
||||
value = DOM.stringify(value);
|
||||
value = { text: value, isLine: modes.extended & modes.LINE };
|
||||
|
||||
if (name == '"')
|
||||
name = 0;
|
||||
if (name == "_")
|
||||
;
|
||||
else if (Set.has(this.selectionRegisters, name))
|
||||
dactyl.clipboardWrite(value.text, false, this.selectionRegisters[name]);
|
||||
else if (!/^[0-9]$/.test(name))
|
||||
this.registers.set(name, value);
|
||||
else {
|
||||
this.registerRing.insert(value, name);
|
||||
this.registerRing.truncate(10);
|
||||
}
|
||||
},
|
||||
|
||||
get isCaret() modes.getStack(1).main == modes.CARET,
|
||||
get isTextEdit() modes.getStack(1).main == modes.TEXT_EDIT,
|
||||
|
||||
@@ -72,30 +163,44 @@ var Editor = Module("editor", {
|
||||
this.editor.setShouldTxnSetSelection(!val);
|
||||
},
|
||||
|
||||
pasteClipboard: function pasteClipboard(clipboard) {
|
||||
let elem = this.element;
|
||||
copy: function copy(range, name) {
|
||||
range = range || this.selection;
|
||||
|
||||
let text = dactyl.clipboardRead(clipboard);
|
||||
if (!range.collapsed)
|
||||
this.setRegister(name, range);
|
||||
},
|
||||
|
||||
cut: function cut(range, name) {
|
||||
if (range)
|
||||
this.selectedRange = range;
|
||||
|
||||
if (!this.selection.isCollapsed)
|
||||
this.setRegister(name, this.selection);
|
||||
|
||||
this.editor.deleteSelection(0);
|
||||
},
|
||||
|
||||
paste: function paste(name) {
|
||||
let text = this.getRegister(name);
|
||||
dactyl.assert(text && this.editor instanceof Ci.nsIPlaintextEditor);
|
||||
|
||||
this.editor.insertText(text);
|
||||
},
|
||||
|
||||
// count is optional, defaults to 1
|
||||
executeCommand: function (cmd, count) {
|
||||
let controller = this.getController(cmd);
|
||||
dactyl.assert(callable(cmd) ||
|
||||
controller &&
|
||||
controller.supportsCommand(cmd) &&
|
||||
controller.isCommandEnabled(cmd));
|
||||
executeCommand: function executeCommand(cmd, count) {
|
||||
if (!callable(cmd)) {
|
||||
var controller = this.getController(cmd);
|
||||
util.assert(controller &&
|
||||
controller.supportsCommand(cmd) &&
|
||||
controller.isCommandEnabled(cmd));
|
||||
cmd = bind("doCommand", controller, cmd);
|
||||
}
|
||||
|
||||
// XXX: better as a precondition
|
||||
if (count == null)
|
||||
count = 1;
|
||||
|
||||
if (!callable(cmd))
|
||||
cmd = bind("doCommand", controller, cmd);
|
||||
|
||||
let didCommand = false;
|
||||
while (count--) {
|
||||
// some commands need this try/catch workaround, because a cmd_charPrevious triggered
|
||||
@@ -363,7 +468,21 @@ var Editor = Module("editor", {
|
||||
range.setStart(range.startContainer, range.endOffset - abbrev.lhs.length);
|
||||
this.mungeRange(range, function () abbrev.expand(this.element), true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// nsIEditActionListener:
|
||||
WillDeleteNode: util.wrapCallback(function WillDeleteNode(node) {
|
||||
if (node.textContent)
|
||||
this.setRegister(0, node);
|
||||
}),
|
||||
WillDeleteSelection: util.wrapCallback(function WillDeleteSelection(selection) {
|
||||
if (!selection.isCollapsed)
|
||||
this.setRegister(0, selection);
|
||||
}),
|
||||
WillDeleteText: util.wrapCallback(function WillDeleteText(node, start, length) {
|
||||
if (length)
|
||||
this.setRegister(0, node.textContent.substr(start, length));
|
||||
})
|
||||
}, {
|
||||
TextsIterator: Class("TextsIterator", {
|
||||
init: function init(range, context, after) {
|
||||
@@ -575,6 +694,38 @@ var Editor = Module("editor", {
|
||||
bases: [modes.INSERT]
|
||||
});
|
||||
},
|
||||
commands: function init_commands() {
|
||||
commands.add(["reg[isters]"],
|
||||
"List the contents of known registers",
|
||||
function (args) {
|
||||
completion.listCompleter("register", args[0]);
|
||||
},
|
||||
{ argCount: "*" });
|
||||
},
|
||||
completion: function init_completion() {
|
||||
completion.register = function complete_register(context) {
|
||||
context = context.fork("registers");
|
||||
context.keys = { text: util.identity, description: editor.closure.getRegister };
|
||||
|
||||
context.match = function (r) !this.filter || ~this.filter.indexOf(r);
|
||||
|
||||
context.fork("clipboard", 0, this, function (ctxt) {
|
||||
ctxt.match = context.match;
|
||||
ctxt.title = ["Clipboard Registers"];
|
||||
ctxt.completions = Object.keys(editor.selectionRegisters);
|
||||
});
|
||||
context.fork("kill-ring", 0, this, function (ctxt) {
|
||||
ctxt.match = context.match;
|
||||
ctxt.title = ["Kill Ring Registers"];
|
||||
ctxt.completions = Array.slice("0123456789");
|
||||
});
|
||||
context.fork("user", 0, this, function (ctxt) {
|
||||
ctxt.match = context.match;
|
||||
ctxt.title = ["User Defined Registers"];
|
||||
ctxt.completions = editor.registers.keys();
|
||||
});
|
||||
};
|
||||
},
|
||||
mappings: function init_mappings() {
|
||||
|
||||
Map.types["editor"] = {
|
||||
@@ -676,9 +827,9 @@ var Editor = Module("editor", {
|
||||
editor.executeCommand("cmd_selectLineNext");
|
||||
}
|
||||
|
||||
function updateRange(editor, forward, re, modify) {
|
||||
function updateRange(editor, forward, re, modify, sameWord) {
|
||||
let range = Editor.extendRange(editor.selection.getRangeAt(0),
|
||||
forward, re, false, editor.rootElement);
|
||||
forward, re, sameWord, editor.rootElement);
|
||||
modify(range);
|
||||
editor.selection.removeAllRanges();
|
||||
editor.selection.addRange(range);
|
||||
@@ -694,10 +845,11 @@ var Editor = Module("editor", {
|
||||
parent.input();
|
||||
}
|
||||
|
||||
function move(forward, re)
|
||||
function move(forward, re, sameWord)
|
||||
function _move(editor) {
|
||||
updateRange(editor, forward, re,
|
||||
function (range) { range.collapse(!forward); });
|
||||
function (range) { range.collapse(!forward); },
|
||||
sameWord);
|
||||
}
|
||||
function select(forward, re)
|
||||
function _select(editor) {
|
||||
@@ -706,7 +858,7 @@ var Editor = Module("editor", {
|
||||
}
|
||||
function beginLine(editor_) {
|
||||
editor.executeCommand("cmd_beginLine");
|
||||
move(true, /\S/)(editor_);
|
||||
move(true, /\s/, true)(editor_);
|
||||
}
|
||||
|
||||
// COUNT CARET TEXT_EDIT VISUAL_TEXT_EDIT
|
||||
@@ -769,6 +921,7 @@ var Editor = Module("editor", {
|
||||
function ({ command, count, motion }) {
|
||||
let start = editor.selectedRange.cloneRange();
|
||||
|
||||
editor._currentMap = null;
|
||||
modes.push(modes.OPERATOR, null, {
|
||||
forCommand: command,
|
||||
|
||||
@@ -786,6 +939,7 @@ var Editor = Module("editor", {
|
||||
doTxn(range, editor);
|
||||
});
|
||||
|
||||
editor.currentRegister = null;
|
||||
modes.delay(function () {
|
||||
if (mode)
|
||||
modes.push(mode);
|
||||
@@ -804,9 +958,9 @@ var Editor = Module("editor", {
|
||||
{ count: true, type: "motion" });
|
||||
}
|
||||
|
||||
addMotionMap(["d", "x"], "Delete text", true, function (editor) { editor.editor.cut(); });
|
||||
addMotionMap(["c"], "Change text", true, function (editor) { editor.editor.cut(); }, modes.INSERT);
|
||||
addMotionMap(["y"], "Yank text", false, function (editor, range) { dactyl.clipboardWrite(String(range)) });
|
||||
addMotionMap(["d", "x"], "Delete text", true, function (editor) { editor.cut(); });
|
||||
addMotionMap(["c"], "Change text", true, function (editor) { editor.cut(); }, modes.INSERT);
|
||||
addMotionMap(["y"], "Yank text", false, function (editor, range) { editor.copy(range); });
|
||||
|
||||
addMotionMap(["gu"], "Lowercase text", false,
|
||||
function (editor, range) {
|
||||
@@ -822,13 +976,13 @@ var Editor = Module("editor", {
|
||||
["c", "d", "y"], "Select the entire line",
|
||||
function ({ command, count }) {
|
||||
dactyl.assert(command == modes.getStack(0).params.forCommand);
|
||||
editor.executeCommand("cmd_beginLine", 1);
|
||||
editor.executeCommand("cmd_selectLineNext", count || 1);
|
||||
let range = editor.selectedRange;
|
||||
if (command == "c" && !range.collapsed) // Hack.
|
||||
if (range.endContainer instanceof Text &&
|
||||
range.endContainer.textContent[range.endOffset - 1] == "\n")
|
||||
editor.executeCommand("cmd_selectCharPrevious", 1);
|
||||
|
||||
let sel = editor.selection;
|
||||
sel.modify("move", "backward", "lineboundary");
|
||||
sel.modify("extend", "forward", "lineboundary");
|
||||
|
||||
if (command != "c")
|
||||
sel.modify("extend", "forward", "character");
|
||||
},
|
||||
{ count: true, type: "operator" });
|
||||
|
||||
@@ -875,7 +1029,7 @@ var Editor = Module("editor", {
|
||||
function () { editor.executeCommand("cmd_deleteCharForward", 1); });
|
||||
|
||||
bind(["<S-Insert>"], "Insert clipboard/selection",
|
||||
function () { editor.pasteClipboard(); });
|
||||
function () { editor.paste(); });
|
||||
|
||||
mappings.add([modes.INPUT],
|
||||
["<C-i>"], "Edit text field with an external editor",
|
||||
@@ -1005,11 +1159,25 @@ var Editor = Module("editor", {
|
||||
bind(["p"], "Paste clipboard contents",
|
||||
function ({ count }) {
|
||||
dactyl.assert(!editor.isCaret);
|
||||
editor.executeCommand("cmd_paste", count || 1);
|
||||
modes.pop(modes.TEXT_EDIT);
|
||||
editor.executeCommand(modules.bind("paste", editor, null),
|
||||
count || 1);
|
||||
},
|
||||
{ count: true });
|
||||
|
||||
mappings.add([modes.TEXT_EDIT, modes.VISUAL],
|
||||
['"'], "Bind a register to the next command",
|
||||
function ({ arg }) {
|
||||
editor.currentRegister = arg;
|
||||
},
|
||||
{ arg: true });
|
||||
|
||||
mappings.add([modes.INPUT],
|
||||
["<C-'>", '<C-">'], "Bind a register to the next command",
|
||||
function ({ arg }) {
|
||||
editor.currentRegister = arg;
|
||||
},
|
||||
{ arg: true });
|
||||
|
||||
let bind = function bind(names, description, action, params)
|
||||
mappings.add([modes.TEXT_EDIT, modes.OPERATOR, modes.VISUAL],
|
||||
names, description,
|
||||
|
||||
@@ -191,6 +191,13 @@ var Events = Module("events", {
|
||||
this.listen(window, this.popups, "events", true);
|
||||
},
|
||||
|
||||
cleanup: function cleanup() {
|
||||
let elem = dactyl.focusedElement;
|
||||
if (DOM(elem).isEditable)
|
||||
util.trapErrors("removeEditActionListener",
|
||||
DOM(elem).editor, editor);
|
||||
},
|
||||
|
||||
signals: {
|
||||
"browser.locationChange": function (webProgress, request, uri) {
|
||||
options.get("passkeys").flush();
|
||||
@@ -572,6 +579,10 @@ var Events = Module("events", {
|
||||
events: {
|
||||
blur: function onBlur(event) {
|
||||
let elem = event.originalTarget;
|
||||
if (DOM(elem).isEditable)
|
||||
util.trapErrors("removeEditActionListener",
|
||||
DOM(elem).editor, editor);
|
||||
|
||||
if (elem instanceof Window && services.focus.activeWindow == null
|
||||
&& document.commandDispatcher.focusedWindow !== window) {
|
||||
// Deals with circumstances where, after the main window
|
||||
@@ -592,6 +603,9 @@ var Events = Module("events", {
|
||||
// TODO: Merge with onFocusChange
|
||||
focus: function onFocus(event) {
|
||||
let elem = event.originalTarget;
|
||||
if (DOM(elem).isEditable)
|
||||
util.trapErrors("addEditActionListener",
|
||||
DOM(elem).editor, editor);
|
||||
|
||||
if (elem == window)
|
||||
overlay.activeWindow = window;
|
||||
|
||||
@@ -134,6 +134,7 @@ var Map = Class("Map", {
|
||||
false);
|
||||
|
||||
try {
|
||||
dactyl.triggerObserver("mappings.willExecute", this, args);
|
||||
this.preExecute(args);
|
||||
this.executing = true;
|
||||
var res = repeat();
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
// given in the LICENSE.txt file included with this file.
|
||||
"use strict";
|
||||
|
||||
try {
|
||||
|
||||
Components.utils.import("resource://dactyl/bootstrap.jsm");
|
||||
defineModule("completion", {
|
||||
exports: ["CompletionContext", "Completion", "completion"]
|
||||
@@ -221,7 +219,7 @@ var CompletionContext = Class("CompletionContext", {
|
||||
},
|
||||
get title() this.__title,
|
||||
|
||||
get activeContexts() this.contextList.filter(function (c) c.hasItems && c.items.length),
|
||||
get activeContexts() this.contextList.filter(function (c) c.items.length),
|
||||
|
||||
// Temporary
|
||||
/**
|
||||
@@ -356,6 +354,7 @@ var CompletionContext = Class("CompletionContext", {
|
||||
yield ["context", function () self];
|
||||
yield ["result", quote ? function () quote[0] + util.trapErrors(1, quote, this.text) + quote[2]
|
||||
: function () this.text];
|
||||
yield ["texts", function () Array.concat(this.text)];
|
||||
};
|
||||
|
||||
for (let i in iter(this.keys, result(this.quote))) {
|
||||
@@ -862,10 +861,9 @@ var CompletionContext = Class("CompletionContext", {
|
||||
|
||||
Filter: {
|
||||
text: function (item) {
|
||||
let text = item.texts || Array.concat(item.text);
|
||||
let text = item.texts;
|
||||
for (let [i, str] in Iterator(text)) {
|
||||
if (this.match(String(str))) {
|
||||
item.texts = text;
|
||||
item.text = String(text[i]);
|
||||
return true;
|
||||
}
|
||||
@@ -1063,11 +1061,13 @@ var Completion = Module("completion", {
|
||||
context.title[0] += " " + _("completion.additional");
|
||||
context.filter = context.parent.filter; // FIXME
|
||||
context.completions = context.parent.completions;
|
||||
|
||||
// For items whose URL doesn't exactly match the filter,
|
||||
// accept them if all tokens match either the URL or the title.
|
||||
// Filter out all directly matching strings.
|
||||
let match = context.filters[0];
|
||||
context.filters[0] = function (item) !match.call(this, item);
|
||||
|
||||
// and all that don't match the tokens.
|
||||
let tokens = context.filter.split(/\s+/);
|
||||
context.filters.push(function (item) tokens.every(
|
||||
@@ -1209,6 +1209,6 @@ var Completion = Module("completion", {
|
||||
|
||||
endModule();
|
||||
|
||||
} catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
|
||||
// catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
|
||||
|
||||
@@ -819,7 +819,7 @@ var RangeFind = Class("RangeFind", {
|
||||
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);
|
||||
res.setEnd(end.endContainer, end.endOffset);
|
||||
return res;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -93,12 +93,32 @@ var ArrayStore = Class("ArrayStore", StoreBase, {
|
||||
this.fireEvent("push", this._object.length);
|
||||
},
|
||||
|
||||
pop: function pop(value) {
|
||||
var res = this._object.pop();
|
||||
this.fireEvent("pop", this._object.length);
|
||||
pop: function pop(value, ord) {
|
||||
if (ord == null)
|
||||
var res = this._object.pop();
|
||||
else
|
||||
res = this._object.splice(ord, 1)[0];
|
||||
|
||||
this.fireEvent("pop", this._object.length, ord);
|
||||
return res;
|
||||
},
|
||||
|
||||
shift: function shift(value) {
|
||||
var res = this._object.shift();
|
||||
this.fireEvent("shift", this._object.length);
|
||||
return res;
|
||||
},
|
||||
|
||||
insert: function insert(value, ord) {
|
||||
if (ord == 0)
|
||||
this._object.unshift(value);
|
||||
else
|
||||
this._object = this._object.slice(0, ord)
|
||||
.concat([value])
|
||||
.concat(this._object.slice(ord));
|
||||
this.fireEvent("insert", this._object.length, ord);
|
||||
},
|
||||
|
||||
truncate: function truncate(length, fromEnd) {
|
||||
var res = this._object.length;
|
||||
if (this._object.length > length) {
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
• Text editing improvements, including:
|
||||
- Added t_gu, t_gU, and v_o mappings. [b8]
|
||||
- Added o_c, o_d, and o_y mappings. [b8]
|
||||
- Added register and basic kill ring support, t_" and I_<C-'>
|
||||
mappings, and :registers command. [b8]
|
||||
- Added operator modes and proper first class motion maps. [b8]
|
||||
- Improved undo support for most mappings. [b8]
|
||||
• General completion improvements
|
||||
|
||||
@@ -13,15 +13,12 @@ BUGS:
|
||||
|
||||
FEATURES:
|
||||
9 Add more tests.
|
||||
9 <C-o>/<C-i> should work as in Vim (i.e., save page positions as well as
|
||||
locations in the history list).
|
||||
9 clean up error message codes and document
|
||||
9 option groups
|
||||
9 global, window-local, tab-local, buffer-local, script-local groups
|
||||
9 add [count] support to :b* and :tab* commands where missing
|
||||
8 wherever possible: get rid of dialogs and ask console-like dialog questions
|
||||
or write error prompts directly on the webpage or with :echo()
|
||||
8 registers
|
||||
8 add support for filename special characters such as %
|
||||
8 :redir and 'verbosefile'
|
||||
8 Add information to dactyl/HACKING file about testing and optimization
|
||||
|
||||
Reference in New Issue
Block a user