1
0
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:
Kris Maglione
2011-10-06 01:02:11 -04:00
parent 09a3bfcaac
commit 1b781416c9
10 changed files with 255 additions and 50 deletions

View File

@@ -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 &&

View File

@@ -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;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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();

View File

@@ -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:

View File

@@ -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;
}
});

View File

@@ -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) {

View File

@@ -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

View File

@@ -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