diff --git a/common/content/buffer.js b/common/content/buffer.js index 7903bc2c..ef322e21 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -1532,11 +1532,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 95388bac..c2ddf9bd 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -441,7 +441,10 @@ const CommandLine = Module("commandline", { */ open: function open(prompt, cmd, extendedMode) { modes.push(modes.COMMAND_LINE, this.currentExtendedMode, { - leave: commandline.closure.leave + leave: function (params) { + if (params.pop) + commandline.leave(); + } }); this.currentExtendedMode = extendedMode || null; @@ -689,13 +692,12 @@ const CommandLine = Module("commandline", { }; modes.push(modes.COMMAND_LINE, modes.PROMPT | extra.extended, { - leave: function (newMode) { - commandline.leave(newMode); + enter: function (stack) { extra.enter && extra.enter(stack); }, + leave: function (stack) { + commandline.leave(stack); if (extra.leave) - extra.leave(newMode); - }, - restore: function (newMode) { extra.restore && extra.restore(newMode) }, - save: function (newMode) { extra.save && extra.save(newMode) } + extra.leave(stack); + } }); this.currentExtendedMode = modes.PROMPT; diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 73853241..1ea54a8d 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..02140809 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 isTextArea() modes.getStack(1).main === modes.TEXTAREA, + 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.TEXTAREA); break; case "c": this.executeCommand("cmd_delete", 1); - modes.set(modes.INSERT, modes.TEXTAREA); + modes.pop(modes.TEXTAREA); + modes.push(modes.INSERT); break; case "y": this.executeCommand("cmd_copy", 1); - this.unselectText(); + modes.pop(modes.TEXTAREA); break; default: @@ -457,19 +442,37 @@ const Editor = Module("editor", { 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,15 +482,15 @@ const Editor = Module("editor", { count = 1; let controller = buffer.selectionController; - while (count--) { - if (modes.extended & modes.TEXTAREA) { + while (count-- && modes.main == modes.VISUAL) { + if (editor.isTextArea) { if (typeof visualTextareaCommand == "function") visualTextareaCommand(); else editor.executeCommand(visualTextareaCommand); } else - controller[caretModeMethod](caretModeArg, true); + caretExecute(true, true); } }, extraInfo); @@ -508,7 +511,7 @@ const Editor = Module("editor", { function (count) { commands.forEach(function (cmd) editor.executeCommand(cmd, 1)); - modes.set(modes.INSERT, modes.TEXTAREA); + modes.push(modes.INSERT); }); } @@ -608,7 +611,7 @@ const Editor = Module("editor", { mappings.add([modes.INSERT], [""], "Edit text field in Vi mode", - function () { dactyl.mode = modes.TEXTAREA; }); + function () { modes.push(modes.TEXTAREA); }); mappings.add([modes.INSERT], ["", ""], "Expand insert mode abbreviation", @@ -628,7 +631,7 @@ const Editor = Module("editor", { ["u"], "Undo", function (count) { editor.executeCommand("cmd_undo", count); - dactyl.mode = modes.TEXTAREA; + editor.unselectText(); }, { count: true }); @@ -636,7 +639,7 @@ const Editor = Module("editor", { [""], "Redo", function (count) { editor.executeCommand("cmd_redo", count); - dactyl.mode = modes.TEXTAREA; + editor.unselectText(); }, { count: true }); @@ -648,7 +651,7 @@ const Editor = Module("editor", { ["o"], "Open line below current", function (count) { editor.executeCommand("cmd_endLine", 1); - modes.set(modes.INSERT, modes.TEXTAREA); + modes.push(modes.INSERT); events.feedkeys(""); }); @@ -656,7 +659,7 @@ const Editor = Module("editor", { ["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); }); @@ -674,7 +677,7 @@ const Editor = Module("editor", { // visual mode mappings.add([modes.CARET, modes.TEXTAREA], ["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", @@ -683,7 +686,7 @@ const Editor = Module("editor", { mappings.add([modes.TEXTAREA], ["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 +694,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.isTextArea); editor.executeCommand("cmd_cut"); - modes.set(modes.INSERT, modes.TEXTAREA); + modes.replace(modes.VISUAL); }); mappings.add([modes.VISUAL], ["d"], "Delete selected text", function (count) { - if (modes.extended & modes.TEXTAREA) { + if (editor.isTextArea) { editor.executeCommand("cmd_cut"); - modes.set(modes.TEXTAREA); + modes.pop(); } else dactyl.beep(); @@ -710,9 +713,9 @@ const Editor = Module("editor", { mappings.add([modes.VISUAL], ["y"], "Yank selected text", function (count) { - if (modes.extended & modes.TEXTAREA) { + if (editor.isTextArea) { editor.executeCommand("cmd_copy"); - modes.set(modes.TEXTAREA); + modes.pop(); } else dactyl.clipboardWrite(buffer.getCurrentWord(), true); @@ -721,12 +724,12 @@ const Editor = Module("editor", { mappings.add([modes.VISUAL, modes.TEXTAREA], ["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.TEXTAREA); }); // finding characters @@ -786,7 +789,7 @@ const Editor = Module("editor", { text.substring(pos + 1); editor.moveToPosition(pos + 1, true, false); } - modes.set(modes.TEXTAREA); + modes.pop(modes.TEXTAREA); }, { count: true }); }, diff --git a/common/content/events.js b/common/content/events.js index 26607990..05cdd7bf 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -76,11 +76,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 +90,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 +106,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 +115,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); @@ -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(Events.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.TEXTAREA: + 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.TEXTAREA); + 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.main; 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).main; } // 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 = ""; @@ -862,7 +816,7 @@ 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)) { + if (mode == modes.COMMAND_LINE && (modes.extended & modes.OUTPUT_MULTILINE)) { commandline.onMultilineOutputEvent(event); return killEvent(); } @@ -871,16 +825,16 @@ const Events = Module("events", { // 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)) { this._input.buffer = ""; return null; } // TODO: handle middle click in content area - if (!isEscapeKey(key)) { + if (!isEscape(key)) { // custom mode... - if (dactyl.mode == modes.CUSTOM) { + if (mode == modes.CUSTOM) { plugins.onEvent(event); return killEvent(); } @@ -910,15 +864,17 @@ const Events = Module("events", { // 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 == modes.CUSTOM) return null; + let mainMode = modes.getMode(mode); + 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, candidateCommand); - let candidates = mappings.getCandidates(dactyl.mode, candidateCommand); + let candidates = mappings.getCandidates(mode, candidateCommand); if (candidates.length == 0 && !map) { map = this._input.pendingMap; this._input.pendingMap = null; @@ -929,7 +885,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 (!mainMode.count || mainMode.input) stop = false; else this._input.buffer = inputStr; @@ -938,7 +894,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 +913,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 +930,14 @@ const Events = Module("events", { stop = false; } } - else if (mappings.getCandidates(dactyl.mode, candidateCommand).length > 0 && !event.skipmap) { + else if (mappings.getCandidates(mode, 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 & (modes.INSERT | modes.COMMAND_LINE | modes.TEXTAREA))) events.feedkeys(this._input.buffer, { noremap: true, skipmap: true }); this._input.buffer = ""; @@ -989,17 +945,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 == modes.TEXTAREA); - if (dactyl.mode == modes.COMMAND_LINE) { + if (mode == 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 (!mainMode.input) dactyl.beep(); } } @@ -1019,9 +975,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,20 +1007,18 @@ 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.TEXTAREA && !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 @@ -1144,11 +1097,11 @@ const Events = Module("events", { 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/modes.js b/common/content/modes.js index 5b963da4..53b4e8b6 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("TEXTAREA", { char: "i", 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) @@ -158,7 +194,7 @@ 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(); @@ -166,9 +202,8 @@ const Modes = Module("modes", { 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); - + 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); @@ -189,8 +224,12 @@ const Modes = Module("modes", { this._extended = this.NONE; } + let prev = stack && stack.pop || this.topOfStack; if (push) this._modeStack.push(push); + 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); @@ -202,22 +241,31 @@ const Modes = Module("modes", { 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(); + if (a.params.leave) + a.params.leave({ pop: a }, this.topOfStack); - this.set(this.topOfStack.main, this.topOfStack.extended, this.topOfStack.params, { pop: a }); - if (this.topOfStack.params.restore) - this.topOfStack.params.restore(a); + this.set(this.topOfStack.main, this.topOfStack.extended, this.topOfStack.params, { pop: a }); - for (let [k, { obj, prop, value }] in Iterator(this.topOfStack.saved)) - obj[prop] = value; + for (let [k, { obj, prop, value }] in Iterator(this.topOfStack.saved)) + obj[prop] = value; + + if (mode == null) + return; + } + }, + + replace: function (mode, oldMode) { + // TODO: This should really be done in one step. + this.pop(oldMode); + 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 +277,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 +289,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<