1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
9 var MOW = Module("mow", {
10 init: function init() {
12 this._resize = Timer(20, 400, function _resize() {
16 if (this.visible && isinstance(modes.main, modes.OUTPUT_MULTILINE))
17 this.updateMorePrompt();
20 this._timer = Timer(20, 400, function _timer() {
21 if (modes.have(modes.OUTPUT_MULTILINE)) {
24 if (options["more"] && this.isScrollable(1)) {
25 // start the last executed command's output at the top of the screen
26 let elements = this.document.getElementsByClassName("ex-command-output");
27 elements[elements.length - 1].scrollIntoView(true);
30 this.body.scrollTop = this.body.scrollHeight;
32 dactyl.focus(this.window);
33 this.updateMorePrompt();
37 events.listen(window, this, "windowEvents");
40 let fontSize = util.computedStyle(document.documentElement).fontSize;
41 styles.system.add("font-size", "dactyl://content/buffer.xhtml",
42 "body { font-size: " + fontSize + "; } \
43 html|html > xul|scrollbar { visibility: collapse !important; }",
46 XML.ignoreWhitespace = true;
47 util.overlayWindow(window, {
51 append: <e4x xmlns={XUL} xmlns:dactyl={NS}>
52 <window id={document.documentElement.id}>
54 <menupopup id="dactyl-contextmenu" highlight="Events" events="contextEvents">
55 <menuitem id="dactyl-context-copylink"
56 label="Copy Link Location" dactyl:group="link"
57 oncommand="goDoCommand('cmd_copyLink');"/>
58 <menuitem id="dactyl-context-copypath"
59 label="Copy File Path" dactyl:group="link path"
60 oncommand="dactyl.clipboardWrite(document.popupNode.getAttribute('path'));"/>
61 <menuitem id="dactyl-context-copy"
62 label="Copy" dactyl:group="selection"
64 <menuitem id="dactyl-context-selectall"
66 command="cmd_selectAll"/>
70 <vbox id={config.commandContainer}>
71 <vbox class="dactyl-container" id="dactyl-multiline-output-container" hidden="false" collapsed="true">
72 <iframe id="dactyl-multiline-output" src="dactyl://content/buffer.xhtml"
73 flex="1" hidden="false" collapsed="false" contextmenu="dactyl-contextmenu"
81 __noSuchMethod__: function (meth, args) Buffer[meth].apply(Buffer, [this.body].concat(args)),
83 get widget() this.widgets.multilineOutput,
84 widgets: Class.memoize(function widgets() commandline.widgets),
86 body: Class.memoize(function body() this.widget.contentDocument.documentElement),
87 get document() this.widget.contentDocument,
88 get window() this.widget.contentWindow,
91 * Display a multi-line message.
93 * @param {string} data
94 * @param {string} highlightGroup
96 echo: function echo(data, highlightGroup, silent) {
97 let body = this.document.body;
99 this.widgets.message = null;
100 if (!commandline.commandVisible)
103 if (modes.main != modes.OUTPUT_MULTILINE) {
104 modes.push(modes.OUTPUT_MULTILINE, null, {
105 onKeyPress: this.closure.onKeyPress,
106 leave: this.closure(function leave(stack) {
108 for (let message in values(this.messages))
110 message.leave(stack);
116 // If it's already XML, assume it knows what it's doing.
117 // Otherwise, white space is significant.
118 // The problem elsewhere is that E4X tends to insert new lines
119 // after interpolated data.
120 XML.ignoreWhitespace = XML.prettyPrinting = false;
122 if (isObject(data) && !isinstance(data, _)) {
123 this.lastOutput = null;
125 var output = util.xmlToDom(<div class="ex-command-output" style="white-space: nowrap" highlight={highlightGroup}/>,
127 data.document = this.document;
129 output.appendChild(data.message);
134 this.messages.push(data);
138 let style = isString(data) ? "pre" : "nowrap";
139 this.lastOutput = <div class="ex-command-output" style={"white-space: " + style} highlight={highlightGroup}>{data}</div>;
141 var output = util.xmlToDom(this.lastOutput, this.document);
144 // FIXME: need to make sure an open MOW is closed when commands
145 // that don't generate output are executed
147 this.body.scrollTop = 0;
148 body.textContent = "";
151 body.appendChild(output);
153 let str = typeof data !== "xml" && data.message || data;
155 dactyl.triggerObserver("echoMultiline", data, highlightGroup, output);
163 click: function onClick(event) {
164 if (event.getPreventDefault())
167 const openLink = function openLink(where) {
168 event.preventDefault();
169 dactyl.open(event.target.href, where);
172 if (event.target instanceof HTMLAnchorElement)
173 switch (events.toString(event)) {
175 openLink(dactyl.CURRENT_TAB);
177 case "<MiddleMouse>":
178 case "<C-LeftMouse>":
179 case "<C-M-LeftMouse>":
180 openLink({ where: dactyl.NEW_TAB, background: true });
182 case "<S-MiddleMouse>":
183 case "<C-S-LeftMouse>":
184 case "<C-M-S-LeftMouse>":
185 openLink({ where: dactyl.NEW_TAB, background: false });
187 case "<S-LeftMouse>":
188 openLink(dactyl.NEW_WINDOW);
192 unload: function onUnload(event) {
193 event.preventDefault();
198 resize: function onResize(event) {
204 popupshowing: function onPopupShowing(event) {
205 let menu = commandline.widgets.contextMenu;
207 link: window.document.popupNode instanceof HTMLAnchorElement,
208 path: window.document.popupNode.hasAttribute("path"),
209 selection: !window.document.commandDispatcher.focusedWindow.getSelection().isCollapsed
212 for (let node in array.iterValues(menu.children)) {
213 let group = node.getAttributeNS(NS, "group");
214 node.hidden = group && !group.split(/\s+/).every(function (g) enabled[g]);
219 onKeyPress: function onKeyPress(eventList) {
220 const KILL = false, PASS = true;
222 if (options["more"] && mow.isScrollable(1))
223 this.updateMorePrompt(false, true);
226 events.feedevents(null, eventList);
233 * Changes the height of the message window to fit in the available space.
235 * @param {boolean} open If true, the widget will be opened if it's not
238 resize: function resize(open, extra) {
239 if (!(open || this.visible))
242 let doc = this.widget.contentDocument;
244 let availableHeight = config.outputHeight;
246 availableHeight += parseFloat(this.widgets.mowContainer.height || 0);
247 availableHeight -= extra || 0;
249 doc.body.style.minWidth = this.widgets.commandbar.commandline.scrollWidth + "px";
250 this.widgets.mowContainer.height = Math.min(doc.body.clientHeight, availableHeight) + "px";
251 this.timeout(function ()
252 this.widgets.mowContainer.height = Math.min(doc.body.clientHeight, availableHeight) + "px",
255 doc.body.style.minWidth = "";
261 let rect = this.widgets.commandbar.commandline.getBoundingClientRect();
262 let offset = rect.bottom - window.innerHeight;
263 return Math.max(0, offset);
267 * Update or remove the multi-line output widget's "MORE" prompt.
269 * @param {boolean} force If true, "-- More --" is shown even if we're
270 * at the end of the output.
271 * @param {boolean} showHelp When true, show the valid key sequences
274 updateMorePrompt: function updateMorePrompt(force, showHelp) {
275 if (!this.visible || !isinstance(modes.main, modes.OUTPUT_MULTILINE))
276 return this.widgets.message = null;
278 let elem = this.widget.contentDocument.documentElement;
281 this.widgets.message = ["MoreMsg", "-- More -- SPACE/<C-f>/j: screen/page/line down, <C-b>/<C-u>/k: up, q: quit"];
282 else if (force || (options["more"] && Buffer.isScrollable(elem, 1)))
283 this.widgets.message = ["MoreMsg", "-- More --"];
285 this.widgets.message = ["Question", "Press ENTER or type command to continue"];
288 visible: Modes.boundProperty({
289 get: function get_mowVisible() !this.widgets.mowContainer.collapsed,
290 set: function set_mowVisible(value) {
291 this.widgets.mowContainer.collapsed = !value;
293 let elem = this.widget;
294 if (!value && elem && elem.contentWindow == document.commandDispatcher.focusedWindow) {
296 let focused = content.document.activeElement;
297 if (Events.isInputElement(focused))
300 document.commandDispatcher.focusedWindow = content;
306 mappings: function initMappings() {
311 mappings.add([modes.COMMAND],
312 ["g<lt>"], "Redisplay the last command output",
314 dactyl.assert(commandline.lastOutput, _("mow.noPreviousOutput"));
315 mow.echo(mow.lastOutput, "Normal");
318 let bind = function bind(keys, description, action, test, default_) {
319 mappings.add([modes.OUTPUT_MULTILINE],
322 if (!options["more"])
324 else if (test && !test(args))
327 res = action.call(this, args);
329 if (res === PASS || res === DROP)
332 mow.updateMorePrompt();
335 else if (res === PASS)
336 events.feedkeys(args.command);
338 count: action.length > 0
342 bind(["j", "<C-e>", "<Down>"], "Scroll down one line",
343 function ({ count }) { mow.scrollVertical("lines", 1 * (count || 1)); },
344 function () mow.isScrollable(1), BEEP);
346 bind(["k", "<C-y>", "<Up>"], "Scroll up one line",
347 function ({ count }) { mow.scrollVertical("lines", -1 * (count || 1)); },
348 function () mow.isScrollable(-1), BEEP);
350 bind(["<C-j>", "<C-m>", "<Return>"], "Scroll down one line, exit on last line",
351 function ({ count }) { mow.scrollVertical("lines", 1 * (count || 1)); },
352 function () mow.isScrollable(1), DROP);
355 bind(["<C-d>"], "Scroll down half a page",
356 function ({ count }) { mow.scrollVertical("pages", .5 * (count || 1)); },
357 function () mow.isScrollable(1), BEEP);
359 bind(["<C-f>", "<PageDown>"], "Scroll down one page",
360 function ({ count }) { mow.scrollVertical("pages", 1 * (count || 1)); },
361 function () mow.isScrollable(1), BEEP);
363 bind(["<Space>"], "Scroll down one page",
364 function ({ count }) { mow.scrollVertical("pages", 1 * (count || 1)); },
365 function () mow.isScrollable(1), DROP);
367 bind(["<C-u>"], "Scroll up half a page",
368 function ({ count }) { mow.scrollVertical("pages", -.5 * (count || 1)); },
369 function () mow.isScrollable(-1), BEEP);
371 bind(["<C-b>", "<PageUp>"], "Scroll up half a page",
372 function ({ count }) { mow.scrollVertical("pages", -1 * (count || 1)); },
373 function () mow.isScrollable(-1), BEEP);
375 bind(["gg"], "Scroll to the beginning of output",
376 function () { mow.scrollToPercent(null, 0); });
378 bind(["G"], "Scroll to the end of output",
379 function ({ count }) { mow.scrollToPercent(null, count || 100); });
381 // copy text to clipboard
382 bind(["<C-y>"], "Yank selection to clipboard",
383 function () { dactyl.clipboardWrite(buffer.getCurrentWord(mow.window)); });
386 bind(["q"], "Close the output window",
388 function () false, DROP);
390 options: function initOptions() {
391 options.add(["more"],
392 "Pause the message list window when the full output will not fit on one page",