diff --git a/common/content/buffer.js b/common/content/buffer.js index 823d5b97..6bfc8a69 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -271,7 +271,9 @@ const Buffer = Module("buffer", { statusline.updateUrl(); statusline.updateProgress(); - autocommands.trigger("LocationChange", { url: buffer.URL }); + util.timeout(function () { + autocommands.trigger("LocationChange", { url: buffer.URL }); + }); // if this is not delayed we get the position of the old buffer util.timeout(function () { @@ -1532,11 +1534,7 @@ const Buffer = Module("buffer", { mappings.add(myModes, ["i", ""], "Start caret mode", - function () { - // setting this option notifies an observer which takes care of the - // mode setting - options.setPref("accessibility.browsewithcaret", true); - }); + function () { modes.push(modes.CARET); }); mappings.add(myModes, [""], "Stop loading the current web page", diff --git a/common/content/commandline.js b/common/content/commandline.js index b1cf3b8f..87f5ba1f 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -440,8 +440,13 @@ const CommandLine = Module("commandline", { * @param {number} extendedMode */ open: function open(prompt, cmd, extendedMode) { + this.widgets.message = null; + modes.push(modes.COMMAND_LINE, this.currentExtendedMode, { - leave: commandline.closure.leave + leave: function (params) { + if (params.pop) + commandline.leave(); + } }); this.currentExtendedMode = extendedMode || null; @@ -474,8 +479,10 @@ const CommandLine = Module("commandline", { this.hideCompletions(); if (!this._keepCommand || this._silent || this._quiet) { - commandline.updateMorePrompt(); - this.hide(); + modes.delay(function () { + this.updateMorePrompt(); + this.hide(); + }, this); } }, @@ -485,7 +492,7 @@ const CommandLine = Module("commandline", { return this._lastCommand; }, set command(val) { - if (this.commandVisible) + if (this.commandVisible && (modes.extended & modes.EX)) return this.widgets.command = val; return this._lastCommand = val; }, @@ -552,6 +559,7 @@ const CommandLine = Module("commandline", { let doc = this.widgets.multilineOutput.contentDocument; let win = this.widgets.multilineOutput.contentWindow; + this.widgets.message = null; if (!this.commandVisible) this.hide(); @@ -559,7 +567,9 @@ const CommandLine = Module("commandline", { this._startHints = false; if (!(modes.extended & modes.OUTPUT_MULTILINE)) - modes.push(modes.COMMAND_LINE, modes.OUTPUT_MULTILINE); + modes.push(modes.COMMAND_LINE, modes.OUTPUT_MULTILINE, { + onEvent: this.closure.onMultilineOutputEvent + }); // If it's already XML, assume it knows what it's doing. // Otherwise, white space is significant. @@ -687,15 +697,13 @@ const CommandLine = Module("commandline", { cancel: extra.onCancel }; - modes.push(modes.COMMAND_LINE, modes.PROMPT | extra.extended, { - leave: function (newMode) { - commandline.leave(newMode); - if (extra.leave) - extra.leave(newMode); - }, - restore: function (newMode) { extra.restore && extra.restore(newMode) }, - save: function (newMode) { extra.save && extra.save(newMode) } - }); + modes.push(modes.COMMAND_LINE, modes.PROMPT | extra.extended, + update(Object.create(extra), { + leave: function leave(stack) { + commandline.leave(stack); + leave.supercall(this, stack); + } + })); this.currentExtendedMode = modes.PROMPT; this.widgets.prompt = !prompt ? null : [extra.promptHighlight || "Question", prompt]; @@ -927,14 +935,7 @@ const CommandLine = Module("commandline", { } if (event instanceof MouseEvent) - return; - - if (this._startHints) { - statusline.updateInputBuffer(""); - this._startHints = false; - hints.show(key, { window: win }); - return; - } + return false; function isScrollable() !win.scrollMaxY == 0; function atEnd() win.scrollY / win.scrollMaxY >= 1; @@ -946,7 +947,7 @@ const CommandLine = Module("commandline", { case ":": commandline.open(":", "", modes.EX); - return; + return false; // down a line case "j": @@ -1058,9 +1059,8 @@ const CommandLine = Module("commandline", { break; case ";": - statusline.updateInputBuffer(";"); - this._startHints = true; - break; + hints.open(";", { window: win }); + return false; // unmapped key default: @@ -1078,6 +1078,7 @@ const CommandLine = Module("commandline", { } else commandline.updateMorePrompt(showMorePrompt, showMoreHelpPrompt); + return false; }, getSpaceNeeded: function getSpaceNeeded() { diff --git a/common/content/dactyl-overlay.js b/common/content/dactyl-overlay.js index f764ef9d..676a403d 100644 --- a/common/content/dactyl-overlay.js +++ b/common/content/dactyl-overlay.js @@ -13,7 +13,7 @@ sandbox.__proto__ = proto || modules; return sandbox; } - const jsmodules = { dump: function dump_(arg) window.dump("dactyl: " + arg + "\n") }; + const jsmodules = {}; const modules = { __proto__: jsmodules, get content() window.content, diff --git a/common/content/dactyl.js b/common/content/dactyl.js index c9dec706..b8e522e9 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -162,7 +162,7 @@ const Dactyl = Module("dactyl", { * * @returns {string} */ - clipboardRead: function clipboardRead() { + clipboardRead: function clipboardRead(getClipboard) { let str = null; try { @@ -171,7 +171,7 @@ const Dactyl = Module("dactyl", { transferable.addDataFlavor("text/unicode"); - if (clipboard.supportsSelectionClipboard()) + if (!getClipboard && clipboard.supportsSelectionClipboard()) clipboard.getData(transferable, clipboard.kSelectionClipboard); else clipboard.getData(transferable, clipboard.kGlobalClipboard); diff --git a/common/content/editor.js b/common/content/editor.js index ecaa2a58..b737a93a 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -16,30 +16,11 @@ const Editor = Module("editor", { // this._lastFindChar = null; this._lastFindCharFunc = null; - - // Hack? - dactyl.registerObserver("modeChange", function (oldMode, newMode, stack) { - switch (oldMode[0]) { - case modes.TEXTAREA: - case modes.INSERT: - editor.unselectText(); - break; - - case modes.VISUAL: - if (newMode[0] == modes.CARET) { - try { // clear any selection made; a simple if (selection) does not work - let selection = window.content.getSelection(); - selection.collapseToStart(); - } - catch (e) {} - } - else - editor.unselectText(); - break; - } - }); }, + get isCaret() modes.getStack(1).main === modes.CARET, + get isTextEdit() modes.getStack(1).main === modes.TEXT_EDIT, + line: function () { let line = 1; let text = Editor.getEditor().value; @@ -60,12 +41,15 @@ const Editor = Module("editor", { return col; }, - unselectText: function () { + unselectText: function (toEnd) { let elem = dactyl.focus; // A error occurs if the element has been removed when "elem.selectionStart" is executed. try { if (elem && elem.selectionEnd) - elem.selectionEnd = elem.selectionStart; + if (toEnd) + elem.selectionStart = elem.selectionEnd; + else + elem.selectionEnd = elem.selectionStart; } catch (e) {} }, @@ -75,7 +59,7 @@ const Editor = Module("editor", { return text.substring(Editor.getEditor().selectionStart, Editor.getEditor().selectionEnd); }, - pasteClipboard: function () { + pasteClipboard: function (clipboard, toStart) { if (dactyl.has("WINNT")) { this.executeCommand("cmd_paste"); return; @@ -85,7 +69,7 @@ const Editor = Module("editor", { let elem = dactyl.focus; if (elem.setSelectionRange) { - let text = dactyl.clipboardRead(); + let text = dactyl.clipboardRead(clipboard); if (!text) return; @@ -101,7 +85,7 @@ const Editor = Module("editor", { let tempStr2 = text; let tempStr3 = elem.value.substring(rangeEnd); elem.value = tempStr1 + tempStr2 + tempStr3; - elem.selectionStart = rangeStart + tempStr2.length; + elem.selectionStart = rangeStart + (toStart ? 0 : tempStr2.length); elem.selectionEnd = elem.selectionStart; elem.scrollTop = curTop; @@ -153,7 +137,8 @@ const Editor = Module("editor", { count--; } - modes.set(modes.VISUAL, modes.TEXTAREA); + if (modes.main != modes.VISUAL) + modes.push(modes.VISUAL); switch (motion) { case "j": @@ -204,16 +189,16 @@ const Editor = Module("editor", { switch (cmd) { case "d": this.executeCommand("cmd_delete", 1); - // need to reset the mode as the visual selection changes it - modes.main = modes.TEXTAREA; + modes.pop(modes.TEXT_EDIT); break; case "c": this.executeCommand("cmd_delete", 1); - modes.set(modes.INSERT, modes.TEXTAREA); + modes.pop(modes.TEXT_EDIT); + modes.push(modes.INSERT); break; case "y": this.executeCommand("cmd_copy", 1); - this.unselectText(); + modes.pop(modes.TEXT_EDIT); break; default: @@ -451,25 +436,43 @@ const Editor = Module("editor", { mappings: function () { var myModes = [modes.INSERT, modes.COMMAND_LINE]; - // add mappings for commands like h,j,k,l,etc. in CARET, VISUAL and TEXTAREA mode - function addMovementMap(keys, hasCount, caretModeMethod, caretModeArg, textareaCommand, visualTextareaCommand) { + // add mappings for commands like h,j,k,l,etc. in CARET, VISUAL and TEXT_EDIT mode + function addMovementMap(keys, hasCount, caretModeMethod, caretModeArg, textEditCommand, visualTextEditCommand) { let extraInfo = {}; if (hasCount) extraInfo.count = true; + function caretExecute(arg, again) { + function fixSelection() { + sel.removeAllRanges(); + sel.addRange(RangeFind.endpoint( + RangeFind.nodeRange(buffer.focusedFrame.document.documentElement), + true)); + } + + let controller = buffer.selectionController; + let sel = controller.getSelection(controller.SELECTION_NORMAL); + if (!sel.rangeCount) // Hack. + fixSelection(); + + try { + controller[caretModeMethod](caretModeArg, arg); + } + catch (e) { + dactyl.assert(again && e.result === Cr.NS_ERROR_FAILURE); + fixSelection(); + caretExecute(arg, false); + } + return false; + } + mappings.add([modes.CARET], keys, "", function (count) { if (typeof count != "number" || count < 1) count = 1; - let controller = buffer.selectionController; - let sel = controller.getSelection(controller.SELECTION_NORMAL); - if (!sel.rangeCount) // Hack. - sel.addRange(RangeFind.endpoint( - RangeFind.nodeRange(buffer.focusedFrame.document.documentElement), - true)); while (count--) - controller[caretModeMethod](caretModeArg, false); + caretExecute(false, true); }, extraInfo); @@ -479,41 +482,41 @@ const Editor = Module("editor", { count = 1; let controller = buffer.selectionController; - while (count--) { - if (modes.extended & modes.TEXTAREA) { - if (typeof visualTextareaCommand == "function") - visualTextareaCommand(); + while (count-- && modes.main == modes.VISUAL) { + if (editor.isTextEdit) { + if (typeof visualTextEditCommand == "function") + visualTextEditCommand(); else - editor.executeCommand(visualTextareaCommand); + editor.executeCommand(visualTextEditCommand); } else - controller[caretModeMethod](caretModeArg, true); + caretExecute(true, true); } }, extraInfo); - mappings.add([modes.TEXTAREA], keys, "", + mappings.add([modes.TEXT_EDIT], keys, "", function (count) { if (typeof count != "number" || count < 1) count = 1; - editor.executeCommand(textareaCommand, count); + editor.executeCommand(textEditCommand, count); }, extraInfo); } - // add mappings for commands like i,a,s,c,etc. in TEXTAREA mode + // add mappings for commands like i,a,s,c,etc. in TEXT_EDIT mode function addBeginInsertModeMap(keys, commands) { - mappings.add([modes.TEXTAREA], keys, "", + mappings.add([modes.TEXT_EDIT], keys, "", function (count) { commands.forEach(function (cmd) editor.executeCommand(cmd, 1)); - modes.set(modes.INSERT, modes.TEXTAREA); + modes.push(modes.INSERT); }); } function addMotionMap(key) { - mappings.add([modes.TEXTAREA], [key], + mappings.add([modes.TEXT_EDIT], [key], "Motion command", function (motion, count) { editor.executeCommandWithMotion(key, motion, count); }, { count: true, motion: true }); @@ -529,7 +532,7 @@ const Editor = Module("editor", { editor.executeCommand("cmd_selectLineNext"); } - // KEYS COUNT CARET TEXTAREA VISUAL_TEXTAREA + // KEYS COUNT CARET TEXT_EDIT VISUAL_TEXT_EDIT addMovementMap(["k", ""], true, "lineMove", false, "cmd_linePrevious", selectPreviousLine); addMovementMap(["j", "", ""], true, "lineMove", true, "cmd_lineNext", selectNextLine); addMovementMap(["h", "", ""], true, "characterMove", false, "cmd_charPrevious", "cmd_selectCharPrevious"); @@ -608,7 +611,12 @@ const Editor = Module("editor", { mappings.add([modes.INSERT], [""], "Edit text field in Vi mode", - function () { dactyl.mode = modes.TEXTAREA; }); + function () { + if (!editor.isTextEdit) + modes.push(modes.TEXT_EDIT); + else + dactyl.beep(); + }); mappings.add([modes.INSERT], ["", ""], "Expand insert mode abbreviation", @@ -623,67 +631,67 @@ const Editor = Module("editor", { ["", ""], "Expand insert mode abbreviation", function () { editor.expandAbbreviation(modes.INSERT); }); - // textarea mode - mappings.add([modes.TEXTAREA], + // text edit mode + mappings.add([modes.TEXT_EDIT], ["u"], "Undo", function (count) { editor.executeCommand("cmd_undo", count); - dactyl.mode = modes.TEXTAREA; + editor.unselectText(); }, { count: true }); - mappings.add([modes.TEXTAREA], + mappings.add([modes.TEXT_EDIT], [""], "Redo", function (count) { editor.executeCommand("cmd_redo", count); - dactyl.mode = modes.TEXTAREA; + editor.unselectText(); }, { count: true }); - mappings.add([modes.TEXTAREA], + mappings.add([modes.TEXT_EDIT], ["D"], "Delete the characters under the cursor until the end of the line", function () { editor.executeCommand("cmd_deleteToEndOfLine"); }); - mappings.add([modes.TEXTAREA], + mappings.add([modes.TEXT_EDIT], ["o"], "Open line below current", function (count) { editor.executeCommand("cmd_endLine", 1); - modes.set(modes.INSERT, modes.TEXTAREA); + modes.push(modes.INSERT); events.feedkeys(""); }); - mappings.add([modes.TEXTAREA], + mappings.add([modes.TEXT_EDIT], ["O"], "Open line above current", function (count) { editor.executeCommand("cmd_beginLine", 1); - modes.set(modes.INSERT, modes.TEXTAREA); + modes.push(modes.INSERT); events.feedkeys(""); editor.executeCommand("cmd_linePrevious", 1); }); - mappings.add([modes.TEXTAREA], + mappings.add([modes.TEXT_EDIT], ["X"], "Delete character to the left", function (count) { editor.executeCommand("cmd_deleteCharBackward", count); }, { count: true }); - mappings.add([modes.TEXTAREA], + mappings.add([modes.TEXT_EDIT], ["x"], "Delete character to the right", function (count) { editor.executeCommand("cmd_deleteCharForward", count); }, { count: true }); // visual mode - mappings.add([modes.CARET, modes.TEXTAREA], + mappings.add([modes.CARET, modes.TEXT_EDIT], ["v"], "Start visual mode", - function (count) { modes.set(modes.VISUAL, dactyl.mode); }); + function (count) { modes.push(modes.VISUAL); }); mappings.add([modes.VISUAL], ["v"], "End visual mode", function (count) { events.onEscape(); }); - mappings.add([modes.TEXTAREA], + mappings.add([modes.TEXT_EDIT], ["V"], "Start visual line mode", function (count) { - modes.set(modes.VISUAL, modes.TEXTAREA | modes.LINE); + modes.push(modes.VISUAL, modes.LINE); editor.executeCommand("cmd_beginLine", 1); editor.executeCommand("cmd_selectLineNext", 1); }); @@ -691,17 +699,17 @@ const Editor = Module("editor", { mappings.add([modes.VISUAL], ["c", "s"], "Change selected text", function (count) { - dactyl.assert(modes.extended & modes.TEXTAREA); + dactyl.assert(editor.isTextEdit); editor.executeCommand("cmd_cut"); - modes.set(modes.INSERT, modes.TEXTAREA); + modes.push(modes.INSERT); }); mappings.add([modes.VISUAL], ["d"], "Delete selected text", function (count) { - if (modes.extended & modes.TEXTAREA) { + if (editor.isTextEdit) { editor.executeCommand("cmd_cut"); - modes.set(modes.TEXTAREA); + modes.pop(); } else dactyl.beep(); @@ -710,27 +718,27 @@ const Editor = Module("editor", { mappings.add([modes.VISUAL], ["y"], "Yank selected text", function (count) { - if (modes.extended & modes.TEXTAREA) { + if (editor.isTextEdit) { editor.executeCommand("cmd_copy"); - modes.set(modes.TEXTAREA); + modes.pop(); } else dactyl.clipboardWrite(buffer.getCurrentWord(), true); }); - mappings.add([modes.VISUAL, modes.TEXTAREA], + mappings.add([modes.VISUAL, modes.TEXT_EDIT], ["p"], "Paste clipboard contents", function (count) { - dactyl.assert(!(modes.extended & modes.CARET)); + dactyl.assert(!editor.isCaret); if (!count) count = 1; while (count--) editor.executeCommand("cmd_paste"); - dactyl.mode = modes.TEXTAREA; + modes.pop(modes.TEXT_EDIT); }); // finding characters - mappings.add([modes.TEXTAREA, modes.VISUAL], + mappings.add([modes.TEXT_EDIT, modes.VISUAL], ["f"], "Move to a character on the current line after the cursor", function (count, arg) { let pos = editor.findCharForward(arg, count); @@ -739,7 +747,7 @@ const Editor = Module("editor", { }, { arg: true, count: true }); - mappings.add([modes.TEXTAREA, modes.VISUAL], + mappings.add([modes.TEXT_EDIT, modes.VISUAL], ["F"], "Move to a charater on the current line before the cursor", function (count, arg) { let pos = editor.findCharBackward(arg, count); @@ -748,7 +756,7 @@ const Editor = Module("editor", { }, { arg: true, count: true }); - mappings.add([modes.TEXTAREA, modes.VISUAL], + mappings.add([modes.TEXT_EDIT, modes.VISUAL], ["t"], "Move before a character on the current line", function (count, arg) { let pos = editor.findCharForward(arg, count); @@ -757,7 +765,7 @@ const Editor = Module("editor", { }, { arg: true, count: true }); - mappings.add([modes.TEXTAREA, modes.VISUAL], + mappings.add([modes.TEXT_EDIT, modes.VISUAL], ["T"], "Move before a character on the current line, backwards", function (count, arg) { let pos = editor.findCharBackward(arg, count); @@ -766,8 +774,8 @@ const Editor = Module("editor", { }, { arg: true, count: true }); - // textarea and visual mode - mappings.add([modes.TEXTAREA, modes.VISUAL], + // text edit and visual mode + mappings.add([modes.TEXT_EDIT, modes.VISUAL], ["~"], "Switch case of the character under the cursor and move the cursor to the right", function (count) { if (modes.main == modes.VISUAL) @@ -786,7 +794,7 @@ const Editor = Module("editor", { text.substring(pos + 1); editor.moveToPosition(pos + 1, true, false); } - modes.set(modes.TEXTAREA); + modes.pop(modes.TEXT_EDIT); }, { count: true }); }, diff --git a/common/content/events.js b/common/content/events.js index 0c5c50fb..a3d0a450 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -47,17 +47,16 @@ const Events = Module("events", { this._code_key = {}; this._key_code = {}; - for (let [k, v] in Iterator(KeyEvent)) - if (/^DOM_VK_(?![A-Z0-9]$)/.test(k)) { - k = k.substr(7).toLowerCase(); - let names = [k.replace(/(^|_)(.)/g, function (m, n1, n2) n2.toUpperCase()) - .replace(/^NUMPAD/, "k")]; - if (k in this._keyTable) - names = this._keyTable[k]; - this._code_key[v] = names[0]; - for (let [, name] in Iterator(names)) - this._key_code[name.toLowerCase()] = v; - } + for (let [k, v] in Iterator(KeyEvent)) { + k = k.substr(7).toLowerCase(); + let names = [k.replace(/(^|_)(.)/g, function (m, n1, n2) n2.toUpperCase()) + .replace(/^NUMPAD/, "k")]; + if (k in this._keyTable) + names = this._keyTable[k]; + this._code_key[v] = names[0]; + for (let [, name] in Iterator(names)) + this._key_code[name.toLowerCase()] = v; + } // HACK: as Gecko does not include an event for <, we must add this in manually. if (!("<" in this._key_code)) { @@ -76,11 +75,11 @@ const Events = Module("events", { this._activeMenubar = false; this.addSessionListener(window, "DOMMenuBarActive", this.closure.onDOMMenuBarActive, true); this.addSessionListener(window, "DOMMenuBarInactive", this.closure.onDOMMenuBarInactive, true); - this.addSessionListener(window, "focus", this.wrapListener(this.closure.onFocus), true); - this.addSessionListener(window, "keydown", this.wrapListener(this.closure.onKeyUpOrDown), true); - this.addSessionListener(window, "keypress", this.wrapListener(this.closure.onKeyPress), true); - this.addSessionListener(window, "keyup", this.wrapListener(this.closure.onKeyUpOrDown), true); - this.addSessionListener(window, "mousedown", this.wrapListener(this.closure.onMouseDown), true); + this.addSessionListener(window, "focus", this.wrapListener(this.onFocus), true); + this.addSessionListener(window, "keydown", this.wrapListener(this.onKeyUpOrDown), true); + this.addSessionListener(window, "keypress", this.wrapListener(this.onKeyPress), true); + this.addSessionListener(window, "keyup", this.wrapListener(this.onKeyUpOrDown), true); + this.addSessionListener(window, "mousedown", this.wrapListener(this.onMouseDown), true); this.addSessionListener(window, "popuphidden", this.closure.onPopupHidden, true); this.addSessionListener(window, "popupshown", this.closure.onPopupShown, true); this.addSessionListener(window, "resize", this.closure.onResize, true); @@ -90,7 +89,8 @@ const Events = Module("events", { destroy: function () { util.dump("Removing all event listeners"); for (let args in values(this.sessionListeners)) - args[0].removeEventListener.apply(args[0], args.slice(1)); + if (args[0].get()) + args[0].get().removeEventListener.apply(args[0].get(), args.slice(1)); }, /** @@ -105,7 +105,8 @@ const Events = Module("events", { */ addSessionListener: function (target, event, callback, capture) { let args = Array.slice(arguments, 0); - target.addEventListener.apply(args[0], args.slice(1)); + args[0].addEventListener.apply(args[0], args.slice(1)); + args[0] = Cu.getWeakReference(args[0]); this.sessionListeners.push(args); }, @@ -113,6 +114,7 @@ const Events = Module("events", { * Wraps an event listener to ensure that errors are reported. */ wrapListener: function wrapListener(method, self) { + self = self || this; return function (event) { try { method.apply(self, arguments); @@ -371,7 +373,7 @@ const Events = Module("events", { * @param {string} keys The string to parse. * @returns {Array[Object]} */ - fromString: function (input) { + fromString: function (input, unknownOk) { let out = []; let re = RegExp("<.*?>?>|[^<]|<(?!.*>)", "g"); @@ -385,9 +387,10 @@ const Events = Module("events", { let [match, modifier, keyname] = evt_str.match(/^<((?:[CSMA]-)*)(.+?)>$/i) || [false, '', '']; modifier = modifier.toUpperCase(); keyname = keyname.toLowerCase(); + evt_obj.dactylKeyname = keyname; if (keyname && !(keyname.length == 1 && modifier.length == 0 || // disallow <> and - !(keyname.length == 1 || this._key_code[keyname] || keyname == "nop" || /mouse$/.test(keyname)))) { // disallow + !(unknownOk || keyname.length == 1 || this._key_code[keyname] || keyname == "nop" || /mouse$/.test(keyname)))) { // disallow evt_obj.ctrlKey = /C-/.test(modifier); evt_obj.altKey = /A-/.test(modifier); evt_obj.shiftKey = /S-/.test(modifier); @@ -501,7 +504,7 @@ const Events = Module("events", { else if (charCode > 0) { key = String.fromCharCode(charCode); - if (key in this._key_code) { + if (!/^[a-z0-9]$/i.test(key) && key in this._key_code) { // a named charcode key ( and ) space can be shifted, must be forced if ((key.match(/^\s$/) && event.shiftKey) || event.dactylShift) modifier += "S-"; @@ -625,69 +628,17 @@ const Events = Module("events", { * The global escape key handler. This is called in ALL modes. */ onEscape: function () { - if (modes.passNextKey) - return; - - if (modes.passAllKeys) { - modes.passAllKeys = false; - return; - } - switch (dactyl.mode) { - case modes.NORMAL: - // clear any selection made - let selection = window.content.getSelection(); - try { // a simple if (selection) does not seem to work - selection.collapseToStart(); - } - catch (e) {} - - modes.reset(); - break; - - case modes.VISUAL: - if (modes.extended & modes.TEXTAREA) - dactyl.mode = modes.TEXTAREA; - else if (modes.extended & modes.CARET) - dactyl.mode = modes.CARET; - break; - - case modes.CARET: - // setting this option will trigger an observer which will - // take care of all other details like setting the NORMAL - // mode - options.setPref("accessibility.browsewithcaret", false); - break; - - case modes.TEXTAREA: - // TODO: different behaviour for text areas and other input - // fields seems unnecessarily complicated. If the user - // likes Vi-mode then they probably like it for all input - // fields, if not they can enter it explicitly for only - // text areas. The mode name TEXTAREA is confusing and - // would be better replaced with something indicating that - // it's a Vi editing mode. Extended modes really need to be - // displayed too. --djk - function isInputField() { - let elem = dactyl.focus; - return elem instanceof HTMLInputElement && set.has(util.editableInputs, elem.type) - || elem instanceof HTMLIsIndexElement; - } - - if (options["insertmode"] || isInputField()) - dactyl.mode = modes.INSERT; - else - modes.reset(); - break; - + case modes.COMMAND_LINE: case modes.INSERT: - if ((modes.extended & modes.TEXTAREA)) - dactyl.mode = modes.TEXTAREA; - else - modes.reset(); + case modes.PASS_THROUGH: + case modes.QUOTE: + case modes.TEXT_EDIT: + case modes.VISUAL: + modes.pop(); break; - default: // HINTS, CUSTOM or COMMAND_LINE + default: modes.reset(); break; } @@ -752,12 +703,15 @@ const Events = Module("events", { } if (elem instanceof HTMLTextAreaElement || (elem && util.computedStyle(elem).MozUserModify == "read-write")) { + if (modes.main === modes.VISUAL && elem.selectionEnd == elem.selectionStart) + modes.pop(); if (options["insertmode"]) modes.set(modes.INSERT); - else if (elem.selectionEnd - elem.selectionStart > 0) - modes.set(modes.VISUAL, modes.TEXTAREA); - else - modes.main = modes.TEXTAREA; + else { + modes.set(modes.TEXT_EDIT); + if (elem.selectionEnd - elem.selectionStart > 0) + modes.push(modes.VISUAL); + } if (hasHTMLDocument(win)) buffer.lastInputField = elem; return; @@ -772,7 +726,7 @@ const Events = Module("events", { if (elem == null && urlbar && urlbar.inputField == this._lastFocus) util.threadYield(true); - if (dactyl.mode & (modes.EMBED | modes.INSERT | modes.TEXTAREA | modes.VISUAL)) + if (modes.getMode(modes.main).ownsFocus) modes.reset(); } finally { @@ -784,7 +738,7 @@ const Events = Module("events", { // the commandline has focus // TODO: ...help me...please... onKeyPress: function (event) { - function isEscapeKey(key) key == "" || key == ""; + function isEscape(key) key == "" || key == ""; function killEvent() { event.preventDefault(); @@ -802,7 +756,7 @@ const Events = Module("events", { dactyl.echomsg("Recorded macro '" + this._currentMacro + "'"); return killEvent(); } - else if (!mappings.hasMap(dactyl.mode, this._input.buffer + key)) + else if (!mappings.hasMap(mode, this._input.buffer + key)) this._macros.set(this._currentMacro, { keys: this._macros.get(this._currentMacro, {}).keys + key, timeRecorded: Date.now() @@ -832,6 +786,7 @@ const Events = Module("events", { try { let stop = false; + let mode = modes.getStack(0); let win = document.commandDispatcher.focusedWindow; if (win && win.document && "designMode" in win.document && win.document.designMode == "on" && !config.isComposeWindow) @@ -839,20 +794,19 @@ const Events = Module("events", { // menus have their own command handlers if (modes.extended & modes.MENU) stop = true; + else if (modes.main == modes.PASS_THROUGH) + // let flow continue to handle these keys to cancel escape-all-keys mode + stop = !isEscape(key) && key != "" // handle Escape-one-key mode (Ctrl-v) - else if (modes.passNextKey && !modes.passAllKeys) { - modes.passNextKey = false; - stop = true; + else if (modes.main == modes.QUOTE) { + stop = modes.getStack(1).main !== modes.PASS_THROUGH || isEscape(key); + // We need to preserve QUOTE mode until the escape + // handler to escape the key + if (!stop || !isEscape(key)) + modes.pop(); + mode = modes.getStack(1); } // handle Escape-all-keys mode (Ctrl-q) - else if (modes.passAllKeys) { - if (modes.passNextKey) - modes.passNextKey = false; // and then let flow continue - else if (isEscapeKey(key) || key == "") - ; // let flow continue to handle these keys to cancel escape-all-keys mode - else - stop = true; - } if (stop) { this._input.buffer = ""; @@ -861,64 +815,32 @@ const Events = Module("events", { stop = true; // set to false if we should NOT consume this event but let the host app handle it - // just forward event without checking any mappings when the MOW is open - if (dactyl.mode == modes.COMMAND_LINE && (modes.extended & modes.OUTPUT_MULTILINE)) { - commandline.onMultilineOutputEvent(event); - return killEvent(); - } - // XXX: ugly hack for now pass certain keys to the host app as // they are without beeping also fixes key navigation in combo // boxes, submitting forms, etc. // FIXME: breaks iabbr for now --mst - if (key in config.ignoreKeys && (config.ignoreKeys[key] & dactyl.mode)) { + if (key in config.ignoreKeys && (config.ignoreKeys[key] & mode.main)) { this._input.buffer = ""; return null; } - // TODO: handle middle click in content area + if (mode.params.onEvent) { + this._input.buffer = ""; + // Bloody hell. + if (key === "") + key = event.dactylString = ""; - if (!isEscapeKey(key)) { - // custom mode... - if (dactyl.mode == modes.CUSTOM) { - plugins.onEvent(event); - return killEvent(); - } - - // All of these special cases for hint mode are driving - // me insane! -Kris - if (modes.extended & modes.HINTS) { - // under HINT mode, certain keys are redirected to hints.onEvent - if (key == "" || key == "" || key == "" - || key == options["mapleader"] - || (key == "" && hints.prevInput == "number") - || (hints.isHintKey(key) && !hints.escNumbers)) { - hints.onEvent(event); - this._input.buffer = ""; - return killEvent(); - } - - // others are left to generate the 'input' event or handled by the host app - return null; - } - } - - // FIXME (maybe): (is an ESC or C-] here): on HINTS mode, it enters - // into 'if (map && !skipMap) below. With that (or however) it - // triggers the onEscape part, where it resets mode. Here I just - // return true, with the effect that it also gets to there (for - // whatever reason). if that happens to be correct, well.. - // XXX: why not just do that as well for HINTS mode actually? - - if (dactyl.mode == modes.CUSTOM) + if (mode.params.onEvent(event) === false) + killEvent(); return null; + } let inputStr = this._input.buffer + key; let countStr = inputStr.match(/^[1-9][0-9]*|/)[0]; let candidateCommand = inputStr.substr(countStr.length); - let map = mappings[event.noremap ? "getDefault" : "get"](dactyl.mode, candidateCommand); + let map = mappings[event.noremap ? "getDefault" : "get"](mode.main, candidateCommand); - let candidates = mappings.getCandidates(dactyl.mode, candidateCommand); + let candidates = mappings.getCandidates(mode.main, candidateCommand); if (candidates.length == 0 && !map) { map = this._input.pendingMap; this._input.pendingMap = null; @@ -929,7 +851,7 @@ const Events = Module("events", { // counts must be at the start of a complete mapping (10j -> go 10 lines down) if (countStr && !candidateCommand) { // no count for insert mode mappings - if (!modes.mainMode.count || modes.mainMode.input) + if (!mode.mainMode.count || mode.mainMode.input) stop = false; else this._input.buffer = inputStr; @@ -938,7 +860,7 @@ const Events = Module("events", { this._input.buffer = ""; let map = this._input.pendingArgMap; this._input.pendingArgMap = null; - if (!isEscapeKey(key)) { + if (!isEscape(key)) { if (modes.isReplaying && !this.waitForPageLoad()) return null; map.execute(null, this._input.count, key); @@ -957,7 +879,7 @@ const Events = Module("events", { this._input.pendingArgMap = map; } else if (this._input.pendingMotionMap) { - if (!isEscapeKey(key)) + if (!isEscape(key)) this._input.pendingMotionMap.execute(candidateCommand, this._input.count, null); this._input.pendingMotionMap = null; } @@ -974,14 +896,14 @@ const Events = Module("events", { stop = false; } } - else if (mappings.getCandidates(dactyl.mode, candidateCommand).length > 0 && !event.skipmap) { + else if (mappings.getCandidates(mode.main, candidateCommand).length > 0 && !event.skipmap) { this._input.pendingMap = map; this._input.buffer += key; } else { // if the key is neither a mapping nor the start of one // the mode checking is necessary so that things like g do not beep if (this._input.buffer != "" && !event.skipmap && - (dactyl.mode & (modes.INSERT | modes.COMMAND_LINE | modes.TEXTAREA))) + (mode.main & (modes.INSERT | modes.COMMAND_LINE | modes.TEXT_EDIT))) events.feedkeys(this._input.buffer, { noremap: true, skipmap: true }); this._input.buffer = ""; @@ -989,17 +911,17 @@ const Events = Module("events", { this._input.pendingMotionMap = null; this._input.pendingMap = null; - if (!isEscapeKey(key)) { + if (!isEscape(key)) { // allow key to be passed to the host app if we can't handle it - stop = (dactyl.mode == modes.TEXTAREA); + stop = (mode.main === modes.TEXT_EDIT); - if (dactyl.mode == modes.COMMAND_LINE) { + if (mode.main === modes.COMMAND_LINE) { if (!(modes.extended & modes.INPUT_MULTILINE)) dactyl.trapErrors(function () { commandline.onEvent(event); // reroute event in command line mode }); } - else if (!modes.mainMode.input) + else if (!mode.mainMode.input) dactyl.beep(); } } @@ -1019,9 +941,8 @@ const Events = Module("events", { // this is need for sites like msn.com which focus the input field on keydown onKeyUpOrDown: function (event) { - if (modes.passNextKey ^ modes.passAllKeys || Events.isInputElemFocused()) - return; - event.stopPropagation(); + if (!Events.isInputElemFocused() && !modes.passThrough) + event.stopPropagation(); }, onMouseDown: function (event) { @@ -1052,27 +973,19 @@ const Events = Module("events", { }, onSelectionChange: function (event) { - let couldCopy = false; let controller = document.commandDispatcher.getControllerForCommand("cmd_copy"); - if (controller && controller.isCommandEnabled("cmd_copy")) - couldCopy = true; + let couldCopy = controller && controller.isCommandEnabled("cmd_copy"); - if (dactyl.mode != modes.VISUAL) { - if (couldCopy) { - if ((dactyl.mode == modes.TEXTAREA || - (modes.extended & modes.TEXTAREA)) - && !options["insertmode"]) - modes.set(modes.VISUAL, modes.TEXTAREA); - else if (dactyl.mode == modes.CARET) - modes.set(modes.VISUAL, modes.CARET); - } + if (dactyl.mode === modes.VISUAL) { + if (!couldCopy) + modes.pop(); // Really not ideal. + } + else if (couldCopy) { + if (modes.main == modes.TEXT_EDIT && !options["insertmode"]) + modes.push(modes.VISUAL); + else if (dactyl.mode == modes.CARET) + modes.push(modes.VISUAL); } - // XXX: disabled, as i think automatically starting visual caret mode does more harm than help - // else - // { - // if (!couldCopy && modes.extended & modes.CARET) - // dactyl.mode = modes.CARET; - // } } }, { isContentNode: function (node) { @@ -1135,17 +1048,17 @@ const Events = Module("events", { [""], "Advance keyboard focus", function () { document.commandDispatcher.advanceFocus(); }); - mappings.add([modes.NORMAL, modes.PLAYER, modes.VISUAL, modes.CARET, modes.INSERT, modes.TEXTAREA], + mappings.add([modes.NORMAL, modes.PLAYER, modes.VISUAL, modes.CARET, modes.INSERT, modes.TEXT_EDIT], [""], "Rewind keyboard focus", function () { document.commandDispatcher.rewindFocus(); }); mappings.add(modes.all, [""], "Temporarily ignore all " + config.appName + " key bindings", - function () { modes.passAllKeys = true; }); + function () { modes.push(modes.PASS_THROUGH); }); mappings.add(modes.all, [""], "Pass through next key", - function () { modes.passNextKey = true; }); + function () { modes.push(modes.QUOTE); }); mappings.add(modes.all, [""], "Do nothing", diff --git a/common/content/finder.js b/common/content/finder.js index afa56cfa..3284f2fc 100644 --- a/common/content/finder.js +++ b/common/content/finder.js @@ -114,8 +114,6 @@ const RangeFinder = Module("rangefinder", { if (options["hlsearch"]) this.highlight(); this.rangeFind.focus(); - - modes.reset(); }, // Called when the search is canceled - for example if someone presses @@ -186,14 +184,14 @@ const RangeFinder = Module("rangefinder", { ["N"], "Find previous", function () { rangefinder.findAgain(true); }); - mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["*"], + mappings.add(myModes.concat([modes.CARET, modes.TEXT_EDIT]), ["*"], "Find word under cursor", function () { rangefinder.find(buffer.getCurrentWord(), false); rangefinder.findAgain(); }); - mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["#"], + mappings.add(myModes.concat([modes.CARET, modes.TEXT_EDIT]), ["#"], "Find word under cursor backwards", function () { rangefinder.find(buffer.getCurrentWord(), true); diff --git a/common/content/hints.js b/common/content/hints.js index 255d4e95..6721398b 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -97,7 +97,7 @@ const Hints = Module("hints", { if (!this._usedTabKey) { this._hintNumber = 0; } - if (this.__continue && this._validHints.length <= 1) { + if (this._continue && this._validHints.length <= 1) { this._hintString = ""; commandline.widgets.command = this._hintString; this._showHints(); @@ -764,6 +764,20 @@ const Hints = Module("hints", { */ isHintKey: function (key) this.hintKeys.indexOf(key) >= 0, + open: function (mode, opts) { + this._extendedhintCount = opts.count; + commandline.input(";", null, { + promptHighlight: "Normal", + completer: function (context) { + context.compare = function () 0; + context.completions = [[k, v.prompt] for ([k, v] in Iterator(hints._hintModes))]; + }, + onAccept: function (arg) { arg && util.timeout(function () hints.show(arg, opts), 0); }, + get onCancel() this.onAccept, + onChange: function () { modes.pop(); } + }); + }, + /** * Updates the display of hints. * @@ -777,8 +791,12 @@ const Hints = Module("hints", { commandline.input(UTF8(this._hintMode.prompt) + ": ", null, { extended: modes.HINTS, - leave: function () { hints.hide(); }, - onChange: this.closure._onInput + leave: function (stack) { + if (!stack.push) + hints.hide(); + }, + onChange: this.closure._onInput, + onEvent: this.closure.onEvent }); modes.extended = modes.HINTS; @@ -860,9 +878,12 @@ const Hints = Module("hints", { } this._showActiveHint(this._hintNumber, oldId); this._updateStatusline(); - return; + return false; case "": + if (this.prevInput !== "number") + return true; + if (this._hintNumber > 0 && !this._usedTabKey) { this._hintNumber = Math.floor(this._hintNumber / this.hintKeys.length); if (this._hintNumber == 0) @@ -882,10 +903,10 @@ const Hints = Module("hints", { this._hintNumber = 0; this._updateStatusline(); - return; + return false; default: - if (this.isHintKey(key)) { + if (!this.escNumbers && this.isHintKey(key)) { this.prevInput = "number"; let oldHintNumber = this._hintNumber; @@ -910,8 +931,9 @@ const Hints = Module("hints", { dactyl.assert(this._hintNumber != 0); this._checkUnique(); - return; + return false; } + return true; } this._updateStatusline(); @@ -923,6 +945,7 @@ const Hints = Module("hints", { this._showHints(); this._processHints(followFirst); } + return false; } //}}} }, { @@ -1048,30 +1071,15 @@ const Hints = Module("hints", { "Start QuickHint mode, but open link in a new tab", function () { hints.show(options.get("activate").has("links") ? "t" : "b"); }); - function inputOpts(opts) ({ - promptHighlight: "Normal", - completer: function (context) { - context.compare = function () 0; - context.completions = [[k, v.prompt] for ([k, v] in Iterator(hints._hintModes))]; - }, - onAccept: function (arg) { arg && util.timeout(function () hints.show(arg, opts), 0); }, - onChange: function () { modes.pop(); }, - onCancel: function (arg) { arg && util.timeout(function () hints.show(arg, opts), 0); } - }); - mappings.add(myModes, [";"], "Start an extended hint mode", - function (count) { - this._extendedhintCount = count; - commandline.input(";", null, inputOpts()); - }, { count: true }); + function (count) { hints.open(";", { count: count }); }, + { count: true }); mappings.add(myModes, ["g;"], "Start an extended hint mode and stay there until is pressed", - function (count) { - this._extendedhintCount = count; - commandline.input("g;", null, inputOpts({ continue: true })); - }, { count: true }); + function (count) { hints.open("g;", { continue: true, count: count }); }, + { count: true }); }, options: function () { const DEFAULT_HINTTAGS = diff --git a/common/content/modes.js b/common/content/modes.js index 5b963da4..39e4f836 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -30,14 +30,42 @@ const Modes = Module("modes", { this.boundProperties = {}; // main modes, only one should ever be active - this.addMode("NORMAL", { char: "n", display: null }); - this.addMode("INSERT", { char: "i", input: true }); - this.addMode("VISUAL", { char: "v", display: function () "VISUAL" + (this._extended & modes.LINE ? " LINE" : "") }); + this.addMode("NORMAL", { char: "n", display: function () null }); + this.addMode("INSERT", { char: "i", input: true, ownsFocus: true }); + this.addMode("VISUAL", { char: "v", ownsFocus: true, display: function () "VISUAL" + (this._extended & modes.LINE ? " LINE" : "") }, { + leave: function (stack, newMode) { + if (newMode.main == modes.CARET) { + let selection = window.content.getSelection(); + if (selection && !selection.isCollapsed) + selection.collapseToStart(); + } + else + editor.unselectText(); + } + }); this.addMode("COMMAND_LINE", { char: "c", input: true }); - this.addMode("CARET"); // text cursor is visible - this.addMode("TEXTAREA", { char: "i" }); - this.addMode("EMBED", { input: true }); - this.addMode("CUSTOM", { display: function () plugins.mode }); + this.addMode("CARET", {}, { + get pref() options.getPref("accessibility.browsewithcaret"), + set pref(val) options.setPref("accessibility.browsewithcaret", val), + enter: function (stack) { + if (stack.pop && !this.pref) + modes.pop(); + else if (!stack.pop && !this.pref) + this.pref = true; + }, + leave: function (stack) { + if (!stack.push && this.pref) + this.pref = false; + } + }); + this.addMode("TEXT_EDIT", { char: "t", ownsFocus: true }); + this.addMode("EMBED", { input: true, ownsFocus: true }); + this.addMode("PASS_THROUGH"); + this.addMode("QUOTE", { + display: function () modes.getStack(1).main == modes.PASS_THROUGH + ? (modes.getStack(2).mainMode.display() || modes.getStack(2).mainMode.name) + " (next)" + : "PASS THROUGH (next)" + }); // this._extended modes, can include multiple modes, and even main modes this.addMode("EX", true); this.addMode("HINTS", true); @@ -45,30 +73,31 @@ const Modes = Module("modes", { this.addMode("OUTPUT_MULTILINE", true); this.addMode("SEARCH_FORWARD", true); this.addMode("SEARCH_BACKWARD", true); - this.addMode("SEARCH_VIEW_FORWARD", true); - this.addMode("SEARCH_VIEW_BACKWARD", true); this.addMode("MENU", true); // a popupmenu is active this.addMode("LINE", true); // linewise visual mode this.addMode("PROMPT", true); this.push(this.NORMAL, 0, { - restore: function (prev) { - // disable caret mode when we want to switch to normal mode + enter: function (stack, prev) { if (options.getPref("accessibility.browsewithcaret")) options.setPref("accessibility.browsewithcaret", false); statusline.updateUrl(); - dactyl.focusContent(true); + if (prev.mainMode.input || prev.mainMode.ownsFocus) + dactyl.focusContent(true); + if (prev.main === modes.NORMAL) { + dactyl.focusContent(true); + // clear any selection made + let selection = window.content.getSelection(); + if (selection && !selection.isCollapsed) + selection.collapseToStart(); + } + } }); }, _getModeMessage: function () { - if (this._passNextKey && !this._passAllKeys) - return "-- PASS THROUGH (next) --"; - else if (this._passAllKeys && !this._passNextKey) - return "-- PASS THROUGH --"; - // when recording a macro let macromode = ""; if (modes.isRecording) @@ -81,7 +110,8 @@ const Modes = Module("modes", { ext += " (menu)"; ext += " --" + macromode; - if (this._main in this._modeMap && typeof this._modeMap[this._main].display == "function") + let val = this._modeMap[this._main].display(); + if (val) return "-- " + this._modeMap[this._main].display() + ext; return macromode; }, @@ -98,27 +128,31 @@ const Modes = Module("modes", { get topOfStack() this._modeStack[this._modeStack.length - 1], - addMode: function (name, extended, options) { + addMode: function (name, extended, options, params) { let disp = name.replace("_", " ", "g"); this[name] = 1 << this._lastMode++; + if (typeof extended == "object") { + params = options; options = extended; extended = false; } + let mode = util.extend({ - extended: extended, count: true, + disp: disp, + extended: extended, input: false, mask: this[name], name: name, - disp: disp + params: params || {} }, options); if (mode.char) { this.modeChars[mode.char] = this.modeChars[mode.char] || []; this.modeChars[mode.char].push(mode); } - if (mode.display !== null) + if (mode.display == null) mode.display = function () disp; this._modeMap[name] = mode; this._modeMap[this[name]] = mode; @@ -129,6 +163,8 @@ const Modes = Module("modes", { getMode: function (name) this._modeMap[name], + getStack: function (idx) this._modeStack[this._modeStack.length - idx - 1] || this._modeStack[0], + getCharModes: function (chr) [m for (m in values(this._modeMap)) if (m.char == chr)], matchModes: function (obj) @@ -139,7 +175,8 @@ const Modes = Module("modes", { let msg = null; if (options["showmode"]) msg = this._getModeMessage(); - commandline.widgets.mode = msg || null; + if (loaded.commandline) + commandline.widgets.mode = msg || null; }, // add/remove always work on the this._extended mode only @@ -148,6 +185,9 @@ const Modes = Module("modes", { this.show(); }, + delayed: [], + delay: function (callback, self) { this.delayed.push([callback, self]) }, + save: function (id, obj, prop) { if (!(id in this.boundProperties)) for (let elem in values(this._modeStack)) @@ -158,27 +198,11 @@ const Modes = Module("modes", { // helper function to set both modes in one go // if silent == true, you also need to take care of the mode handling changes yourself set: function (mainMode, extendedMode, params, stack) { - params = params || {}; + params = params || this.getMode(mainMode || this.main).params; if (!stack && mainMode != null && this._modeStack.length > 1) this.reset(); - let push = mainMode != null && !(stack && stack.pop) && - Modes.StackElem(mainMode, extendedMode || this.NONE, params, {}); - if (push && this.topOfStack) { - if (this.topOfStack.params.save) - this.topOfStack.params.save(push); - - for (let [id, { obj, prop }] in Iterator(this.boundProperties)) { - if (!obj.get()) - delete this.boundProperties(id); - else - this.topOfStack.saved[id] = { obj: obj.get(), prop: prop, value: obj.get()[prop] }; - } - } - - let silent = this._main === mainMode && this._extended === extendedMode; - // if a this._main mode is set, the this._extended is always cleared let oldMain = this._main, oldExtended = this._extended; if (typeof extendedMode === "number") @@ -189,35 +213,68 @@ const Modes = Module("modes", { this._extended = this.NONE; } + if (stack && stack.pop && stack.pop.params.leave) + stack.pop.params.leave(stack, this.topOfStack); + + let push = mainMode != null && !(stack && stack.pop) && + Modes.StackElem(this._main, this._extended, params, {}); + if (push && this.topOfStack) { + if (this.topOfStack.params.leave) + this.topOfStack.params.leave({ push: push }, push); + for (let [id, { obj, prop }] in Iterator(this.boundProperties)) { + if (!obj.get()) + delete this.boundProperties(id); + else + this.topOfStack.saved[id] = { obj: obj.get(), prop: prop, value: obj.get()[prop] }; + } + } + + this.delayed.forEach(function ([fn, self]) fn.call(self)); + this.delayed = []; + + let prev = stack && stack.pop || this.topOfStack; if (push) this._modeStack.push(push); - dactyl.triggerObserver("modeChange", [oldMain, oldExtended], [this._main, this._extended], stack); + if (stack && stack.pop) { + for (let [k, { obj, prop, value }] in Iterator(this.topOfStack.saved)) + obj[prop] = value; + } - if (!silent) - this.show(); + if (this.topOfStack.params.enter && prev) + this.topOfStack.params.enter(push ? { push: push } : stack || {}, + prev); + + dactyl.triggerObserver("modeChange", [oldMain, oldExtended], [this._main, this._extended], stack); + this.show(); }, push: function (mainMode, extendedMode, params) { this.set(mainMode, extendedMode, params, { push: this.topOfStack }); }, - pop: function () { - let a = this._modeStack.pop(); - if (a.params.leave) - a.params.leave(this.topOfStack); + pop: function (mode) { + while (this._modeStack.length > 1 && this.main != mode) { + let a = this._modeStack.pop(); + this.set(this.topOfStack.main, this.topOfStack.extended, this.topOfStack.params, + { pop: a }); - this.set(this.topOfStack.main, this.topOfStack.extended, this.topOfStack.params, { pop: a }); - if (this.topOfStack.params.restore) - this.topOfStack.params.restore(a); + if (mode == null) + return; + } + }, - for (let [k, { obj, prop, value }] in Iterator(this.topOfStack.saved)) - obj[prop] = value; + replace: function (mode, oldMode) { + while (oldMode && this._modeStack.length > 1 && this.main != oldMode) + this.pop(); + + this.set(mode, null, null, { push: this.topOfStack, pop: this._modeStack.pop() }); + this.push(mode); }, reset: function () { - if (this._modeStack.length == 1 && this.topOfStack.params.restore) - this.topOfStack.params.restore(this.topOfStack); + if (this._modeStack.length == 1 && this.topOfStack.params.enter) + this.topOfStack.params.enter({}, this.topOfStack); while (this._modeStack.length > 1) this.pop(); }, @@ -229,12 +286,6 @@ const Modes = Module("modes", { } }, - get passNextKey() this._passNextKey, - set passNextKey(value) { this._passNextKey = value; this.show(); }, - - get passAllKeys() this._passAllKeys, - set passAllKeys(value) { this._passAllKeys = value; this.show(); }, - get isRecording() this._isRecording, set isRecording(value) { this._isRecording = value; this.show(); }, @@ -247,7 +298,17 @@ const Modes = Module("modes", { get extended() this._extended, set extended(value) { this.set(null, value); } }, { - StackElem: Struct("main", "extended", "params", "saved"), + StackElem: (function () { + let struct = Struct("main", "extended", "params", "saved"); + struct.prototype.__defineGetter__("mainMode", function () modes.getMode(this.main)); + struct.prototype.toString = function () !loaded.modes ? this.main : "[mode " + + modes.getMode(this.main).name + + (!this.extended ? "" : + "(" + + [modes.getMode(1<Enable passthrough mode on all Google sites:

-:autocmd LocationChange .* js modes.passAllKeys = /google\.com/.test(buffer.URL) +:autocmd LocationChange google\.com -js modes.push(modes.PASS_THROUGH)

Enable passthrough mode on some Google sites:

-:autocmd LocationChange .* js modes.passAllKeys = /(www|mail)\.google\.com/.test(buffer.URL) +:autocmd LocationChange (www|mail)\.google\.com -js modes.push(modes.PASS_THROUGH)

Set the filetype to mail when editing email at Gmail:

- -:autocmd LocationChange !'mail\.google\.com' :set editor=gvim -f -:autocmd LocationChange 'mail\.google\.com' :set editor=gvim -f -c 'set ft=mail' - +:autocmd LocationChange !mail\.google\.com :set editor=gvim -f +:autocmd LocationChange mail\.google\.com :set editor=gvim -f -c 'set ft=mail' diff --git a/common/locale/en-US/gui.xml b/common/locale/en-US/gui.xml index 81d32a5a..6b638a01 100644 --- a/common/locale/en-US/gui.xml +++ b/common/locale/en-US/gui.xml @@ -154,7 +154,8 @@ :extu :extupdate - :extupdate!
extension + :extupdate extension + :extupdate!

Update an extension. When ! is given, update all diff --git a/common/locale/en-US/index.xml b/common/locale/en-US/index.xml index 351faadc..c2838f61 100644 --- a/common/locale/en-US/index.xml +++ b/common/locale/en-US/index.xml @@ -16,9 +16,9 @@ This file contains a list of all available commands, mappings and options.

Insert mode

-
i
Start Insert mode in text areas when insertmode is not set
+
i
Start Insert mode in text areas when insertmode is not set
Launch the external editor
-
Enter Textarea mode
+
Enter TextEdit mode
Expand an Insert-mode abbreviation
diff --git a/common/locale/en-US/insert.xml b/common/locale/en-US/insert.xml index 7a7e1d7a..3ec7d65a 100644 --- a/common/locale/en-US/insert.xml +++ b/common/locale/en-US/insert.xml @@ -20,8 +20,8 @@

- i_i - i_i + t_i + t_i

Starts Insert mode in text areas when insertmode is not set.

@@ -42,7 +42,7 @@ <C-t>

- Enter Textarea mode. This is useful for quick editing of text fields + Enter TextEdit mode. This is useful for quick editing of text fields with basic Vim-keys support. See also insertmode.

diff --git a/common/locale/en-US/map.xml b/common/locale/en-US/map.xml index 02aac3c1..4b125a2e 100644 --- a/common/locale/en-US/map.xml +++ b/common/locale/en-US/map.xml @@ -52,6 +52,7 @@
n
Normal mode: When browsing normally
v
Visual mode: When selecting text with the cursor keys
i
Insert mode: When interacting with text fields on a website
+
t
TextEdit mode: When editing text fields in Vim-like NORMAL mode
c
Command-line mode: When typing into the &dactyl.appName; command line
@@ -82,6 +83,8 @@ :vmap lhs rhs :im :imap :imap lhs rhs + :tm :tmap + :tmap lhs rhs :cm :cmap :cmap lhs rhs @@ -126,6 +129,8 @@ :vnoremap lhs rhs :ino :inoremap :inoremap lhs rhs + :tno :tnoremap + :tnoremap lhs rhs :cno :cnoremap :cnoremap lhs rhs @@ -150,6 +155,8 @@ :vunmap lhs rhs :iu :iunmap :iunmap lhs rhs + :tu :tunmap + :tunmap lhs rhs :cu :cunmap :cunmap lhs rhs @@ -166,6 +173,8 @@ :vmapclear :imapc :imapclear :imapclear + :tmapc :tmapclear + :tmapclear :cmapc :cmapclear :cmapclear @@ -180,6 +189,7 @@ :nmap :vmap :imap + :tmap :cmap

List all mappings for the applicable mode(s).

@@ -195,6 +205,8 @@ :vmap lhs :imap_l :imap lhs + :tmap_l + :tmap lhs :cmap_l :cmap lhs @@ -224,6 +236,7 @@ :nmap :nnoremap :nunmap :nmapclear – Normal mode :vmap :vnoremap :vunmap :vmapclear – Visual mode :imap :inoremap :iunmap :imapclear – Insert mode +:tmap :tnoremap :tunmap :tmapclear – Text Edit mode :cmap :cnoremap :cunmap :cmapclear – Command-line mode diff --git a/common/locale/en-US/options.xml b/common/locale/en-US/options.xml index d132dd66..db8b69fc 100644 --- a/common/locale/en-US/options.xml +++ b/common/locale/en-US/options.xml @@ -842,7 +842,7 @@

- Textarea mode can be entered with from Insert mode. + TextEdit mode can be entered with from Insert mode.

diff --git a/common/modules/util.jsm b/common/modules/util.jsm index 2db77281..0081bd82 100644 --- a/common/modules/util.jsm +++ b/common/modules/util.jsm @@ -57,6 +57,7 @@ const Util = Module("Util", { if (observers[target]) observers[target].call(obj, subject, data); }); + obj.observe.unRegister = function () register("removeObserver"); register("addObserver"); }, @@ -73,7 +74,7 @@ const Util = Module("Util", { if (services.get("threadManager").isMainThread) callback.call(self); else - mainThread.dispatch(Runnable(self, callback), mainThread.DISPATCH_NORMAL); + mainThread.dispatch(Runnable(self, callback, Array.slice(arguments, 2)), mainThread.DISPATCH_NORMAL); }, /** @@ -240,10 +241,10 @@ const Util = Module("Util", { * @param {number} frames The number of frames to print. */ dumpStack: function dumpStack(msg, frames) { - let stack = Error().stack.replace(/(?:.*\n){1}/, ""); + let stack = Error().stack.replace(/(?:.*\n){2}/, ""); if (frames != null) [stack] = stack.match(RegExp("(?:.*\n){0," + frames + "}")); - util.dump((msg || "Stack") + "\n" + stack + "\n"); + util.dump((arguments.length == 0 ? "Stack" : msg) + "\n" + stack + "\n"); }, editableInputs: set(["date", "datetime", "datetime-local", "email", "file", diff --git a/teledactyl/content/config.js b/teledactyl/content/config.js index e47b5815..f9e8821f 100644 --- a/teledactyl/content/config.js +++ b/teledactyl/content/config.js @@ -63,7 +63,7 @@ const Config = Module("config", ConfigBase, { // we switch to -- MESSAGE -- mode for Teledactyl when the main HTML widget gets focus if (win && win.document instanceof HTMLDocument || dactyl.focus instanceof HTMLAnchorElement) { if (config.isComposeWindow) - modes.set(modes.INSERT, modes.TEXTAREA); + modes.set(modes.INSERT, modes.TEXT_EDIT); else if (dactyl.mode != modes.MESSAGE) dactyl.mode = modes.MESSAGE; }