diff options
author | Kris Maglione <maglione.k@gmail.com> | 2011-01-26 01:24:54 -0500 |
---|---|---|
committer | Kris Maglione <maglione.k@gmail.com> | 2011-01-26 01:24:54 -0500 |
commit | c284e1ced8df668592c22969dfd3338fcbc1d66f (patch) | |
tree | 6da2e76bb73efd16e4dddbab3e1a6993c192c24e /common/content | |
parent | a66864d0774a081e11446fb0ffffb4246a228617 (diff) | |
download | pentadactyl-c284e1ced8df668592c22969dfd3338fcbc1d66f.tar.gz |
First work towards cleaning up the commandline.js rat's nest. Don't expect any of these new interfaces to stay remotely as they are.
--HG--
branch : key-processing
Diffstat (limited to 'common/content')
-rw-r--r-- | common/content/bookmarks.js | 10 | ||||
-rw-r--r-- | common/content/browser.js | 12 | ||||
-rw-r--r-- | common/content/buffer.js | 58 | ||||
-rw-r--r-- | common/content/commandline.js | 950 | ||||
-rw-r--r-- | common/content/dactyl.js | 6 | ||||
-rw-r--r-- | common/content/events.js | 17 | ||||
-rw-r--r-- | common/content/hints.js | 87 | ||||
-rw-r--r-- | common/content/mappings.js | 2 | ||||
-rw-r--r-- | common/content/modes.js | 46 | ||||
-rw-r--r-- | common/content/mow.js | 352 | ||||
-rw-r--r-- | common/content/tabs.js | 2 |
11 files changed, 771 insertions, 771 deletions
diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js index 8b741d9a..fb936e3b 100644 --- a/common/content/bookmarks.js +++ b/common/content/bookmarks.js @@ -115,9 +115,8 @@ var Bookmarks = Module("bookmarks", { if (charset != null && charset !== "UTF-8") options["-charset"] = charset; - commandline.open(":", - commands.commandToString({ command: "bmark", options: options, arguments: [url] }) + " -keyword ", - modes.EX); + CommandExMode().open( + commands.commandToString({ command: "bmark", options: options, arguments: [url] }) + " -keyword "); }, /** @@ -582,9 +581,8 @@ var Bookmarks = Module("bookmarks", { options["-charset"] = content.document.characterSet; } - commandline.open(":", - commands.commandToString({ command: "bmark", options: options, arguments: [buffer.uri.spec] }), - modes.EX); + CommandExMode().open( + commands.commandToString({ command: "bmark", options: options, arguments: [buffer.uri.spec] })); }); mappings.add(myModes, ["A"], diff --git a/common/content/browser.js b/common/content/browser.js index 1be4fcfb..fd00987e 100644 --- a/common/content/browser.js +++ b/common/content/browser.js @@ -71,27 +71,27 @@ var Browser = Module("browser", { // opening websites mappings.add([modes.NORMAL], ["o"], "Open one or more URLs", - function () { commandline.open(":", "open ", modes.EX); }); + function () { CommandExMode().open("open "); }); mappings.add([modes.NORMAL], ["O"], "Open one or more URLs, based on current location", - function () { commandline.open(":", "open " + buffer.uri.spec, modes.EX); }); + function () { CommandExMode().open("open " + buffer.uri.spec); }); mappings.add([modes.NORMAL], ["t"], "Open one or more URLs in a new tab", - function () { commandline.open(":", "tabopen ", modes.EX); }); + function () { CommandExMode().open("tabopen "); }); mappings.add([modes.NORMAL], ["T"], "Open one or more URLs in a new tab, based on current location", - function () { commandline.open(":", "tabopen " + buffer.uri.spec, modes.EX); }); + function () { CommandExMode().open("tabopen " + buffer.uri.spec); }); mappings.add([modes.NORMAL], ["w"], "Open one or more URLs in a new window", - function () { commandline.open(":", "winopen ", modes.EX); }); + function () { CommandExMode().open("winopen "); }); mappings.add([modes.NORMAL], ["W"], "Open one or more URLs in a new window, based on current location", - function () { commandline.open(":", "winopen " + buffer.uri.spec, modes.EX); }); + function () { CommandExMode().open("winopen " + buffer.uri.spec); }); mappings.add([modes.NORMAL], ["<C-a>"], "Increment last number in URL", diff --git a/common/content/buffer.js b/common/content/buffer.js index 999e1710..1bbaab99 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -766,25 +766,25 @@ var Buffer = Module("buffer", { try { window.urlSecurityCheck(uri.spec, doc.nodePrincipal); - commandline.input("Save link: ", function (path) { - let file = io.File(path); - if (file.exists() && file.isDirectory()) - file.append(buffer.getDefaultNames(elem)[0][0]); + io.CommandFileMode("Save link: ", { + onSubmit: function (path) { + let file = io.File(path); + if (file.exists() && file.isDirectory()) + file.append(buffer.getDefaultNames(elem)[0][0]); - try { - if (!file.exists()) - file.create(File.NORMAL_FILE_TYPE, octal(644)); - } - catch (e) { - util.assert(false, "Invalid destination: " + e.name); - } + try { + if (!file.exists()) + file.create(File.NORMAL_FILE_TYPE, octal(644)); + } + catch (e) { + util.assert(false, "Invalid destination: " + e.name); + } - buffer.saveURI(uri, file); - }, { - autocomplete: true, - completer: function (context) completion.savePage(context, elem), - history: "file" - }); + buffer.saveURI(uri, file); + }, + + completer: function (context) completion.savePage(context, elem) + }).open(); } catch (e) { dactyl.echoerr(e); @@ -1360,17 +1360,15 @@ var Buffer = Module("buffer", { }, openUploadPrompt: function openUploadPrompt(elem) { - commandline.input("Upload file: ", function (path) { - let file = io.File(path); - dactyl.assert(file.exists()); - - elem.value = file.path; - events.dispatch(elem, events.create(elem.ownerDocument, "change", {})); - }, { - completer: function (context) completion.file(context), - default: elem.value, - history: "file" - }); + io.CommandFileMode("Upload file: ", { + onSubmit: function (path) { + let file = io.File(path); + dactyl.assert(file.exists()); + + elem.value = file.path; + events.dispatch(elem, events.create(elem.ownerDocument, "change", {})); + } + }).open(elem.value); } }, { commands: function () { @@ -1530,7 +1528,8 @@ var Buffer = Module("buffer", { if (/^>>/.test(context.filter)) context.advance(/^>>\s*/.exec(context.filter)[0].length); - return completion.savePage(context, content.document); + completion.savePage(context, content.document); + context.fork("file", 0, completion, "file"); }, literal: 0 }); @@ -1642,7 +1641,6 @@ var Buffer = Module("buffer", { this, function (context) { context.completions = buffer.getDefaultNames(node); }); - return context.fork("files", 0, completion, "file"); }; }, events: function () { diff --git a/common/content/commandline.js b/common/content/commandline.js index 8b3bae08..c78dd142 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -20,39 +20,12 @@ var CommandWidgets = Class("CommandWidgets", { eventTarget: commandline }, append: <e4x xmlns={XUL} xmlns:dactyl={NS}> - <window id={document.documentElement.id}> - <popupset> - <menupopup id="dactyl-contextmenu" - onpopupshowing="return (event.target != this || dactyl.modules.commandline.onContext(event));"> - <menuitem id="dactyl-context-copylink" - label="Copy Link Location" dactyl:group="link" - oncommand="goDoCommand('cmd_copyLink');"/> - <menuitem id="dactyl-context-copypath" - label="Copy File Path" dactyl:group="link path" - oncommand="dactyl.clipboardWrite(document.popupNode.getAttribute('path'));"/> - <menuitem id="dactyl-context-copy" - label="Copy" dactyl:group="selection" - command="cmd_copy"/> - <menuitem id="dactyl-context-selectall" - label="Select All" - command="cmd_selectAll"/> - </menupopup> - </popupset> - </window> - <vbox id={config.commandContainer}> - - <vbox class="dactyl-container" id="dactyl-multiline-output-container" hidden="false" collapsed="true"> - <iframe id="dactyl-multiline-output" src="dactyl://content/buffer.xhtml" - flex="1" hidden="false" collapsed="false" contextmenu="dactyl-contextmenu" - highlight="Events" events="multilineOutputEvents" /> - </vbox> - <vbox class="dactyl-container" hidden="false" collapsed="true"> <iframe class="dactyl-completions" id="dactyl-completions-dactyl-commandline" src="dactyl://content/buffer.xhtml" contextmenu="dactyl-contextmenu" flex="1" hidden="false" collapsed="false" - highlight="Events" events="multilineOutputEvents" /> + highlight="Events" events="mowEvents" /> </vbox> <stack orient="horizontal" align="stretch" class="dactyl-container" id="dactyl-container" highlight="CmdLine CmdCmdLine"> @@ -69,7 +42,7 @@ var CommandWidgets = Class("CommandWidgets", { <vbox class="dactyl-container" hidden="false" collapsed="false" highlight="CmdLine"> <textbox id="dactyl-multiline-input" class="plain" flex="1" rows="1" hidden="false" collapsed="true" multiline="true" - highlight="Normal Events" events="multilineInputEvents" /> + highlight="Normal Events" events="mowEvents" /> </vbox> </vbox> @@ -87,28 +60,32 @@ var CommandWidgets = Class("CommandWidgets", { <vbox id={"dactyl-completions-" + s + "commandline-container"} class="dactyl-container" hidden="false" collapsed="true"> <iframe class="dactyl-completions" id={"dactyl-completions-" + s + "commandline"} src="dactyl://content/buffer.xhtml" contextmenu="dactyl-contextmenu" flex="1" hidden="false" collapsed="false" - highlight="Events" events="multilineOutputEvents" /> + highlight="Events" events="mowEvents" /> </vbox> </toolbar> </e4x>.elements() }); this.elements = {}; + this.addElement({ name: "container", noValue: true }); + this.addElement({ name: "commandline", getGroup: function () options.get("guioptions").has("C") ? this.commandbar : this.statusbar, getValue: function () this.command }); + this.addElement({ name: "strut", defaultGroup: "Normal", getGroup: function () this.commandbar, getValue: function () options.get("guioptions").has("c") }); + this.addElement({ name: "command", id: "commandline-command", @@ -125,24 +102,26 @@ var CommandWidgets = Class("CommandWidgets", { getElement: CommandWidgets.getEditor, getGroup: function (value) this.activeGroup.commandline, onChange: function (elem) { - if (elem.inputField != dactyl.focusedElement) { + if (elem.inputField != dactyl.focusedElement) try { elem.selectionStart = elem.value.length; elem.selectionEnd = elem.value.length; } catch (e) {} - } + if (!elem.collapsed) dactyl.focus(elem); }, onVisibility: function (elem, visible) { visible && dactyl.focus(elem); } }); + this.addElement({ name: "prompt", id: "commandline-prompt", defaultGroup: "CmdPrompt", getGroup: function () this.activeGroup.commandline }); + this.addElement({ name: "message", defaultGroup: "Normal", @@ -157,20 +136,20 @@ var CommandWidgets = Class("CommandWidgets", { return this.activeGroup.mode; } }); + this.addElement({ name: "mode", defaultGroup: "ModeMsg", getGroup: function (value) { if (!options.get("guioptions").has("M")) if (this.commandbar.container.clientHeight == 0 || - value && !this.commandbar.commandline.collapsed) + value && !this.commandbar.commandline.collapsed) return this.statusbar; return this.commandbar; } }); let fontSize = util.computedStyle(document.documentElement).fontSize; - styles.registerSheet("resource://dactyl-skin/dactyl.css"); styles.system.add("font-size", "dactyl://content/buffer.xhtml", "body { font-size: " + fontSize + "; } \ html|html > xul|scrollbar { visibility: collapse !important; }", @@ -188,14 +167,17 @@ var CommandWidgets = Class("CommandWidgets", { memoize(this.statusbar, obj.name, function () get("dactyl-statusline-field-", statusline.widgets, (obj.id || obj.name))); memoize(this.commandbar, obj.name, function () get("dactyl-", {}, (obj.id || obj.name))); - if (!(obj.noValue || obj.getValue)) + if (!(obj.noValue || obj.getValue)) { Object.defineProperty(this, obj.name, Modes.boundProperty({ + get: function get_widgetValue() { let elem = self.getGroup(obj.name, obj.value)[obj.name]; if (obj.value != null) - return [obj.value[0], obj.get ? obj.get.call(this, elem) : elem.value]; + return [obj.value[0], + obj.get ? obj.get.call(this, elem) : elem.value]; return null; }, + set: function set_widgetValue(val) { if (val != null && !isArray(val)) val = [obj.defaultGroup || "", val]; @@ -219,19 +201,23 @@ var CommandWidgets = Class("CommandWidgets", { return val; } }).init(obj.name)); - else if (obj.defaultGroup) + } + else if (obj.defaultGroup) { [this.commandbar, this.statusbar].forEach(function (nodeSet) { let elem = nodeSet[obj.name]; if (elem) highlight.highlightNode(elem, obj.defaultGroup.split(/\s/) .map(function (g) g + " " + nodeSet.group + g).join(" ")); }); + } }, + getGroup: function getgroup(name, value) { if (!statusline.visible) return this.commandbar; return this.elements[name].getGroup.call(this, arguments.length > 1 ? value : this[name]); }, + updateVisibility: function updateVisibility() { for (let elem in values(this.elements)) if (elem.getGroup) { @@ -249,7 +235,10 @@ var CommandWidgets = Class("CommandWidgets", { } } } - // Hack. + + // Hack. Collapse hidden elements in the stack. + // Might possibly be better to use a deck and programmatically + // choose which element to select. function check(node) { if (util.computedStyle(node).display === "-moz-stack") { let nodes = Array.filter(node.children, function (n) !n.collapsed && n.boxObject.height); @@ -306,6 +295,163 @@ var CommandWidgets = Class("CommandWidgets", { } }); + +var CommandMode = Class("CommandMode", { + init: function init() { + this.keepCommand = userContext.hidden_option_command_afterimage; + + if (this.historyKey) + this.history = CommandLine.History(commandline.widgets.active.command.inputField, this.historyKey); + + if (this.complete) + this.completions = CommandLine.Completions(commandline.widgets.active.command.inputField); + + this.autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) { + if (!events.feedingKeys && this.completions && options["autocomplete"].length) { + this.completions.complete(true, false); + if (this.completions) + this.completions.itemList.visible = true; + } + }, this); + }, + + open: function (command) { + this.command = command; + + dactyl.assert(isinstance(this.mode, modes.COMMAND_LINE), + "Not opening command line in non-command-line mode."); + modes.push(this.mode, null, this.closure); + + this.widgets.active.commandline.collapsed = false; + this.widgets.prompt = this.prompt; + this.widgets.command = command || ""; + }, + + get mappingSelf() this, + + get widgets() commandline.widgets, + + enter: function (stack) { + commandline.commandSession = this; + if (this.command || stack.pop && commandline.command) { + this.onChange(commandline.command); + this.autocompleteTimer.flush(true); + } + }, + + leave: function (stack) { + this.autocompleteTimer.reset(); + + if (this.completions) { + this.completions.previewClear(); + this.completions.tabTimer.reset(); + } + + if (this.history) + this.history.save(); + + commandline.hideCompletions(); + this.resetCompletions(); + + if (!stack.push) { + modes.delay(function () { + if (!this.keepCommand || commandline.silent || commandline.quiet) + commandline.hide(); + this[this.accepted ? "onSubmit" : "onCancel"](commandline.command); + }, this); + commandline.commandSession = null; + } + }, + + events: { + input: function onInput(event) { + if (this.completions) { + this.resetCompletions(); + + this.autocompleteTimer.tell(false); + if (!this.completions.itemList.visible) + this.autocompleteTimer.flush(); + } + this.onChange(commandline.command); + }, + keyup: function onKeyUp(event) { + let key = events.toString(event); + if (/-?Tab>$/.test(key) && this.completions) + this.completions.tabTimer.flush(); + } + }, + + autocomplete: false, + keepCommand: false, + + onKeyPress: function onKeyPress(event) { + let key = events.toString(event); + if (this.completions) + this.completions.previewClear(); + + return true; /* Pass event */ + }, + + complete: function (context) { + }, + + onCancel: function (value) { + }, + + onChange: function (value) { + }, + + onSubmit: function (value) { + }, + + resetCompletions: function resetCompletions() { + if (this.completions) { + this.completions.context.cancelAll(); + this.completions.wildIndex = -1; + this.completions.previewClear(); + } + if (this.history) + this.history.reset(); + }, +}); + +var CommandExMode = Class("CommandExMode", CommandMode, { + + get mode() modes.EX, + + historyKey: "command", + + prompt: ["Normal", ":"], + + complete: function complete(context) { + context.fork("ex", 0, completion, "ex"); + }, + + onSubmit: function onSubmit(command) { + io.withSavedValues(["readHeredoc", "sourcing"], function () { + this.sourcing = { file: "[Command Line]", line: 1 }; + this.readHeredoc = commandline.readHeredoc; + commands.repeat = command; + dactyl.execute(command); + }); + } +}); + +var CommandPromptMode = Class("CommandPromptMode", CommandMode, { + init: function init(prompt, params) { + this.prompt = prompt; + update(this, params); + init.supercall(this); + }, + + complete: function () { + if (this.completer) + return this.completer.apply(this, arguments); + }, + + get mode() modes.PROMPT +}); + /** * This class is used for prompting of user input and echoing of messages. * @@ -364,117 +510,10 @@ var CommandLine = Module("commandline", { } }; //}}} - this._lastMowOutput = null; - this._silent = false; this._quiet = false; - this._keepCommand = false; this._lastEcho = null; - /////////////////////////////////////////////////////////////////////////////}}} - ////////////////////// TIMERS ////////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////{{{ - - this._autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) { - dactyl.trapErrors(function _autocompleteTell() { - if (!events.feedingKeys && self._completions && options["autocomplete"].length) { - self._completions.complete(true, false); - if (self._completions) - self._completions.itemList.show(); - } - }); - }); - - this._statusTimer = Timer(5, 100, function statusTell() { - if (self._completions == null || self._completions.selected == null) - statusline.progress = ""; - else - statusline.progress = "match " + (self._completions.selected + 1) + " of " + self._completions.items.length; - }); - - // This timer just prevents <Tab>s from queueing up when the - // system is under load (and, thus, giving us several minutes of - // the completion list scrolling). Multiple <Tab> presses are - // still processed normally, as the timer is flushed on "keyup". - this._tabTimer = Timer(0, 0, function tabTell(event) { - dactyl.trapErrors(function () { - if (self._completions) - self._completions.tab(event.shiftKey, event.altKey && options["altwildmode"]); - }); - }); - - this._timers = [this._autocompleteTimer, this._statusTimer, this._tabTimer]; - - /////////////////////////////////////////////////////////////////////////////}}} - ////////////////////// VARIABLES /////////////////////////////////////////////// - /////////////////////////////////////////////////////////////////////////////{{{ - - this.__defineGetter__("_completionList", function () { - let node = this.widgets.active.commandline; - if (!node._completionList) - this.widgets._whenReady.call(node, "_completionList", "dactyl-completions-" + node.id, - function (node) ItemList(node.id)); - return node._completionList; - }); - this._completions = null; - this._history = null; - - this._startHints = false; // whether we're waiting to start hints mode - this._lastSubstring = ""; - - // we need to save the mode which were in before opening the command line - // this is then used if we focus the command line again without the "official" - // way of calling "open" - this.currentExtendedMode = null; // the extended mode which we last opened the command line for - - this._input = {}; - - this.registerCallback("submit", modes.EX, function (command) { - io.withSavedValues(["readHeredoc", "sourcing"], function () { - this.sourcing = { file: "[Command Line]", line: 1 }; - this.readHeredoc = commandline.readHeredoc; - commands.repeat = command; - dactyl.execute(command); - }); - }); - - this.registerCallback("complete", modes.EX, function (context) { - context.fork("ex", 0, completion, "ex"); - }); - this.registerCallback("change", modes.EX, function (command, from) { - if (from !== "history") - self._autocompleteTimer.tell(false); - }); - - this.registerCallback("cancel", modes.PROMPT, cancelPrompt); - this.registerCallback("submit", modes.PROMPT, closePrompt); - this.registerCallback("change", modes.PROMPT, function (str) { - if (self._input.complete) - self._autocompleteTimer.tell(false); - if (self._input.change) - self._input.change.call(commandline, str); - }); - this.registerCallback("complete", modes.PROMPT, function (context) { - if (self._input.complete) - context.fork("input", 0, commandline, self._input.complete); - }); - - function cancelPrompt(value) { - let callback = self._input.cancel; - self._input = {}; - if (callback) - dactyl.trapErrors(callback, self, value != null ? value : commandline.command); - } - - function closePrompt(value) { - let callback = self._input.submit; - self._input = {}; - if (callback) - dactyl.trapErrors(callback, self, value != null ? value : commandline.command); - } - }, - cleanup: function cleanup() { - styles.unregisterSheet("resource://dactyl-skin/dactyl.css"); }, /** @@ -482,8 +521,7 @@ var CommandLine = Module("commandline", { * * @returns {boolean} */ - get commandVisible() modes.main == modes.COMMAND_LINE && - !(modes.extended & modes.INPUT_MULTILINE), + get commandVisible() !!this.commandSession, /** * Ensure that the multiline input widget is the correct size. @@ -513,13 +551,12 @@ var CommandLine = Module("commandline", { get completionContext() this._completions.context, - get mode() (modes.extended == modes.EX) ? "cmd" : "search", - get silent() this._silent, set silent(val) { this._silent = val; - this._quiet = this._quiet; + this.quiet = this.quiet; }, + get quiet() this._quiet, set quiet(val) { this._quiet = val; @@ -532,133 +569,36 @@ var CommandLine = Module("commandline", { widgets: Class.memoize(function () CommandWidgets()), - // @param type can be: - // "submit": when the user pressed enter in the command line - // "change" - // "cancel" - // "complete" - registerCallback: function registerCallback(type, mode, func) { - if (!(type in this._callbacks)) - this._callbacks[type] = {}; - this._callbacks[type][mode] = func; - }, - - triggerCallback: function triggerCallback(type, mode) { - if (this._callbacks[type] && this._callbacks[type][mode]) - try { - this._callbacks[type][mode].apply(this, Array.slice(arguments, 2)); - } - catch (e) { - dactyl.reportError(e, true); - } - }, - runSilently: function runSilently(func, self) { - this.withSavedValues(["_silent"], function () { - this._silent = true; + this.withSavedValues(["silent"], function () { + this.silent = true; func.call(self); }); }, + get completionList() { + let node = this.widgets.active.commandline; + if (!node.completionList) + this.widgets._whenReady.call(node, "completionList", "dactyl-completions-" + node.id, + function (node) ItemList(node.id)); + return node.completionList; + }, + hideCompletions: function hideCompletions() { for (let nodeSet in values([this.widgets.statusbar, this.widgets.commandbar])) - if (nodeSet.commandline._completionList) - nodeSet.commandline._completionList.hide(); + if (nodeSet.commandline.completionList) + nodeSet.commandline.completionList.visible = false; }, _multilineEnd: Modes.boundProperty(), _multilineCallback: Modes.boundProperty(), - currentExtendedMode: Modes.boundProperty(), - _completions: Modes.boundProperty(), - _history: Modes.boundProperty(), _lastClearable: Modes.boundProperty(), - _keepCommand: Modes.boundProperty(), messages: Modes.boundProperty(), multilineInputVisible: Modes.boundProperty({ set: function set_miwVisible(value) { this.widgets.multilineInput.collapsed = !value; } }), - multilineOutputVisible: Modes.boundProperty({ - set: function set_mowVisible(value) { - this.widgets.mowContainer.collapsed = !value; - let elem = this.widgets.multilineOutput; - if (!value && elem && elem.contentWindow == document.commandDispatcher.focusedWindow) - document.commandDispatcher.focusedWindow = content; - } - }), - - /** - * Open the command line. The main mode is set to COMMAND_LINE, the - * extended mode to *extendedMode*. Further, callbacks defined for - * *extendedMode* are triggered as appropriate - * (see {@link #registerCallback}). - * - * @param {string} prompt - * @param {string} cmd - * @param {number} extendedMode - */ - open: function open(prompt, cmd, extendedMode) { - this.widgets.message = null; - - this.currentExtendedMode = extendedMode || null; - modes.push(modes.COMMAND_LINE, this.currentExtendedMode, { - autocomplete: cmd.length, - onKeyPress: this.closure.onKeyPress, - history: (extendedMode || {}).params.history, - leave: function (params) { - if (params.pop) - commandline.leave(); - }, - keyModes: [this.currentExtendedMode] - }); - - this._keepCommand = false; - - this.widgets.active.commandline.collapsed = false; - this.widgets.prompt = prompt; - this.widgets.command = cmd || ""; - - this.enter(); - }, - - enter: function enter() { - let params = modes.getStack(0).params; - - if (params.history) - this._history = CommandLine.History(this.widgets.active.command.inputField, params.history); - this._completions = CommandLine.Completions(this.widgets.active.command.inputField); - - if (params.autocomplete) { - commandline.triggerCallback("change", this.currentExtendedMode, commandline.command); - this._autocompleteTimer.flush(); - } - }, - - /** - * Called when leaving a command-line mode. - */ - leave: function leave() { - commandline.triggerCallback("cancel", this.currentExtendedMode); - - this._timers.forEach(function (timer) timer.reset()); - if (this._completions) - this._completions.previewClear(); - if (this._history) - this._history.save(); - this.resetCompletions(); // cancels any asynchronous completion still going on, must be before we set completions = null - this.hideCompletions(); - this._completions = null; - this._history = null; - this._statusTimer.tell(); - - if (!this._keepCommand || this._silent || this._quiet) { - modes.delay(function () { - this.updateMorePrompt(); - this.hide(); - }, this); - } - }, get command() { if (this.commandVisible && this.widgets.command) @@ -670,18 +610,22 @@ var CommandLine = Module("commandline", { return this.widgets.command = val; return this._lastCommand = val; }, + get lastCommand() this._lastCommand || this.command, set lastCommand(val) { this._lastCommand = val }, clear: function clear() { if (this.widgets.message && this.widgets.message[1] === this._lastClearable) this.widgets.message = null; + if (modes.main != modes.COMMAND_LINE) this.widgets.command = null; + if (modes.main == modes.OUTPUT_MULTILINE && !mow.isScrollable(1)) modes.pop(); + if (modes.main != modes.OUTPUT_MULTILINE) - this.multilineOutputVisible = false; + mow.visible = false; }, /** @@ -726,89 +670,8 @@ var CommandLine = Module("commandline", { let field = this.widgets.active.message.inputField; if (field.value && !forceSingle && field.editor.rootElement.scrollWidth > field.scrollWidth) { this.widgets.message = null; - this._echoMultiline(<span highlight="Message">{str}</span>, highlightGroup, true); - } - }, - - /** - * Display a multi-line message. - * - * @param {string} data - * @param {string} highlightGroup - */ - _echoMultiline: function echoMultiline(data, highlightGroup, silent) { - let doc = this.widgets.multilineOutput.contentDocument; - let win = this.widgets.multilineOutput.contentWindow; - let elem = doc.documentElement; - let body = doc.body; - - this.widgets.message = null; - if (!this.commandVisible) - this.hide(); - - this._startHints = false; - if (modes.main != modes.OUTPUT_MULTILINE) { - modes.push(modes.OUTPUT_MULTILINE, null, { - onKeyPress: this.closure.onMOWKeyPress, - leave: this.closure(function leave(stack) { - if (stack.pop) - for (let message in values(this.messages)) - if (message.leave) - message.leave(stack); - }) - }); - this.messages = []; - } - - // If it's already XML, assume it knows what it's doing. - // Otherwise, white space is significant. - // The problem elsewhere is that E4X tends to insert new lines - // after interpolated data. - XML.ignoreWhitespace = XML.prettyPrinting = false; - - if (isObject(data)) { - this._lastMowOutput = null; - - var output = util.xmlToDom(<div class="ex-command-output" style="white-space: nowrap" highlight={highlightGroup}/>, doc); - data.document = doc; - output.appendChild(data.message); - - this.messages.push(data); - } - else { - let style = isString(data) ? "pre" : "nowrap"; - this._lastMowOutput = <div class="ex-command-output" style={"white-space: " + style} highlight={highlightGroup}>{data}</div>; - - var output = util.xmlToDom(this._lastMowOutput, doc); - } - - // FIXME: need to make sure an open MOW is closed when commands - // that don't generate output are executed - if (this.widgets.mowContainer.collapsed) { - elem.scrollTop = 0; - while (body.firstChild) - body.removeChild(body.firstChild); - } - - body.appendChild(output); - - let str = typeof data !== "xml" && data.message || data; - if (!silent) - dactyl.triggerObserver("echoMultiline", data, highlightGroup, output); - - commandline.updateOutputHeight(true); - - if (options["more"] && Buffer.isScrollable(elem, 1)) { - // start the last executed command's output at the top of the screen - let elements = doc.getElementsByClassName("ex-command-output"); - elements[elements.length - 1].scrollIntoView(true); + mow.echo(<span highlight="Message">{str}</span>, highlightGroup, true); } - else - elem.scrollTop = elem.scrollHeight; - - dactyl.focus(win); - - commandline.updateMorePrompt(); }, /** @@ -857,19 +720,19 @@ var CommandLine = Module("commandline", { let action = this._echoLine; if ((flags & this.FORCE_MULTILINE) || (/\n/.test(data) || !isString(data)) && !(flags & this.FORCE_SINGLELINE)) - action = this._echoMultiline; + action = mow.closure.echo; if (single) this._lastEcho = null; else { if (this.widgets.message && this.widgets.message[1] == this._lastEcho) - this._echoMultiline(<span highlight="Message">{this._lastEcho}</span>, - this.widgets.message[0], true); + mow.echo(<span highlight="Message">{this._lastEcho}</span>, + this.widgets.message[0], true); if (action === this._echoLine && !(flags & this.FORCE_MULTILINE) && !(dactyl.fullyInitialized && this.widgets.mowContainer.collapsed)) { highlightGroup += " Message"; - action = this._echoMultiline; + action = mow.closure.echo; } this._lastEcho = (action == this._echoLine) && data; } @@ -899,29 +762,7 @@ var CommandLine = Module("commandline", { input: function _input(prompt, callback, extra) { extra = extra || {}; - this._input = { - submit: callback || extra.onAccept, - change: extra.onChange, - complete: extra.completer, - cancel: extra.onCancel - }; - - modes.push(modes.COMMAND_LINE, modes.PROMPT | extra.extended, - update(Object.create(extra), { - onKeyPress: extra.onKeyPress || this.closure.onKeyPress, - leave: function leave(stack) { - commandline.leave(stack); - leave.supercall(extra, stack); - }, - keyModes: [extra.extended, modes.PROMPT] - })); - this.currentExtendedMode = modes.PROMPT; - - this.widgets.prompt = !prompt ? null : [extra.promptHighlight || "Question", prompt]; - this.widgets.command = extra.default || ""; - this.widgets.active.commandline.collapsed = false; - - this.enter(); + CommandPromptMode(prompt, extra).open(); }, readHeredoc: function readHeredoc(end) { @@ -959,56 +800,32 @@ var CommandLine = Module("commandline", { this.timeout(function () { dactyl.focus(this.widgets.multilineInput); }, 10); }, - onContext: function onContext(event) { - try { - let enabled = { - link: window.document.popupNode instanceof HTMLAnchorElement, - path: window.document.popupNode.hasAttribute("path"), - selection: !window.document.commandDispatcher.focusedWindow.getSelection().isCollapsed - }; - - for (let node in array.iterValues(event.target.children)) { - let group = node.getAttributeNS(NS, "group"); - node.hidden = group && !group.split(/\s+/).every(function (g) enabled[g]); - } - } - catch (e) { - util.reportError(e); + events: update( + iter(CommandMode.prototype.events).map( + function ([event, handler]) [ + event, function (event) { + if (this.commandSession) + handler.call(this.commandSession, event); + } + ]).toObject(), + { + blur: function onBlur(event) { + this.timeout(function () { + if (this.commandSession && event.originalTarget === this.widgets.active.command.inputField) + dactyl.focus(this.widgets.active.command.inputField); + }); + }, + focus: function onFocus(event) { + if (!this.commandSession + && event.originalTarget === this.widgets.active.command.inputField) { + event.target.blur(); + dactyl.beep(); + } + }, } - return true; - }, + ), - onKeyPress: function onKeyPress(event) { - let key = events.toString(event); - if (this._completions) - this._completions.previewClear(); - - return true; /* Pass event */ - }, - - events: { - blur: function onBlur(event) { - this.timeout(function () { - if (this.commandVisible && event.originalTarget == this.widgets.active.command.inputField) - dactyl.focus(this.widgets.active.command.inputField); - }); - }, - focus: function onFocus(event) { - if (!this.commandVisible && event.target == this.widgets.active.command.inputField) { - event.target.blur(); - dactyl.beep(); - } - }, - input: function onInput(event) { - this.resetCompletions(); - this.triggerCallback("change", this.currentExtendedMode, this.command); - }, - keyup: function onKeyUp(event) { - let key = events.toString(event); - if (/-?Tab>$/.test(key)) - this._tabTimer.flush(); - } - }, + get mowEvents() mow.events, /** * Multiline input events, they will come straight from @@ -1028,117 +845,7 @@ var CommandLine = Module("commandline", { } }, - multilineOutputEvents: { - click: function onClick(event) { - if (event.getPreventDefault()) - return; - - const openLink = function openLink(where) { - event.preventDefault(); - dactyl.open(event.target.href, where); - } - - if (event.target instanceof HTMLAnchorElement) - switch (events.toString(event)) { - case "<LeftMouse>": - openLink(dactyl.CURRENT_TAB); - break; - case "<MiddleMouse>": - case "<C-LeftMouse>": - case "<C-M-LeftMouse>": - openLink({ where: dactyl.NEW_TAB, background: true }); - break; - case "<S-MiddleMouse>": - case "<C-S-LeftMouse>": - case "<C-M-S-LeftMouse>": - openLink({ where: dactyl.NEW_TAB, background: false }); - break; - case "<S-LeftMouse>": - openLink(dactyl.NEW_WINDOW); - break; - } - }, - unload: function onUnload(event) { - event.preventDefault(); - } - }, - - onMOWKeyPress: function onMOWKeyPress(event) { - const KILL = false, PASS = true; - - if (options["more"] && mow.isScrollable(1)) - commandline.updateMorePrompt(false, true); - else { - modes.pop(); - events.feedkeys(events.toString(event)); - return KILL; - } - return PASS; - }, - - getSpaceNeeded: function getSpaceNeeded() { - let rect = this.widgets.commandbar.commandline.getBoundingClientRect(); - let offset = rect.bottom - window.innerHeight; - return Math.max(0, offset); - }, - - /** - * Update or remove the multi-line output widget's "MORE" prompt. - * - * @param {boolean} force If true, "-- More --" is shown even if we're - * at the end of the output. - * @param {boolean} showHelp When true, show the valid key sequences - * and what they do. - */ - updateMorePrompt: function updateMorePrompt(force, showHelp) { - if (this.widgets.mowContainer.collapsed) - return this.widgets.message = null; - let elem = this.widgets.multilineOutput.contentDocument.documentElement; - - if (showHelp) - this.widgets.message = ["MoreMsg", "-- More -- SPACE/<C-f>/j: screen/page/line down, <C-b>/<C-u>/k: up, q: quit"]; - else if (force || (options["more"] && Buffer.isScrollable(elem, 1))) - this.widgets.message = ["MoreMsg", "-- More --"]; - else - this.widgets.message = ["Question", "Press ENTER or type command to continue"]; - }, - - /** - * Changes the height of the message window to fit in the available space. - * - * @param {boolean} open If true, the widget will be opened if it's not - * already so. - */ - updateOutputHeight: function updateOutputHeight(open, extra) { - if (!open && this.widgets.mowContainer.collapsed) - return; - - let doc = this.widgets.multilineOutput.contentDocument; - - let availableHeight = config.outputHeight; - if (!this.widgets.mowContainer.collapsed) - availableHeight += parseFloat(this.widgets.mowContainer.height); - availableHeight -= extra || 0; - - doc.body.style.minWidth = this.widgets.commandbar.commandline.scrollWidth + "px"; - this.widgets.mowContainer.height = Math.min(doc.body.clientHeight, availableHeight) + "px"; - this.timeout(function () - this.widgets.mowContainer.height = Math.min(doc.body.clientHeight, availableHeight) + "px", - 0); - - doc.body.style.minWidth = ""; - this.multilineOutputVisible = true; - }, - - resetCompletions: function resetCompletions() { - if (this._completions) { - this._completions.context.cancelAll(); - this._completions.wildIndex = -1; - this._completions.previewClear(); - } - if (this._history) - this._history.reset(); - }, + updateOutputHeight: deprecated("mow.resize", function updateOutputHeight(open, extra) mow.resize(open, extra)), withOutputToString: function withOutputToString(fn, self) { dactyl.registerObserver("echoLine", observe, true); @@ -1213,7 +920,7 @@ var CommandLine = Module("commandline", { replace: function replace(val) { delete this.input.dactylKeyPress; this.input.value = val; - commandline.triggerCallback("change", commandline.currentExtendedMode, val, "history"); + commandline.commandSession.onChange(val, "history"); }, /** @@ -1281,8 +988,12 @@ var CommandLine = Module("commandline", { this.selected = null; this.wildmode = options.get("wildmode"); this.wildtypes = this.wildmode.value; - this.itemList = commandline._completionList; + this.itemList = commandline.completionList; this.itemList.setItems(this.context); + + this.tabTimer = Timer(0, 0, function tabTell(event) { + this.tab(event.shiftKey, event.altKey && options["altwildmode"]); + }, this); }, UP: {}, @@ -1291,6 +1002,8 @@ var CommandLine = Module("commandline", { PAGE_DOWN: {}, RESET: null, + lastSubstring: "", + get completion() { let str = commandline.command; return str.substring(this.prefix.length, str.length - this.suffix.length); @@ -1329,7 +1042,7 @@ var CommandLine = Module("commandline", { complete: function complete(show, tabPressed) { this.context.reset(); this.context.tabPressed = tabPressed; - commandline.triggerCallback("complete", commandline.currentExtendedMode, this.context); + commandline.commandSession.complete(this.context); this.context.updateAsync = true; this.reset(show, tabPressed); this.wildIndex = 0; @@ -1362,10 +1075,10 @@ var CommandLine = Module("commandline", { } // Don't show 1-character substrings unless we've just hit backspace - if (substring.length < 2 && (!this._lastSubstring || this._lastSubstring.indexOf(substring) != 0)) + if (substring.length < 2 && this.lastSubstring.indexOf(substring) !== 0) return; - this._lastSubstring = substring; + this.lastSubstring = substring; let value = this.completion; if (util.compareIgnoreCase(value, substring.substr(0, value.length))) @@ -1495,7 +1208,8 @@ var CommandLine = Module("commandline", { tabs: [], tab: function tab(reverse, wildmode) { - commandline._autocompleteTimer.flush(); + commandline.commandSession.autocompleteTimer.flush(); + if (this._caret != this.caret) this.reset(); this._caret = this.caret; @@ -1529,12 +1243,15 @@ var CommandLine = Module("commandline", { } if (this.haveType("list")) - this.itemList.show(); + this.itemList.visible = true; this.wildIndex++; this.preview(); - commandline._statusTimer.tell(); + if (this.selected == null) + statusline.progress = ""; + else + statusline.progress = "match " + (this.selected + 1) + " of " + this.items.length; } if (this.items.length == 0) @@ -1623,19 +1340,30 @@ var CommandLine = Module("commandline", { subCommand: 0 }); }, + modes: function () { + modes.addMode("COMMAND_LINE", { + char: "c", + description: "Active when the command line is focused", + input: true, + get mappingSelf() commandline.commandSession + }); + // this._extended modes, can include multiple modes, and even main modes + modes.addMode("EX", { + description: "Ex command mode, active when the command line is open for Ex commands", + bases: [modes.COMMAND_LINE], + input: true + }); + modes.addMode("PROMPT", { + description: "Active when a prompt is open in the command line", + bases: [modes.COMMAND_LINE], + input: true + }); + }, mappings: function init_mappings() { mappings.add([modes.COMMAND], [":"], "Enter command-line mode", - function () { commandline.open(":", "", modes.EX); }); - - mappings.add([modes.COMMAND], - ["g<lt>"], "Redisplay the last command output", - function () { - dactyl.assert(commandline._lastMowOutput, "No previous command output"); - commandline._echoMultiline(commandline._lastMowOutput, commandline.HL_NORMAL); - }); - + function () { CommandExMode().open(""); }); mappings.add([modes.INPUT_MULTILINE], ["<Return>", "<C-j>", "<C-m>"], "Begin a new line", @@ -1666,23 +1394,18 @@ var CommandLine = Module("commandline", { // be two lists of the same characters (one here and a regexp in // mappings.js) bind(["<Space>", '"', "'"], "Expand command line abbreviation", - function () { - commandline.resetCompletions(); + function ({ self }) { + self.resetCompletions(); editor.expandAbbreviation(modes.COMMAND_LINE); return Events.PASS; }); bind(["<Return>", "<C-j>", "<C-m>"], "Accept the current input", - function (args) { - - commandline._keepCommand = userContext.hidden_option_command_afterimage; - - let mode = commandline.currentExtendedMode; + function ({ self }) { let command = commandline.command; - commandline.currentExtendedMode = null; // Don't let modes.pop trigger "cancel" - modes.pop(); - return function () commandline.triggerCallback("submit", mode, command); + self.accepted = true; + modes.pop(); }); [ @@ -1692,17 +1415,23 @@ var CommandLine = Module("commandline", { [["<S-Down>", "<C-n>", "<PageDown>"], "next", false, false] ].forEach(function ([keys, desc, up, search]) { bind(keys, "Recall the " + desc + " command line from the history list", - function (args) { - dactyl.assert(commandline._history); - commandline._history.select(up, search); + function ({ self }) { + dactyl.assert(self.history); + self.history.select(up, search); }); }); bind(["<A-Tab>", "<Tab>"], "Select the next matching completion item", - function ({ events }) { commandline._tabTimer.tell(events[0]); }); + function ({ events, self }) { + dactyl.assert(self.completions); + self.completions.tabTimer.tell(events[0]); + }); bind(["<A-S-Tab>", "<S-Tab>"], "Select the previous matching completion item", - function ({ events }) { commandline._tabTimer.tell(events[0]); }); + function ({ events, self }) { + dactyl.assert(self.completions); + self.completions.tabTimer.tell(events[0]); + }); bind(["<BS>", "<C-h>"], "Delete the previous character", function () { @@ -1714,86 +1443,6 @@ var CommandLine = Module("commandline", { bind(["<C-]>", "<C-5>"], "Expand command line abbreviation", function () { editor.expandAbbreviation(modes.COMMAND_LINE); }); - - let mow = modules.mow = { - __noSuchMethod__: function (meth, args) Buffer[meth].apply(Buffer, [this.body].concat(args)) - }; - memoize(mow, "body", function () commandline.widgets.multilineOutput.contentDocument.documentElement); - memoize(mow, "window", function () commandline.widgets.multilineOutput.contentWindow); - - const PASS = true; - const DROP = false; - const BEEP = {}; - - bind = function bind(keys, description, action, test, default_) { - mappings.add([modes.OUTPUT_MULTILINE], - keys, description, - function (command) { - if (!options["more"]) - var res = PASS; - else if (test && !test(command)) - res = default_; - else - res = action.call(command); - - if (res === PASS || res === DROP) - modes.pop(); - else - commandline.updateMorePrompt(); - if (res === BEEP) - dactyl.beep(); - else if (res === PASS) - events.feedkeys(command); - }); - } - - bind(["j", "<C-e>", "<Down>"], "Scroll down one line", - function () { mow.scrollVertical("lines", 1); }, - function () mow.isScrollable(1), BEEP); - - bind(["k", "<C-y>", "<Up>"], "Scroll up one line", - function () { mow.scrollVertical("lines", -1); }, - function () mow.isScrollable(-1), BEEP); - - bind(["<C-j>", "<C-m>", "<Return>"], "Scroll down one line, exit on last line", - function () { mow.scrollVertical("lines", 1); }, - function () mow.isScrollable(1), DROP); - - // half page down - bind(["<C-d>"], "Scroll down half a page", - function () { mow.scrollVertical("pages", .5); }, - function () mow.isScrollable(1), BEEP); - - bind(["<C-f>", "<PageDown>"], "Scroll down one page", - function () { mow.scrollVertical("pages", 1); }, - function () mow.isScrollable(1), BEEP); - - bind(["<Space>"], "Scroll down one page", - function () { mow.scrollVertical("pages", 1); }, - function () mow.isScrollable(1), DROP); - - bind(["<C-u>"], "Scroll up half a page", - function () { mow.scrollVertical("pages", -.5); }, - function () mow.isScrollable(-1), BEEP); - - bind(["<C-b>", "<PageUp>"], "Scroll up half a page", - function () { mow.scrollVertical("pages", -1); }, - function () mow.isScrollable(-1), BEEP); - - bind(["gg"], "Scroll to the beginning of output", - function () { mow.scrollToPercent(null, 0); }) - - bind(["G"], "Scroll to the end of output", - function () { mow.body.scrollTop = mow.body.scrollHeight; }) - - // copy text to clipboard - bind(["<C-y>"], "Yank selection to clipboard", - function () { dactyl.clipboardWrite(buffer.getCurrentWord(mow.window)); }); - - // close the window - bind(["q"], "Close the output window", - function () {}, - function () false, DROP); }, options: function init_options() { options.add(["history", "hi"], @@ -1811,10 +1460,6 @@ var CommandLine = Module("commandline", { "number", 100, { validator: function (value) value >= 0 }); - options.add(["more"], - "Pause the message list window when the full output will not fit on one page", - "boolean", true); - options.add(["showmode", "smd"], "Show the current mode in the command line", "boolean", true); @@ -1903,12 +1548,14 @@ var ItemList = Class("ItemList", { this._div.style.minWidth = ""; // FIXME: Belongs elsewhere. - commandline.updateOutputHeight(false, Math.max(0, this._minHeight - this._container.height)); + mow.resize(false, Math.max(0, this._minHeight - this._container.height)); this._container.height = this._minHeight; - this._container.height -= commandline.getSpaceNeeded(); - commandline.updateOutputHeight(false); - this.timeout(function () { this._container.height -= commandline.getSpaceNeeded(); }, 0); + this._container.height -= mow.spaceNeeded; + mow.resize(false); + this.timeout(function () { + this._container.height -= mow.spaceNeeded; + }); }, _getCompletion: function _getCompletion(index) this._completionElements.snapshotItem(index - this._startIndex), @@ -2037,9 +1684,8 @@ var ItemList = Class("ItemList", { }, clear: function clear() { this.setItems(); this._doc.body.innerHTML = ""; }, - hide: function hide() { this._container.collapsed = true; }, - show: function show() { this._container.collapsed = false; }, - visible: function visible() !this._container.collapsed, + get visible() !this._container.collapsed, + set visible(val) this._container.collapsed = !val, reset: function reset(brief) { this._startIndex = this._endIndex = this._selIndex = -1; @@ -2061,7 +1707,7 @@ var ItemList = Class("ItemList", { this.reset(true); if (typeof selectedItem == "number") { this.selectItem(selectedItem); - this.show(); + this.visible = true; } }, diff --git a/common/content/dactyl.js b/common/content/dactyl.js index 95b7c6f6..bb5aa362 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -40,11 +40,15 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { this.commands["dactyl.restart"] = function (event) { dactyl.restart(); }; + + styles.registerSheet("resource://dactyl-skin/dactyl.css"); }, cleanup: function () { delete window.dactyl; delete window.liberator; + + styles.unregisterSheet("resource://dactyl-skin/dactyl.css"); }, destroy: function () { @@ -1285,6 +1289,8 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { */ trapErrors: function trapErrors(func, self) { try { + if (isString(func)) + func = self[func]; return func.apply(self || this, Array.slice(arguments, 2)); } catch (e) { diff --git a/common/content/events.js b/common/content/events.js index c2d7ab91..b0cfecee 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -19,7 +19,7 @@ var ProcessorStack = Class("ProcessorStack", { .flatten().array; for (let [i, input] in Iterator(this.processors)) { - let params = input.main == mode.main ? mode.params : input.main.params; + let params = input.main.params; if (params.preExecute) input.preExecute = params.preExecute; if (params.postExecute) @@ -101,7 +101,8 @@ var ProcessorStack = Class("ProcessorStack", { result = res === Events.PASS ? Events.PASS : Events.KILL; } } - else if (result !== Events.KILL && processors.some(function (p) !p.main.passUnknown)) { + else if (result !== Events.KILL && !this.actions.length && + processors.some(function (p) !p.main.passUnknown)) { result = Events.KILL; dactyl.beep(); } @@ -157,12 +158,13 @@ var KeyProcessor = Class("KeyProcessor", { return this.onKeyPress(event); }, - execute: function execute(map) - let (self = this, args = arguments) + execute: function execute(map, args) + let (self = this) function execute() { if (self.preExecute) self.preExecute.apply(self, args); - let res = map.execute.apply(map, Array.slice(args, 1)); + let res = map.execute.call(map, update({ self: self.main.params.mappingSelf || self.main.mappingSelf || map }, + args)) if (self.postExecute) self.postExecute.apply(self, args); return res; @@ -1092,7 +1094,8 @@ var Events = Module("events", { let hives = mappings.hives.slice(event.noremap ? -1 : 0); - let keyModes = array([mode.params.keyModes, mode.main, mode.main.allBases]).flatten().compact(); + let main = { __proto__: mode.main, params: mode.params }; + let keyModes = array([mode.params.keyModes, main, mode.main.allBases]).flatten().compact(); this.processor = ProcessorStack(mode, hives, keyModes); } @@ -1175,7 +1178,7 @@ var Events = Module("events", { // Huh? --djk onFocusChange: function onFocusChange(event) { // command line has its own focus change handler - if (modes.main == modes.COMMAND_LINE) + if (modes.main.input) return; function hasHTMLDocument(win) win && win.document && win.document instanceof HTMLDocument diff --git a/common/content/hints.js b/common/content/hints.js index 9381de8d..f9bcae15 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -9,16 +9,18 @@ /** @scope modules */ /** @instance hints */ -var HintSession = Class("HintSession", { +var HintSession = Class("HintSession", CommandMode, { init: function init(mode, opts) { + init.supercall(this); + opts = opts || {}; // Hack. if (!opts.window && modes.main == modes.OUTPUT_MULTILINE) opts.window = commandline.widgets.multilineOutput.contentWindow; - this.mode = hints.modes[mode]; - dactyl.assert(this.mode); + this.hintMode = hints.modes[mode]; + dactyl.assert(this.hintMode); this.activeTimeout = null; // needed for hinttimeout > 0 this.continue = Boolean(opts.continue); @@ -31,8 +33,7 @@ var HintSession = Class("HintSession", { this.usedTabKey = false; this.validHints = []; // store the indices of the "hints" array with valid elements - commandline.input(UTF8(this.mode.prompt) + ": ", null, this.closure); - modes.extended = modes.HINTS; + this.open(); this.top = opts.window || content; this.top.addEventListener("resize", hints.resizeTimer.closure.tell, true); @@ -51,7 +52,22 @@ var HintSession = Class("HintSession", { this.checkUnique(); }, - get extended() modes.HINTS, + get mode() modes.HINTS, + + get prompt() ["Question", UTF8(this.hintMode.prompt) + ": "], + + leave: function leave(stack) { + leave.superapply(this, arguments); + + if (!stack.push) { + if (hints.hintSession == this) + hints.hintSession = null; + if (this.top) + this.top.removeEventListener("resize", hints.resizeTimer.closure.tell, true); + + this.removeHints(0); + } + }, checkUnique: function _checkUnique() { if (this.hintNumber == 0) @@ -236,7 +252,7 @@ var HintSession = Class("HintSession", { let baseNodeAbsolute = util.xmlToDom(<span highlight="Hint" style="display: none"/>, doc); - let mode = this.mode; + let mode = this.hintMode; let res = util.evaluateXPath(mode.xpath, doc, true); let start = this.pageHints.length; @@ -289,18 +305,6 @@ var HintSession = Class("HintSession", { return true; }, - leave: function leave(stack) { - if (!stack.push) { - if (hints.hintSession == this) - hints.hintSession = null; - this.continue = false; - if (this.top) - this.top.removeEventListener("resize", hints.resizeTimer.closure.tell, true); - - this.removeHints(0); - } - }, - /** * Handle user input. * @@ -429,7 +433,7 @@ var HintSession = Class("HintSession", { if ((modes.extended & modes.HINTS) && !this.continue) modes.pop(); commandline.lastEcho = null; // Hack. - dactyl.trapErrors(this.mode.action, this.mode, + dactyl.trapErrors("action", this.hintMode, elem, elem.href || elem.src || "", this.extendedhintCount, top); if (this.continue && this.top) @@ -662,6 +666,7 @@ var Hints = Module("hints", { if (modes.extended & modes.HINTS) modes.getStack(0).params.onResize(); }); + let appContent = document.getElementById("appcontent"); if (appContent) events.addSessionListener(appContent, "scroll", this.resizeTimer.closure.tell, false); @@ -681,9 +686,9 @@ var Hints = Module("hints", { this.addMode("t", "Follow hint in a new tab", function (elem) buffer.followLink(elem, dactyl.NEW_TAB)); this.addMode("b", "Follow hint in a background tab", function (elem) buffer.followLink(elem, dactyl.NEW_BACKGROUND_TAB)); this.addMode("w", "Follow hint in a new window", function (elem) buffer.followLink(elem, dactyl.NEW_WINDOW)); - this.addMode("O", "Generate an ‘:open URL’ prompt", function (elem, loc) commandline.open(":", "open " + loc, modes.EX)); - this.addMode("T", "Generate a ‘:tabopen URL’ prompt", function (elem, loc) commandline.open(":", "tabopen " + loc, modes.EX)); - this.addMode("W", "Generate a ‘:winopen URL’ prompt", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX)); + this.addMode("O", "Generate an ‘:open URL’ prompt", function (elem, loc) CommandExMode.open("open " + loc)); + this.addMode("T", "Generate a ‘:tabopen URL’ prompt", function (elem, loc) CommandExMode.open("tabopen " + loc)); + this.addMode("W", "Generate a ‘:winopen URL’ prompt", function (elem, loc) CommandExMode.open("winopen " + loc)); this.addMode("a", "Add a bookmark", function (elem) bookmarks.addSearchKeyword(elem)); this.addMode("S", "Add a search keyword", function (elem) bookmarks.addSearchKeyword(elem)); this.addMode("v", "View hint source", function (elem, loc) buffer.viewSource(loc, false)); @@ -952,20 +957,19 @@ var Hints = Module("hints", { open: function open(mode, opts) { this._extendedhintCount = opts.count; - commandline.input(mode, null, { + commandline.input(["Normal", mode], "", { completer: function (context) { context.compare = function () 0; context.completions = [[k, v.prompt] for ([k, v] in Iterator(hints.modes))]; }, - onAccept: function (arg) { + onSubmit: function (arg) { if (arg) - util.timeout(function () { - dactyl.trapErrors(hints.show, hints, arg, opts); - }); + hints.show(arg, opts); + }, + onChange: function () { + this.accepted = true; + modes.pop(); }, - get onCancel() this.onAccept, - onChange: function () { modes.pop(); }, - promptHighlight: "Normal" }); }, @@ -1075,6 +1079,15 @@ var Hints = Module("hints", { Mode: Struct("name", "prompt", "action", "tags", "filter") }, { + modes: function () { + modes.addMode("HINTS", { + extended: true, + description: "Active when selecting elements in QuickHint or ExtendedHint mode", + bases: [modes.COMMAND_LINE], + input: true, + ownsBuffer: true + }); + }, mappings: function () { var myModes = config.browserModes.concat(modes.OUTPUT_MULTILINE); mappings.add(myModes, ["f"], @@ -1097,25 +1110,23 @@ var Hints = Module("hints", { mappings.add(modes.HINTS, ["<Return>"], "Follow the selected hint", - function () { hints.hintSession.update(true); }); + function ({ self }) { self.update(true); }); mappings.add(modes.HINTS, ["<Tab>"], "Focus the next matching hint", - function () { hints.hintSession.tab(false); }); + function ({ self }) { self.tab(false); }); mappings.add(modes.HINTS, ["<S-Tab>"], "Focus the previous matching hint", - function () { hints.hintSession.tab(true); }); + function ({ self }) { self.tab(true); }); mappings.add(modes.HINTS, ["<BS>", "<C-h>"], "Delete the previous character", - function () hints.hintSession.backspace()); + function ({ self }) self.backspace()); mappings.add(modes.HINTS, ["<Leader>"], "Toggle hint filtering", - function () { - hints.hintSession.escapeNumbers = !hints.hintSession.escapeNumbers; - }); + function ({ self }) { self.escapeNumbers = !self.escapeNumbers; }); }, options: function () { const DEFAULT_HINTTAGS = diff --git a/common/content/mappings.js b/common/content/mappings.js index bb83522e..b5255d77 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -295,6 +295,8 @@ var Mappings = Module("mappings", { this.allHives = [this.user, this.builtin]; }, + repeat: Modes.boundProperty(), + hives: Class.memoize(function () array(this.allHives.filter(function (h) h.filter(buffer.uri)))), get userHives() this.allHives.filter(function (h) h !== this.builtin, this), diff --git a/common/content/modes.js b/common/content/modes.js index 8904aeec..fe9b0275 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -119,12 +119,6 @@ var Modes = Module("modes", { input: true, ownsFocus: true }); - this.addMode("COMMAND_LINE", { - char: "c", - description: "Active when the command line is focused", - input: true - }); - this.addMode("EMBED", { input: true, @@ -160,18 +154,6 @@ var Modes = Module("modes", { input: true }); - // this._extended modes, can include multiple modes, and even main modes - this.addMode("EX", { - extended: true, - description: "Ex command mode, active when the command line is open for Ex commands", - input: true - }, { history: "command" }); - this.addMode("HINTS", { - extended: true, - description: "Active when selecting elements in QuickHint or ExtendedHint mode", - count: false, - ownsBuffer: true - }); this.addMode("INPUT_MULTILINE", { extended: true, hidden: true, @@ -180,11 +162,6 @@ var Modes = Module("modes", { this.addMode("LINE", { extended: true, hidden: true }); - this.addMode("PROMPT", { - extended: true, - description: "Active when a prompt is open in the command line", - input: true - }); this.push(this.NORMAL, 0, { enter: function (stack, prev) { @@ -214,9 +191,9 @@ var Modes = Module("modes", { _getModeMessage: function () { // when recording a macro let macromode = ""; - if (modes.recording) + if (this.recording) macromode = "recording"; - else if (modes.replaying) + else if (this.replaying) macromode = "replaying"; let val = this._modeMap[this._main].display(); @@ -317,13 +294,15 @@ var Modes = Module("modes", { } if (stack && stack.pop && stack.pop.params.leave) - stack.pop.params.leave(stack, this.topOfStack); + dactyl.trapErrors("leave", stack.pop.params, + stack, this.topOfStack); let push = mainMode != null && !(stack && stack.pop) && Modes.StackElement(this._main, this._extended, params, {}); if (push && this.topOfStack) { if (this.topOfStack.params.leave) - this.topOfStack.params.leave({ push: push }, push); + dactyl.trapErrors("leave", this.topOfStack.params, + { push: push }, push); for (let [id, { obj, prop }] in Iterator(this.boundProperties)) { if (!obj.get()) delete this.boundProperties[id]; @@ -332,7 +311,7 @@ var Modes = Module("modes", { } } - this.delayed.forEach(function ([fn, self]) fn.call(self)); + let delayed = this.delayed; this.delayed = []; let prev = stack && stack.pop || this.topOfStack; @@ -343,9 +322,14 @@ var Modes = Module("modes", { for (let { obj, prop, value } in values(this.topOfStack.saved)) obj[prop] = value; + this.show(); + + delayed.forEach(function ([fn, self]) dactyl.trapErrors(fn, self)); + if (this.topOfStack.params.enter && prev) - this.topOfStack.params.enter(push ? { push: push } : stack || {}, - prev); + dactyl.trapErrors("enter", this.topOfStack.params, + push ? { push: push } : stack || {}, + prev); dactyl.triggerObserver("modeChange", [oldMain, oldExtended], [this._main, this._extended], stack); this.show(); @@ -439,7 +423,7 @@ var Modes = Module("modes", { input: false, - passUnknown: false, + get passUnknown() this.input, get mask() this, diff --git a/common/content/mow.js b/common/content/mow.js new file mode 100644 index 00000000..bccff26c --- /dev/null +++ b/common/content/mow.js @@ -0,0 +1,352 @@ +// Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org> +// Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com> +// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com> +// +// This work is licensed for reuse under an MIT license. Details are +// given in the LICENSE.txt file included with this file. + +var MOW = Module("mow", { + init: function () { + + XML.ignoreWhitespace = true; + util.overlayWindow(window, { + objects: { + eventTarget: this + }, + append: <e4x xmlns={XUL} xmlns:dactyl={NS}> + <window id={document.documentElement.id}> + <popupset> + <menupopup id="dactyl-contextmenu" highlight="Events" events="contextEvents"> + <menuitem id="dactyl-context-copylink" + label="Copy Link Location" dactyl:group="link" + oncommand="goDoCommand('cmd_copyLink');"/> + <menuitem id="dactyl-context-copypath" + label="Copy File Path" dactyl:group="link path" + oncommand="dactyl.clipboardWrite(document.popupNode.getAttribute('path'));"/> + <menuitem id="dactyl-context-copy" + label="Copy" dactyl:group="selection" + command="cmd_copy"/> + <menuitem id="dactyl-context-selectall" + label="Select All" + command="cmd_selectAll"/> + </menupopup> + </popupset> + </window> + <vbox id={config.commandContainer}> + <vbox class="dactyl-container" id="dactyl-multiline-output-container" hidden="false" collapsed="true"> + <iframe id="dactyl-multiline-output" src="dactyl://content/buffer.xhtml" + flex="1" hidden="false" collapsed="false" contextmenu="dactyl-contextmenu" + highlight="Events" /> + </vbox> + </vbox> + </e4x> + }); + }, + + __noSuchMethod__: function (meth, args) Buffer[meth].apply(Buffer, [this.body].concat(args)), + + get widget() this.widgets.multilineOutput, + widgets: Class.memoize(function () commandline.widgets), + + body: Class.memoize(function () this.widget.contentDocument.documentElement), + document: Class.memoize(function () this.widget.contentDocument), + window: Class.memoize(function () this.widget.contentWindow), + + /** + * Display a multi-line message. + * + * @param {string} data + * @param {string} highlightGroup + */ + echo: function echo(data, highlightGroup, silent) { + let body = this.document.body; + + this.widgets.message = null; + if (!commandline.commandVisible) + commandline.hide(); + + this._startHints = false; + if (modes.main != modes.OUTPUT_MULTILINE) { + modes.push(modes.OUTPUT_MULTILINE, null, { + onKeyPress: this.closure.onKeyPress, + leave: this.closure(function leave(stack) { + if (stack.pop) + for (let message in values(this.messages)) + if (message.leave) + message.leave(stack); + }) + }); + this.messages = []; + } + + // If it's already XML, assume it knows what it's doing. + // Otherwise, white space is significant. + // The problem elsewhere is that E4X tends to insert new lines + // after interpolated data. + XML.ignoreWhitespace = XML.prettyPrinting = false; + + if (isObject(data)) { + this.lastOutput = null; + + var output = util.xmlToDom(<div class="ex-command-output" style="white-space: nowrap" highlight={highlightGroup}/>, + this.document); + data.document = this.document; + output.appendChild(data.message); + + this.messages.push(data); + } + else { + let style = isString(data) ? "pre" : "nowrap"; + this.lastOutput = <div class="ex-command-output" style={"white-space: " + style} highlight={highlightGroup}>{data}</div>; + + var output = util.xmlToDom(this.lastOutput, this.document); + } + + // FIXME: need to make sure an open MOW is closed when commands + // that don't generate output are executed + if (this.widgets.mowContainer.collapsed) { + this.body.scrollTop = 0; + while (body.firstChild) + body.removeChild(body.firstChild); + } + + body.appendChild(output); + + let str = typeof data !== "xml" && data.message || data; + if (!silent) + dactyl.triggerObserver("echoMultiline", data, highlightGroup, output); + + this.resize(true); + + if (options["more"] && this.isScrollable(1)) { + // start the last executed command's output at the top of the screen + let elements = this.document.getElementsByClassName("ex-command-output"); + elements[elements.length - 1].scrollIntoView(true); + } + else + this.body.scrollTop = this.body.scrollHeight; + + dactyl.focus(this.window); + this.updateMorePrompt(); + }, + + events: { + click: function onClick(event) { + if (event.getPreventDefault()) + return; + + const openLink = function openLink(where) { + event.preventDefault(); + dactyl.open(event.target.href, where); + } + + if (event.target instanceof HTMLAnchorElement) + switch (events.toString(event)) { + case "<LeftMouse>": + openLink(dactyl.CURRENT_TAB); + break; + case "<MiddleMouse>": + case "<C-LeftMouse>": + case "<C-M-LeftMouse>": + openLink({ where: dactyl.NEW_TAB, background: true }); + break; + case "<S-MiddleMouse>": + case "<C-S-LeftMouse>": + case "<C-M-S-LeftMouse>": + openLink({ where: dactyl.NEW_TAB, background: false }); + break; + case "<S-LeftMouse>": + openLink(dactyl.NEW_WINDOW); + break; + } + }, + unload: function onUnload(event) { + event.preventDefault(); + } + }, + contextEvents: { + popupshowing: function (event) { + let enabled = { + link: window.document.popupNode instanceof HTMLAnchorElement, + path: window.document.popupNode.hasAttribute("path"), + selection: !window.document.commandDispatcher.focusedWindow.getSelection().isCollapsed + }; + + for (let node in array.iterValues(event.target.children)) { + let group = node.getAttributeNS(NS, "group"); + node.hidden = group && !group.split(/\s+/).every(function (g) enabled[g]); + } + } + }, + + onContext: function onContext(event) { + return true; + }, + + onKeyPress: function onKeyPress(event) { + const KILL = false, PASS = true; + + if (options["more"] && mow.isScrollable(1)) + commandline.updateMorePrompt(false, true); + else { + modes.pop(); + events.feedkeys(events.toString(event)); + return KILL; + } + return PASS; + }, + + /** + * Changes the height of the message window to fit in the available space. + * + * @param {boolean} open If true, the widget will be opened if it's not + * already so. + */ + resize: function updateOutputHeight(open, extra) { + if (!open && this.widgets.mowContainer.collapsed) + return; + + let doc = this.widget.contentDocument; + + let availableHeight = config.outputHeight; + if (!this.widgets.mowContainer.collapsed) + availableHeight += parseFloat(this.widgets.mowContainer.height); + availableHeight -= extra || 0; + + doc.body.style.minWidth = this.widgets.commandbar.commandline.scrollWidth + "px"; + this.widgets.mowContainer.height = Math.min(doc.body.clientHeight, availableHeight) + "px"; + this.timeout(function () + this.widgets.mowContainer.height = Math.min(doc.body.clientHeight, availableHeight) + "px", + 0); + + doc.body.style.minWidth = ""; + this.visible = true; + }, + + get spaceNeeded() { + let rect = this.widgets.commandbar.commandline.getBoundingClientRect(); + let offset = rect.bottom - window.innerHeight; + return Math.max(0, offset); + }, + + /** + * Update or remove the multi-line output widget's "MORE" prompt. + * + * @param {boolean} force If true, "-- More --" is shown even if we're + * at the end of the output. + * @param {boolean} showHelp When true, show the valid key sequences + * and what they do. + */ + updateMorePrompt: function updateMorePrompt(force, showHelp) { + if (this.widgets.mowContainer.collapsed) + return this.widgets.message = null; + let elem = this.widget.contentDocument.documentElement; + + if (showHelp) + this.widgets.message = ["MoreMsg", "-- More -- SPACE/<C-f>/j: screen/page/line down, <C-b>/<C-u>/k: up, q: quit"]; + else if (force || (options["more"] && Buffer.isScrollable(elem, 1))) + this.widgets.message = ["MoreMsg", "-- More --"]; + else + this.widgets.message = ["Question", "Press ENTER or type command to continue"]; + }, + + visible: Modes.boundProperty({ + set: function set_mowVisible(value) { + this.widgets.mowContainer.collapsed = !value; + + let elem = this.widget; + if (!value && elem && elem.contentWindow == document.commandDispatcher.focusedWindow) + document.commandDispatcher.focusedWindow = content; + } + }), + +}, { +}, { + mappings: function () { + const PASS = true; + const DROP = false; + const BEEP = {}; + + mappings.add([modes.COMMAND], + ["g<lt>"], "Redisplay the last command output", + function () { + dactyl.assert(commandline.lastOutput, "No previous command output"); + mow.echo(mow.lastOutput, "Normal"); + }); + + bind = function bind(keys, description, action, test, default_) { + mappings.add([modes.OUTPUT_MULTILINE], + keys, description, + function (command) { + if (!options["more"]) + var res = PASS; + else if (test && !test(command)) + res = default_; + else + res = action.call(command); + + if (res === PASS || res === DROP) + modes.pop(); + else + mow.updateMorePrompt(); + if (res === BEEP) + dactyl.beep(); + else if (res === PASS) + events.feedkeys(command); + }); + } + + bind(["j", "<C-e>", "<Down>"], "Scroll down one line", + function () { mow.scrollVertical("lines", 1); }, + function () mow.isScrollable(1), BEEP); + + bind(["k", "<C-y>", "<Up>"], "Scroll up one line", + function () { mow.scrollVertical("lines", -1); }, + function () mow.isScrollable(-1), BEEP); + + bind(["<C-j>", "<C-m>", "<Return>"], "Scroll down one line, exit on last line", + function () { mow.scrollVertical("lines", 1); }, + function () mow.isScrollable(1), DROP); + + // half page down + bind(["<C-d>"], "Scroll down half a page", + function () { mow.scrollVertical("pages", .5); }, + function () mow.isScrollable(1), BEEP); + + bind(["<C-f>", "<PageDown>"], "Scroll down one page", + function () { mow.scrollVertical("pages", 1); }, + function () mow.isScrollable(1), BEEP); + + bind(["<Space>"], "Scroll down one page", + function () { mow.scrollVertical("pages", 1); }, + function () mow.isScrollable(1), DROP); + + bind(["<C-u>"], "Scroll up half a page", + function () { mow.scrollVertical("pages", -.5); }, + function () mow.isScrollable(-1), BEEP); + + bind(["<C-b>", "<PageUp>"], "Scroll up half a page", + function () { mow.scrollVertical("pages", -1); }, + function () mow.isScrollable(-1), BEEP); + + bind(["gg"], "Scroll to the beginning of output", + function () { mow.scrollToPercent(null, 0); }) + + bind(["G"], "Scroll to the end of output", + function () { mow.body.scrollTop = mow.body.scrollHeight; }) + + // copy text to clipboard + bind(["<C-y>"], "Yank selection to clipboard", + function () { dactyl.clipboardWrite(buffer.getCurrentWord(mow.window)); }); + + // close the window + bind(["q"], "Close the output window", + function () {}, + function () false, DROP); + }, + options: function () { + options.add(["more"], + "Pause the message list window when the full output will not fit on one page", + "boolean", true); + } +}); diff --git a/common/content/tabs.js b/common/content/tabs.js index a6ecaeae..87a91e7d 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -908,7 +908,7 @@ var Tabs = Module("tabs", { if (count != null) tabs.switchTo(String(count)); else - commandline.open(":", "buffer! ", modes.EX); + CommandExMode.open("buffer! "); }, { count: true }); |