summaryrefslogtreecommitdiff
path: root/common/content/mow.js
diff options
context:
space:
mode:
Diffstat (limited to 'common/content/mow.js')
-rw-r--r--common/content/mow.js352
1 files changed, 352 insertions, 0 deletions
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);
+ }
+});