X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fcontent%2Fbuffer.js;h=d0c732cd10fa9983cda1940b77eb110646841c14;hb=70740024f9c028c1fd63e1a1850ab062ff956054;hp=af099e747e8d58bde657158747088141187b5654;hpb=eeed0be1a8abf7e3c97f43b63c1d595e940fef21;p=dactyl.git diff --git a/common/content/buffer.js b/common/content/buffer.js index af099e7..d0c732c 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -19,6 +19,24 @@ var Buffer = Module("buffer", { this.evaluateXPath = util.evaluateXPath; this.pageInfo = {}; + this.addPageInfoSection("e", "Search Engines", function (verbose) { + + let n = 1; + let nEngines = 0; + for (let { document: doc } in values(buffer.allFrames())) { + let engines = util.evaluateXPath(["link[@href and @rel='search' and @type='application/opensearchdescription+xml']"], doc); + nEngines += engines.snapshotLength; + + if (verbose) + for (let link in engines) + yield [link.title || /*L*/ "Engine " + n++, + {link.href}]; + } + + if (!verbose && nEngines) + yield nEngines + /*L*/" engine" + (nEngines > 1 ? "s" : ""); + }); + this.addPageInfoSection("f", "Feeds", function (verbose) { const feedTypes = { "application/rss+xml": "RSS", @@ -75,7 +93,7 @@ var Buffer = Module("buffer", { } if (!verbose && nFeed) - yield nFeed + " feed" + (nFeed > 1 ? "s" : ""); + yield nFeed + /*L*/" feed" + (nFeed > 1 ? "s" : ""); }); this.addPageInfoSection("g", "General Info", function (verbose) { @@ -110,7 +128,7 @@ var Buffer = Module("buffer", { if (!verbose) { if (pageSize[0]) - yield (pageSize[1] || pageSize[0]) + " bytes"; + yield (pageSize[1] || pageSize[0]) + /*L*/" bytes"; yield lastMod; return; } @@ -134,6 +152,9 @@ var Buffer = Module("buffer", { }); this.addPageInfoSection("m", "Meta Tags", function (verbose) { + if (!verbose) + return []; + // get meta tag data, sort and put into pageMeta[] let metaNodes = buffer.focusedFrame.document.getElementsByTagName("meta"); @@ -141,9 +162,48 @@ var Buffer = Module("buffer", { .sort(function (a, b) util.compareIgnoreCase(a[0], b[0])); }); + let identity = window.gIdentityHandler; + this.addPageInfoSection("s", "Security", function (verbose) { + if (!verbose || !identity) + return; // For now + + // Modified from Firefox + function location(data) array.compact([ + data.city, data.state, data.country + ]).join(", "); + + switch (statusline.security) { + case "secure": + case "extended": + var data = identity.getIdentityData(); + + yield ["Host", identity.getEffectiveHost()]; + + if (statusline.security === "extended") + yield ["Owner", data.subjectOrg]; + else + yield ["Owner", _("pageinfo.s.ownerUnverified", data.subjectOrg)]; + + if (location(data).length) + yield ["Location", location(data)]; + + yield ["Verified by", data.caOrg]; + + if (identity._overrideService.hasMatchingOverride(identity._lastLocation.hostname, + (identity._lastLocation.port || 443), + data.cert, {}, {})) + yield ["User exception", /*L*/"true"]; + break; + } + }); + dactyl.commands["buffer.viewSource"] = function (event) { let elem = event.originalTarget; - buffer.viewSource([elem.getAttribute("href"), Number(elem.getAttribute("line"))]); + let obj = { url: elem.getAttribute("href"), line: Number(elem.getAttribute("line")) }; + if (elem.hasAttribute("column")) + obj.column = elem.getAttribute("column"); + + buffer.viewSource(obj); }; }, @@ -311,7 +371,7 @@ var Buffer = Module("buffer", { allFrames: function allFrames(win, focusedFirst) { let frames = []; (function rec(frame) { - if (frame.document.body instanceof HTMLBodyElement) + if (true || frame.document.body instanceof HTMLBodyElement) frames.push(frame); Array.forEach(frame.frames, rec); })(win || content); @@ -340,7 +400,7 @@ var Buffer = Module("buffer", { * @returns {string} */ get currentWord() Buffer.currentWord(this.focusedFrame), - getCurrentWord: deprecated("buffer.currentWord", function getCurrentWord() this.currentWord), + getCurrentWord: deprecated("buffer.currentWord", function getCurrentWord() Buffer.currentWord(this.focusedFrame, true)), /** * Returns true if a scripts are allowed to focus the given input @@ -352,8 +412,16 @@ var Buffer = Module("buffer", { focusAllowed: function focusAllowed(elem) { if (elem instanceof Window && !Editor.getEditor(elem)) return true; + let doc = elem.ownerDocument || elem.document || elem; - return !options["strictfocus"] || doc.dactylFocusAllowed; + switch (options.get("strictfocus").getKey(doc.documentURIObject || util.newURI(doc.documentURI), "moderate")) { + case "despotic": + return elem.dactylFocusAllowed || elem.frameElement && elem.frameElement.dactylFocusAllowed; + case "moderate": + return doc.dactylFocusAllowed || elem.frameElement && elem.frameElement.ownerDocument.dactylFocusAllowed; + default: + return true; + } }, /** @@ -365,6 +433,7 @@ var Buffer = Module("buffer", { */ focusElement: function focusElement(elem) { let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem; + elem.dactylFocusAllowed = true; win.document.dactylFocusAllowed = true; if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement])) @@ -413,7 +482,7 @@ var Buffer = Module("buffer", { }, /** - * Find the counth last link on a page matching one of the given + * Find the *count*th last link on a page matching one of the given * regular expressions, or with a @rel or @rev attribute matching * the given relation. Each frame is searched beginning with the * last link and progressing to the first, once checking for @@ -483,7 +552,7 @@ var Buffer = Module("buffer", { */ followLink: function followLink(elem, where) { let doc = elem.ownerDocument; - let view = doc.defaultView; + let win = doc.defaultView; let { left: offsetX, top: offsetY } = elem.getBoundingClientRect(); if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement])) @@ -526,6 +595,8 @@ var Buffer = Module("buffer", { ctrlKey: ctrlKey, shiftKey: shiftKey, metaKey: ctrlKey })); }); + let sel = util.selectionController(win); + sel.getSelection(sel.SELECTION_FOCUS_REGION).collapseToStart(); }); }, @@ -533,9 +604,7 @@ var Buffer = Module("buffer", { * @property {nsISelectionController} The current document's selection * controller. */ - get selectionController() config.browser.docShell - .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsISelectionDisplay) - .QueryInterface(Ci.nsISelectionController), + get selectionController() util.selectionController(this.focusedFrame), /** * Opens the appropriate context menu for *elem*. @@ -561,7 +630,7 @@ var Buffer = Module("buffer", { try { window.urlSecurityCheck(uri.spec, doc.nodePrincipal); - io.CommandFileMode("Save link: ", { + io.CommandFileMode(_("buffer.prompt.saveLink") + " ", { onSubmit: function (path) { let file = io.File(path); if (file.exists() && file.isDirectory()) @@ -598,16 +667,16 @@ var Buffer = Module("buffer", { | persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES; let downloadListener = new window.DownloadListener(window, - services.Transfer(uri, services.io.newFileURI(file), "", + services.Transfer(uri, File(file).URI, "", null, null, null, persist)); persist.progressListener = update(Object.create(downloadListener), { - onStateChange: function onStateChange(progress, request, flag, status) { - if (callback && (flag & Ci.nsIWebProgressListener.STATE_STOP) && status == 0) - dactyl.trapErrors(callback, self, uri, file, progress, request, flag, status); + onStateChange: util.wrapCallback(function onStateChange(progress, request, flags, status) { + if (callback && (flags & Ci.nsIWebProgressListener.STATE_STOP) && status == 0) + dactyl.trapErrors(callback, self, uri, file, progress, request, flags, status); return onStateChange.superapply(this, arguments); - } + }) }); persist.saveURI(uri, null, null, null, null, file); @@ -672,7 +741,7 @@ var Buffer = Module("buffer", { */ findScrollable: function findScrollable(dir, horizontal) { function find(elem) { - while (!(elem instanceof Element) && elem.parentNode) + while (elem && !(elem instanceof Element) && elem.parentNode) elem = elem.parentNode; for (; elem && elem.parentNode instanceof Element; elem = elem.parentNode) if (Buffer.isScrollable(elem, dir, horizontal)) @@ -702,7 +771,7 @@ var Buffer = Module("buffer", { doc.documentElement); } let doc = this.focusedFrame.document; - return elem || doc.body || doc.documentElement; + return dactyl.assert(elem || doc.body || doc.documentElement); }, /** @@ -728,12 +797,43 @@ var Buffer = Module("buffer", { return win; }, + /** + * Finds the next visible element for the node path in 'jumptags' + * for *arg*. + * + * @param {string} arg The element in 'jumptags' to use for the search. + * @param {number} count The number of elements to jump. + * @optional + * @param {boolean} reverse If true, search backwards. @optional + */ + findJump: function findJump(arg, count, reverse) { + const FUDGE = 10; + + let path = options["jumptags"][arg]; + dactyl.assert(path, _("error.invalidArgument", arg)); + + let distance = reverse ? function (rect) -rect.top : function (rect) rect.top; + let elems = [[e, distance(e.getBoundingClientRect())] for (e in path.matcher(this.focusedFrame.document))] + .filter(function (e) e[1] > FUDGE) + .sort(function (a, b) a[1] - b[1]) + + let idx = Math.min((count || 1) - 1, elems.length); + dactyl.assert(idx in elems); + + let elem = elems[idx][0]; + elem.scrollIntoView(true); + + let sel = elem.ownerDocument.defaultView.getSelection(); + sel.removeAllRanges(); + sel.addRange(RangeFind.endpoint(RangeFind.nodeRange(elem), true)); + }, + // TODO: allow callback for filtering out unwanted frames? User defined? /** * Shifts the focus to another frame within the buffer. Each buffer * contains at least one frame. * - * @param {number} count The number of frames to skip through. A negative + * @param {number} count The number of frames to skip through. A negative * count skips backwards. */ shiftFrameFocus: function shiftFrameFocus(count) { @@ -785,7 +885,7 @@ var Buffer = Module("buffer", { * @param {Node} elem The element to query. */ showElementInfo: function showElementInfo(elem) { - dactyl.echo(<>Element:
{util.objectToString(elem, true)}, commandline.FORCE_MULTILINE); + dactyl.echo(<>Element:
{util.objectToString(elem, true)}, commandline.FORCE_MULTILINE); }, /** @@ -798,15 +898,15 @@ var Buffer = Module("buffer", { showPageInfo: function showPageInfo(verbose, sections) { // Ctrl-g single line output if (!verbose) { - let file = content.location.pathname.split("/").pop() || "[No Name]"; - let title = content.document.title || "[No Title]"; + let file = content.location.pathname.split("/").pop() || _("buffer.noName"); + let title = content.document.title || _("buffer.noTitle"); - let info = template.map("gf", + let info = template.map(sections || options["pageinfo"], function (opt) template.map(buffer.pageInfo[opt].action(), util.identity, ", "), ", "); if (bookmarkcache.isBookmarked(this.URL)) - info += ", bookmarked"; + info += ", " + _("buffer.bookmarked"); let pageInfoText = <>{file.quote()} [{info}] {title}; dactyl.echo(pageInfoText, commandline.FORCE_SINGLELINE); @@ -852,26 +952,34 @@ var Buffer = Module("buffer", { * specified *url*. Either the default viewer or the configured external * editor is used. * - * @param {string} url The URL of the source. - * @default The current buffer. + * @param {string|object|null} loc If a string, the URL of the source, + * otherwise an object with some or all of the following properties: + * + * url: The URL to view. + * doc: The document to view. + * line: The line to select. + * column: The column to select. + * + * If no URL is provided, the current document is used. + * @default The current buffer. * @param {boolean} useExternalEditor View the source in the external editor. */ - viewSource: function viewSource(url, useExternalEditor) { + viewSource: function viewSource(loc, useExternalEditor) { let doc = this.focusedFrame.document; - if (isArray(url)) { - if (options.get("editor").has("line")) - this.viewSourceExternally(url[0] || doc, url[1]); + if (isObject(loc)) { + if (options.get("editor").has("line") || !loc.url) + this.viewSourceExternally(loc.doc || loc.url || doc, loc); else window.openDialog("chrome://global/content/viewSource.xul", "_blank", "all,dialog=no", - url[0], null, null, url[1]); + loc.url, null, null, loc.line); } else { if (useExternalEditor) - this.viewSourceExternally(url || doc); + this.viewSourceExternally(loc || doc); else { - url = url || doc.location.href; + let url = loc || doc.location.href; const PREFIX = "view-source:"; if (url.indexOf(PREFIX) == 0) url = url.substr(PREFIX.length); @@ -894,13 +1002,19 @@ var Buffer = Module("buffer", { * immediately. * * @param {Document} doc The document to view. + * @param {function|object} callback If a function, the callback to be + * called with two arguments: the nsIFile of the file, and temp, a + * boolean which is true if the file is temporary. Otherwise, an object + * with line and column properties used to determine where to open the + * source. + * @optional */ viewSourceExternally: Class("viewSourceExternally", XPCOM([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference]), { init: function init(doc, callback) { this.callback = callable(callback) ? callback : function (file, temp) { - editor.editFileExternally({ file: file.path, line: callback }, + editor.editFileExternally(update({ file: file.path }, callback || {}), function () { temp && file.remove(false); }); return true; }; @@ -928,8 +1042,8 @@ var Buffer = Module("buffer", { return null; }, - onStateChange: function onStateChange(progress, request, flag, status) { - if ((flag & this.STATE_STOP) && status == 0) { + onStateChange: function onStateChange(progress, request, flags, status) { + if ((flags & this.STATE_STOP) && status == 0) { try { var ok = this.callback(this.file, true); } @@ -997,14 +1111,14 @@ var Buffer = Module("buffer", { * Adjusts the page zoom of the current buffer relative to the * current zoom level. * - * @param {number} steps The integral number of natural fractions by - * which to adjust the current page zoom. If positive, the zoom - * level is increased, if negative it is decreased. + * @param {number} steps The integral number of natural fractions by which + * to adjust the current page zoom. If positive, the zoom level is + * increased, if negative it is decreased. * @param {boolean} fullZoom If true, zoom all content of the page, - * including raster images. If false, zoom only text. If omitted, - * use the current zoom function. @optional - * @throws {FailedAssertion} if the buffer's zoom level is already - * at its extreme in the given direction. + * including raster images. If false, zoom only text. If omitted, use + * the current zoom function. @optional + * @throws {FailedAssertion} if the buffer's zoom level is already at its + * extreme in the given direction. */ bumpZoomLevel: function bumpZoomLevel(steps, fullZoom) { if (fullZoom === undefined) @@ -1019,7 +1133,7 @@ var Buffer = Module("buffer", { this.setZoom(Math.round(values[i] * 100), fullZoom); }, - getAllFrames: deprecated("buffer.allFrames", function getAllFrames() buffer.getAllFrames.apply(buffer, arguments)), + getAllFrames: deprecated("buffer.allFrames", "allFrames"), scrollTop: deprecated("buffer.scrollToPercent", function scrollTop() buffer.scrollToPercent(null, 0)), scrollBottom: deprecated("buffer.scrollToPercent", function scrollBottom() buffer.scrollToPercent(null, 100)), scrollStart: deprecated("buffer.scrollToPercent", function scrollStart() buffer.scrollToPercent(0, null)), @@ -1027,7 +1141,7 @@ var Buffer = Module("buffer", { scrollColumns: deprecated("buffer.scrollHorizontal", function scrollColumns(cols) buffer.scrollHorizontal("columns", cols)), scrollPages: deprecated("buffer.scrollHorizontal", function scrollPages(pages) buffer.scrollVertical("pages", pages)), scrollTo: deprecated("Buffer.scrollTo", function scrollTo(x, y) content.scrollTo(x, y)), - textZoom: deprecated("buffer.zoomValue and buffer.fullZoom", function textZoom() config.browser.markupDocumentViewer.textZoom * 100) + textZoom: deprecated("buffer.zoomValue/buffer.fullZoom", function textZoom() config.browser.markupDocumentViewer.textZoom * 100) }, { PageInfo: Struct("PageInfo", "name", "title", "action") .localize("title"), @@ -1044,17 +1158,21 @@ var Buffer = Module("buffer", { * * @returns {string} */ - currentWord: function currentWord(win) { + currentWord: function currentWord(win, select) { let selection = win.getSelection(); if (selection.rangeCount == 0) return ""; let range = selection.getRangeAt(0).cloneRange(); - if (range.collapsed) { + if (range.collapsed && range.startContainer instanceof Text) { let re = options.get("iskeyword").regexp; Editor.extendRange(range, true, re, true); Editor.extendRange(range, false, re, true); } + if (select) { + selection.removeAllRanges(); + selection.addRange(range); + } return util.domToString(range); }, @@ -1079,13 +1197,13 @@ var Buffer = Module("buffer", { var names = []; if (node.title) - names.push([node.title, "Page Name"]); + names.push([node.title, /*L*/"Page Name"]); if (node.alt) - names.push([node.alt, "Alternate Text"]); + names.push([node.alt, /*L*/"Alternate Text"]); if (!isinstance(node, Document) && node.textContent) - names.push([node.textContent, "Link Text"]); + names.push([node.textContent, /*L*/"Link Text"]); names.push([decodeURIComponent(url.replace(/.*?([^\/]*)\/*$/, "$1")), "File Name"]); @@ -1133,6 +1251,11 @@ var Buffer = Module("buffer", { elem.scrollLeft = left; if (top != null) elem.scrollTop = top; + + if (util.haveGecko("2.0") && !util.haveGecko("7.*")) + elem.ownerDocument.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils) + .redraw(); }, /** @@ -1156,10 +1279,11 @@ var Buffer = Module("buffer", { else throw Error(); + dactyl.assert(number < 0 ? elem.scrollLeft > 0 : elem.scrollLeft < elem.scrollWidth - elem.clientWidth); + let left = elem.dactylScrollDestX !== undefined ? elem.dactylScrollDestX : elem.scrollLeft; elem.dactylScrollDestX = undefined; - dactyl.assert(number < 0 ? left > 0 : left < elem.scrollWidth - elem.clientWidth); Buffer.scrollTo(elem, left + number * increment, null); }, @@ -1184,10 +1308,11 @@ var Buffer = Module("buffer", { else throw Error(); + dactyl.assert(number < 0 ? elem.scrollTop > 0 : elem.scrollTop < elem.scrollHeight - elem.clientHeight); + let top = elem.dactylScrollDestY !== undefined ? elem.dactylScrollDestY : elem.scrollTop; elem.dactylScrollDestY = undefined; - dactyl.assert(number < 0 ? top > 0 : top < elem.scrollHeight - elem.clientHeight); Buffer.scrollTo(elem, null, top + number * increment); }, @@ -1212,7 +1337,7 @@ var Buffer = Module("buffer", { }, openUploadPrompt: function openUploadPrompt(elem) { - io.CommandFileMode("Upload file: ", { + io.CommandFileMode(_("buffer.prompt.uploadFile") + " ", { onSubmit: function onSubmit(path) { let file = io.File(path); dactyl.assert(file.exists()); @@ -1238,12 +1363,21 @@ var Buffer = Module("buffer", { // FIXME: arg handling is a bit of a mess, check for filename dactyl.assert(!arg || arg[0] == ">" && !util.OS.isWindows, - _("error.trailing")); + _("error.trailingCharacters")); + + const PRINTER = "PostScript/default"; + const BRANCH = "print.printer_" + PRINTER + "."; prefs.withContext(function () { if (arg) { - prefs.set("print.print_to_file", "true"); - prefs.set("print.print_to_filename", io.File(arg.substr(1)).path); + prefs.set("print.print_printer", PRINTER); + + prefs.set( "print.print_to_file", true); + prefs.set(BRANCH + "print_to_file", true); + + prefs.set( "print.print_to_filename", io.File(arg.substr(1)).path); + prefs.set(BRANCH + "print_to_filename", io.File(arg.substr(1)).path); + dactyl.echomsg(_("print.toFile", arg.substr(1))); } else @@ -1417,7 +1551,7 @@ var Buffer = Module("buffer", { level = Math.constrain(level, Buffer.ZOOM_MIN, Buffer.ZOOM_MAX); } else - dactyl.assert(false, _("error.trailing")); + dactyl.assert(false, _("error.trailingCharacters")); buffer.setZoom(level, args.bang); }, @@ -1434,21 +1568,24 @@ var Buffer = Module("buffer", { let styles = iter([s.title, []] for (s in values(buffer.alternateStyleSheets))).toObject(); buffer.alternateStyleSheets.forEach(function (style) { - styles[style.title].push(style.href || "inline"); + styles[style.title].push(style.href || _("style.inline")); }); context.completions = [[title, href.join(", ")] for ([title, href] in Iterator(styles))]; }; - completion.buffer = function buffer(context) { + completion.buffer = function buffer(context, visible) { let filter = context.filter.toLowerCase(); + let defItem = { parent: { getTitle: function () "" } }; + let tabGroups = {}; tabs.getGroups(); - tabs.allTabs.forEach(function (tab, i) { + tabs[visible ? "visibleTabs" : "allTabs"].forEach(function (tab, i) { let group = (tab.tabItem || tab._tabViewTabItem || defItem).parent || defItem.parent; - if (!set.has(tabGroups, group.id)) + if (!Set.has(tabGroups, group.id)) tabGroups[group.id] = [group.getTitle(), []]; + group = tabGroups[group.id]; group[1].push([i, tab.linkedBrowser]); }); @@ -1470,7 +1607,7 @@ var Buffer = Module("buffer", { command: function () "tabs.select" }; context.compare = CompletionContext.Sort.number; - context.filters = [CompletionContext.Filter.textDescription]; + context.filters[0] = CompletionContext.Filter.textDescription; for (let [id, vals] in Iterator(tabGroups)) context.fork(id, 0, this, function (context, [name, browsers]) { @@ -1483,12 +1620,12 @@ var Buffer = Module("buffer", { else if (i == tabs.index(tabs.alternate)) indicator = "#"; - let tab = tabs.getTab(i); + let tab = tabs.getTab(i, visible); let url = browser.contentDocument.location.href; i = i + 1; return { - text: [i + ": " + (tab.label || "(Untitled)"), i + ": " + url], + text: [i + ": " + (tab.label || /*L*/"(Untitled)"), i + ": " + url], tab: tab, id: i - 1, url: url, @@ -1514,21 +1651,21 @@ var Buffer = Module("buffer", { function () { dactyl.clipboardWrite(buffer.uri.spec, true); }); mappings.add([modes.NORMAL], - [""], "Increment last number in URL", + ["", ""], "Increment last number in URL", function (args) { buffer.incrementURL(Math.max(args.count, 1)); }, { count: true }); mappings.add([modes.NORMAL], - [""], "Decrement last number in URL", + ["", ""], "Decrement last number in URL", function (args) { buffer.incrementURL(-Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.NORMAL], ["gu"], + mappings.add([modes.NORMAL], ["gu", ""], "Go to parent directory", function (args) { buffer.climbUrlPath(Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.NORMAL], ["gU"], + mappings.add([modes.NORMAL], ["gU", ""], "Go to the root of the website", function () { buffer.climbUrlPath(-1); }); @@ -1542,11 +1679,11 @@ var Buffer = Module("buffer", { }, { count: true }); - mappings.add([modes.COMMAND], ["i", ""], - "Start caret mode", + mappings.add([modes.NORMAL], ["i", ""], + "Start Caret mode", function () { modes.push(modes.CARET); }); - mappings.add([modes.COMMAND], [""], + mappings.add([modes.NORMAL], ["", ""], "Stop loading the current web page", function () { ex.stop(); }); @@ -1579,12 +1716,12 @@ var Buffer = Module("buffer", { "Scroll to the absolute right of the document", function () { buffer.scrollToPercent(100, null); }); - mappings.add([modes.COMMAND], ["gg", ""], + mappings.add([modes.COMMAND], ["gg", "", ""], "Go to the top of the document", function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 0); }, { count: true }); - mappings.add([modes.COMMAND], ["G", ""], + mappings.add([modes.COMMAND], ["G", "", ""], "Go to the end of the document", function (args) { buffer.scrollToPercent(null, args.count != null ? args.count : 100); }, { count: true }); @@ -1607,55 +1744,84 @@ var Buffer = Module("buffer", { function (args) { buffer._scrollByScrollSize(args.count, false); }, { count: true }); - mappings.add([modes.COMMAND], ["", "", "", ""], + mappings.add([modes.COMMAND], ["", "", "", ""], "Scroll up a full page", function (args) { buffer.scrollVertical("pages", -Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.COMMAND], ["", "", "", ""], + mappings.add([modes.COMMAND], [""], + "Scroll down a full page", + function (args) { + if (isinstance(content.document.activeElement, [HTMLInputElement, HTMLButtonElement])) + return Events.PASS; + buffer.scrollVertical("pages", Math.max(args.count, 1)); + }, + { count: true }); + + mappings.add([modes.COMMAND], ["", "", ""], "Scroll down a full page", function (args) { buffer.scrollVertical("pages", Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.COMMAND], ["]f", ""], + mappings.add([modes.NORMAL], ["]f", ""], "Focus next frame", function (args) { buffer.shiftFrameFocus(Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.COMMAND], ["[f", ""], + mappings.add([modes.NORMAL], ["[f", ""], "Focus previous frame", function (args) { buffer.shiftFrameFocus(-Math.max(args.count, 1)); }, { count: true }); - mappings.add([modes.COMMAND], ["]]", ""], + mappings.add([modes.NORMAL], ["["], + "Jump to the previous element as defined by 'jumptags'", + function (args) { buffer.findJump(args.arg, args.count, true); }, + { arg: true, count: true }); + + mappings.add([modes.NORMAL], ["]"], + "Jump to the next element as defined by 'jumptags'", + function (args) { buffer.findJump(args.arg, args.count, false); }, + { arg: true, count: true }); + + mappings.add([modes.NORMAL], ["{"], + "Jump to the previous paragraph", + function (args) { buffer.findJump("p", args.count, true); }, + { count: true }); + + mappings.add([modes.NORMAL], ["}"], + "Jump to the next paragraph", + function (args) { buffer.findJump("p", args.count, false); }, + { count: true }); + + mappings.add([modes.NORMAL], ["]]", ""], "Follow the link labeled 'next' or '>' if it exists", function (args) { buffer.findLink("next", options["nextpattern"], (args.count || 1) - 1, true); }, { count: true }); - mappings.add([modes.COMMAND], ["[[", ""], + mappings.add([modes.NORMAL], ["[[", ""], "Follow the link labeled 'prev', 'previous' or '<' if it exists", function (args) { buffer.findLink("previous", options["previouspattern"], (args.count || 1) - 1, true); }, { count: true }); - mappings.add([modes.COMMAND], ["gf", ""], + mappings.add([modes.NORMAL], ["gf", ""], "Toggle between rendered and source view", function () { buffer.viewSource(null, false); }); - mappings.add([modes.COMMAND], ["gF", ""], + mappings.add([modes.NORMAL], ["gF", ""], "View source with an external editor", function () { buffer.viewSource(null, true); }); - mappings.add([modes.COMMAND], ["gi", ""], + mappings.add([modes.NORMAL], ["gi", ""], "Focus last used input field", function (args) { let elem = buffer.lastInputField; if (args.count >= 1 || !elem || !events.isContentNode(elem)) { - let xpath = ["frame", "iframe", "input", "textarea[not(@disabled) and not(@readonly)]"]; + let xpath = ["frame", "iframe", "input", "xul:textbox", "textarea[not(@disabled) and not(@readonly)]"]; let frames = buffer.allFrames(null, true); @@ -1664,13 +1830,14 @@ var Buffer = Module("buffer", { if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement])) return Editor.getEditor(elem.contentWindow); - if (elem.readOnly || elem instanceof HTMLInputElement && !set.has(util.editableInputs, elem.type)) + if (elem.readOnly || elem instanceof HTMLInputElement && !Set.has(util.editableInputs, elem.type)) return false; let computedStyle = util.computedStyle(elem); let rect = elem.getBoundingClientRect(); return computedStyle.visibility != "hidden" && computedStyle.display != "none" && - computedStyle.MozUserFocus != "ignore" && rect.width && rect.height; + (elem instanceof Ci.nsIDOMXULTextBoxElement || computedStyle.MozUserFocus != "ignore") && + rect.width && rect.height; }); dactyl.assert(elements.length > 0); @@ -1681,36 +1848,40 @@ var Buffer = Module("buffer", { }, { count: true }); - mappings.add([modes.COMMAND], ["gP"], - "Open (]put) a URL based on the current clipboard contents in a new buffer", + function url() { + let url = dactyl.clipboardRead(); + dactyl.assert(url, _("error.clipboardEmpty")); + + let proto = /^([-\w]+):/.exec(url); + if (proto && "@mozilla.org/network/protocol;1?name=" + proto[1] in Cc && !RegExp(options["urlseparator"]).test(url)) + return url.replace(/\s+/g, ""); + return url; + } + + mappings.add([modes.NORMAL], ["gP"], + "Open (put) a URL based on the current clipboard contents in a new background buffer", function () { - let url = dactyl.clipboardRead(); - dactyl.assert(url, _("error.clipboardEmpty")); - dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB, background: true }); + dactyl.open(url(), { from: "paste", where: dactyl.NEW_TAB, background: true }); }); - mappings.add([modes.COMMAND], ["p", "", ""], + mappings.add([modes.NORMAL], ["p", "", ""], "Open (put) a URL based on the current clipboard contents in the current buffer", function () { - let url = dactyl.clipboardRead(); - dactyl.assert(url, _("error.clipboardEmpty")); - dactyl.open(url); + dactyl.open(url()); }); - mappings.add([modes.COMMAND], ["P", ""], + mappings.add([modes.NORMAL], ["P", ""], "Open (put) a URL based on the current clipboard contents in a new buffer", function () { - let url = dactyl.clipboardRead(); - dactyl.assert(url, _("error.clipboardEmpty")); - dactyl.open(url, { from: "paste", where: dactyl.NEW_TAB }); + dactyl.open(url(), { from: "paste", where: dactyl.NEW_TAB }); }); // reloading - mappings.add([modes.COMMAND], ["r", ""], + mappings.add([modes.NORMAL], ["r", ""], "Reload the current web page", function () { tabs.reload(tabs.getTab(), false); }); - mappings.add([modes.COMMAND], ["R", ""], + mappings.add([modes.NORMAL], ["R", ""], "Reload while skipping the cache", function () { tabs.reload(tabs.getTab(), true); }); @@ -1724,62 +1895,62 @@ var Buffer = Module("buffer", { }); // zooming - mappings.add([modes.COMMAND], ["zi", "+", ""], + mappings.add([modes.NORMAL], ["zi", "+", ""], "Enlarge text zoom of current web page", function (args) { buffer.zoomIn(Math.max(args.count, 1), false); }, { count: true }); - mappings.add([modes.COMMAND], ["zm", ""], + mappings.add([modes.NORMAL], ["zm", ""], "Enlarge text zoom of current web page by a larger amount", function (args) { buffer.zoomIn(Math.max(args.count, 1) * 3, false); }, { count: true }); - mappings.add([modes.COMMAND], ["zo", "-", ""], + mappings.add([modes.NORMAL], ["zo", "-", ""], "Reduce text zoom of current web page", function (args) { buffer.zoomOut(Math.max(args.count, 1), false); }, { count: true }); - mappings.add([modes.COMMAND], ["zr", ""], + mappings.add([modes.NORMAL], ["zr", ""], "Reduce text zoom of current web page by a larger amount", function (args) { buffer.zoomOut(Math.max(args.count, 1) * 3, false); }, { count: true }); - mappings.add([modes.COMMAND], ["zz", ""], + mappings.add([modes.NORMAL], ["zz", ""], "Set text zoom value of current web page", function (args) { buffer.setZoom(args.count > 1 ? args.count : 100, false); }, { count: true }); - mappings.add([modes.COMMAND], ["ZI", "zI", ""], + mappings.add([modes.NORMAL], ["ZI", "zI", ""], "Enlarge full zoom of current web page", function (args) { buffer.zoomIn(Math.max(args.count, 1), true); }, { count: true }); - mappings.add([modes.COMMAND], ["ZM", "zM", ""], + mappings.add([modes.NORMAL], ["ZM", "zM", ""], "Enlarge full zoom of current web page by a larger amount", function (args) { buffer.zoomIn(Math.max(args.count, 1) * 3, true); }, { count: true }); - mappings.add([modes.COMMAND], ["ZO", "zO", ""], + mappings.add([modes.NORMAL], ["ZO", "zO", ""], "Reduce full zoom of current web page", function (args) { buffer.zoomOut(Math.max(args.count, 1), true); }, { count: true }); - mappings.add([modes.COMMAND], ["ZR", "zR", ""], + mappings.add([modes.NORMAL], ["ZR", "zR", ""], "Reduce full zoom of current web page by a larger amount", function (args) { buffer.zoomOut(Math.max(args.count, 1) * 3, true); }, { count: true }); - mappings.add([modes.COMMAND], ["zZ", ""], + mappings.add([modes.NORMAL], ["zZ", ""], "Set full zoom value of current web page", function (args) { buffer.setZoom(args.count > 1 ? args.count : 100, true); }, { count: true }); // page info - mappings.add([modes.COMMAND], ["", ""], + mappings.add([modes.NORMAL], ["", ""], "Print the current file name", function () { buffer.showPageInfo(false); }); - mappings.add([modes.COMMAND], ["g", ""], + mappings.add([modes.NORMAL], ["g", ""], "Print file information", function () { buffer.showPageInfo(true); }); }, @@ -1817,6 +1988,23 @@ var Buffer = Module("buffer", { validator: function (value) RegExp(value) }); + options.add(["jumptags", "jt"], + "XPath or CSS selector strings of jumpable elements for extended hint modes", + "stringmap", { + "p": "p,table,ul,ol,blockquote", + "h": "h1,h2,h3,h4,h5,h6" + }, + { + keepQuotes: true, + setter: function (vals) { + for (let [k, v] in Iterator(vals)) + vals[k] = update(new String(v), { matcher: util.compileMatcher(Option.splitList(v)) }); + return vals; + }, + validator: function (value) util.validateMatcher.call(this, value) + && Object.keys(value).every(function (v) v.length == 1) + }); + options.add(["nextpattern"], "Patterns to use when guessing the next page in a document sequence", "regexplist", UTF8("'\\bnext\\b',^>$,^(>>|»)$,^(>|»),(>|»)$,'\\bmore\\b'"), @@ -1829,7 +2017,7 @@ var Buffer = Module("buffer", { options.add(["pageinfo", "pa"], "Define which sections are shown by the :pageinfo command", - "charlist", "gfm", + "charlist", "gesfm", { get values() values(buffer.pageInfo).toObject() }); options.add(["scroll", "scr"],