diff --git a/common/content/buffer.js b/common/content/buffer.js index 19d69cc4..cd7221b9 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -135,6 +135,19 @@ function Buffer() //{{{ pageInfo[option] = [fn, title]; } + function openUploadPrompt(elem) + { + commandline.input("Upload file: ", function (path) { + let file = io.getFile(path); + + if (!file.exists()) + return liberator.beep(); + + elem.value = file.path; + }, + { completer: completion.file, default: elem.value }); + } + /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// OPTIONS ///////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ @@ -360,16 +373,13 @@ function Buffer() //{{{ function (count) { if (count < 1 && buffer.lastInputField) - { - buffer.lastInputField.focus(); - } + buffer.focusElement(buffer.lastInputField); else { let elements = []; let matches = buffer.evaluateXPath( - // TODO: type="file" - "//input[not(@type) or @type='text' or @type='password'] | //textarea[not(@disabled) and not(@readonly)] |" + - "//xhtml:input[not(@type) or @type='text' or @type='password'] | //xhtml:textarea[not(@disabled) and not(@readonly)]" + "//input[not(@type) or @type='text' or @type='password' or @type='file'] | //textarea[not(@disabled) and not(@readonly)] |" + + "//xhtml:input[not(@type) or @type='text' or @type='password' or @type='file'] | //xhtml:textarea[not(@disabled) and not(@readonly)]" ); for (let match in matches) @@ -382,12 +392,10 @@ function Buffer() //{{{ if (elements.length > 0) { count = Math.min(Math.max(count, 1), elements.length); - elements[count - 1].focus(); + buffer.focusElement(elements[count - 1]); } else - { liberator.beep(); - } } }, { flags: Mappings.flags.COUNT }); @@ -509,6 +517,14 @@ function Buffer() //{{{ ////////////////////// COMMANDS //////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + commands.add(["frameo[nly]"], + "Show only the current frame's page", + function (args) + { + liberator.open(tabs.localStore.focusedFrame.document.documentURI); + }, + { argCount: "0" }); + commands.add(["ha[rdcopy]"], "Print current document", function (args) @@ -1063,18 +1079,10 @@ function Buffer() //{{{ elem.contentWindow.focus(); return; } - else if (elemTagName == "input" && elem.getAttribute('type').toLowerCase() == "file") + else if (elemTagName == "input" && elem.type.toLowerCase() == "file") { - commandline.input("Upload file: ", function (path) - { - let file = io.getFile(path); - - if (!file.exists()) - return liberator.beep(); - - elem.value = file.path; - } - , {completer: completion.file, default: elem.value}); + openUploadPrompt(elem); + buffer.lastInputField = elem; return; } @@ -1182,18 +1190,9 @@ function Buffer() //{{{ offsetX = Number(coords[0]) + 1; offsetY = Number(coords[1]) + 1; } - else if (localName == "input" && elem.getAttribute('type').toLowerCase() == "file") + else if (localName == "input" && elem.type.toLowerCase() == "file") { - commandline.input("Upload file: ", function (path) - { - let file = io.getFile(path); - - if (!file.exists()) - return liberator.beep(); - - elem.value = file.path; - } - , {completer: completion.file, default: elem.value}); + openUploadPrompt(elem); return; } @@ -1931,7 +1930,7 @@ function Marks() //{{{ } if (!ok) - liberator.echoerr("E20: Mark not set"); // FIXME: move up? + liberator.echoerr("E20: Mark not set"); }, /** diff --git a/common/content/commands.js b/common/content/commands.js index b6a2bdd7..420b78f8 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -34,13 +34,25 @@ the terms of any one of the MPL, the GPL or the LGPL. * A class representing Ex commands. Instances are created by * the {@link Commands} class. * + * @param {string[]} specs The names by which this command can be invoked. + * These are specified in the form "com[mand]" where "com" is a unique + * command name prefix. + * @param {string} description A short one line description of the command. + * @param {function} action The action invoked by this command when executed. + * @param {Object} extraInfo An optional extra configuration hash. The + * following properties are supported. + * argCount - See (@link Command#argCount) + * bang - See (@link Command#bang) + * completer - See (@link Command#completer) + * count - See (@link Command#count) + * heredoc - See (@link Command#heredoc) + * literal - See (@link Command#literal) + * options - See (@link Command#options) + * serial - See (@link Command#serial) * @private */ function Command(specs, description, action, extraInfo) //{{{ { - if (!specs || !action) - return null; - if (!extraInfo) extraInfo = {}; @@ -91,7 +103,7 @@ function Command(specs, description, action, extraInfo) //{{{ /** @property {string[]} All of this command's long and short names. */ this.names = expandedSpecs.names; // return all command name aliases - /** @property {string} This command's description, as shown in :exinfo */ + /** @property {string} This command's description, as shown in :exusage */ this.description = description || ""; /** @property {function (Args)} The function called to execute this command. */ this.action = action; diff --git a/common/content/completion.js b/common/content/completion.js index c37e9cd5..cf0a142f 100644 --- a/common/content/completion.js +++ b/common/content/completion.js @@ -46,7 +46,6 @@ the terms of any one of the MPL, the GPL or the LGPL. * @author Kris Maglione * @constructor */ - function CompletionContext(editor, name, offset) //{{{ { if (!(this instanceof arguments.callee)) diff --git a/common/content/editor.js b/common/content/editor.js index f2952281..4245b5ad 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -690,7 +690,7 @@ function Editor() //{{{ // motion = b, 0, gg, G, etc. executeCommandWithMotion: function (cmd, motion, count) { - if (!typeof count == "number" || count < 1) + if (typeof count != "number" || count < 1) count = 1; if (cmd == motion) @@ -705,12 +705,12 @@ function Editor() //{{{ { case "j": this.executeCommand("cmd_beginLine", 1); - this.executeCommand("cmd_selectLineNext", count+1); + this.executeCommand("cmd_selectLineNext", count + 1); break; case "k": this.executeCommand("cmd_beginLine", 1); this.executeCommand("cmd_lineNext", 1); - this.executeCommand("cmd_selectLinePrevious", count+1); + this.executeCommand("cmd_selectLinePrevious", count + 1); break; case "h": this.executeCommand("cmd_selectCharPrevious", count); diff --git a/common/content/events.js b/common/content/events.js index 3e643cdc..18acc004 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -1733,38 +1733,6 @@ function Events() //{{{ // Stub for something else, presumably. Not in any documented // interface. onLinkIconAvailable: function () {} - }, - - // TODO: move to options.js? - prefObserver: { - register: function () - { - // better way to monitor all changes? - this._branch = services.get("pref").getBranch("").QueryInterface(Ci.nsIPrefBranch2); - this._branch.addObserver("", this, false); - }, - - unregister: function () - { - if (this._branch) - this._branch.removeObserver("", this); - }, - - observe: function (aSubject, aTopic, aData) - { - if (aTopic != "nsPref:changed") - return; - - // aSubject is the nsIPrefBranch we're observing (after appropriate QI) - // aData is the name of the pref that's been changed (relative to aSubject) - switch (aData) - { - case "accessibility.browsewithcaret": - let value = options.getPref("accessibility.browsewithcaret", false); - liberator.mode = value ? modes.CARET : modes.NORMAL; - break; - } - } } }; //}}} @@ -1782,10 +1750,8 @@ function Events() //{{{ } catch (e) {} - self.prefObserver.register(); liberator.registerObserver("shutdown", function () { self.destroy(); - self.prefObserver.unregister(); }); window.addEventListener("keypress", wrapListener("onKeyPress"), true); diff --git a/common/content/finder.js b/common/content/finder.js index 765dc170..9c8e5560 100644 --- a/common/content/finder.js +++ b/common/content/finder.js @@ -30,7 +30,6 @@ the terms of any one of the MPL, the GPL or the LGPL. // TODO: proper backwards search - implement our own component? // : implement our own highlighter? -// : frameset pages // : should cancel search highlighting in 'incsearch' mode and jump // back to the presearch page location - can probably use the same // solution as marks @@ -48,8 +47,6 @@ function Finder() //{{{ ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - // FIXME: - //var self = this; // needed for callbacks since "this" is the "liberator" object in a callback var found = false; // true if the last search was successful var backwards = false; // currently searching backwards var searchString = ""; // current search string (without modifiers) diff --git a/common/content/hints.js b/common/content/hints.js index 846bb8cf..0d93a315 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -638,8 +638,8 @@ function Hints() //{{{ * @param {boolean} allowWordOverleaping Whether to allow non-contiguous * words to match. * - * @return {function(String):bool} A function that will filter only - * hints that match as above. + * @return {function(String):boolean} A function that will filter only + * hints that match as above. */ function wordStartsWithMatcher(hintString, allowWordOverleaping) //{{{ { @@ -886,7 +886,7 @@ function Hints() //{{{ context.completions = [[k, v.prompt] for ([k, v] in Iterator(hintModes))]; }, onChange: function () { modes.pop() }, - onCancel: function (arg) { arg && setTimeout(function () hints.show(arg), 0); }, + onCancel: function (arg) { arg && setTimeout(function () hints.show(arg), 0); } }); }, { flags: Mappings.flags.COUNT }); @@ -897,14 +897,16 @@ function Hints() //{{{ return { /** - * Create a new hint mode + * Creates a new hint mode. * * @param {String} mode The letter that identifies this mode. - * @param {String} description The description to display to the user about this mode. - * @param {function(Node)} callback The function to be called with the element that matches. - * @param {function():String} selector The function that returns an XPath selector to decide - * which elements can be hinted (the default returns - * options.hinttags) + * @param {String} description The description to display to the user + * about this mode. + * @param {function(Node)} callback The function to be called with the + * element that matches. + * @param {function():String} selector The function that returns an + * XPath selector to decide which elements can be hinted (the + * default returns options.hinttags). */ addMode: function (mode) { @@ -912,11 +914,11 @@ function Hints() //{{{ }, /** - * Update the display of hints. + * Updates the display of hints. * * @param {String} minor Which hint mode to use. * @param {String} filter The filter to use. - * @param {Object} win The window in which we are showing hints + * @param {Object} win The window in which we are showing hints. */ show: function (minor, filter, win) { @@ -968,7 +970,7 @@ function Hints() //{{{ }, /** - * Handle an event + * Handle an event. * * @param {Event} event The event to handle. */ diff --git a/common/content/io.js b/common/content/io.js index a3f3a248..cd05d0e9 100644 --- a/common/content/io.js +++ b/common/content/io.js @@ -335,7 +335,7 @@ function IO() //{{{ function () { let list = template.tabular(["", "Filename"], ["text-align: right; padding-right: 1em;"], - ([i + 1, file] for ([i, file] in Iterator(scriptNames)))); // TODO: add colon? + ([i + 1, file] for ([i, file] in Iterator(scriptNames)))); // TODO: add colon and remove column titles for pedantic Vim compatibility? commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE); }, @@ -345,11 +345,13 @@ function IO() //{{{ "Read Ex commands from a file", function (args) { - // FIXME: "E172: Only one file name allowed" - io.source(args[0], args.bang); + if (args.length > 1) + liberator.echoerr("E172: Only one file name allowed"); + else + io.source(args[0], args.bang); }, { - argCount: "1", + argCount: "+", // FIXME: should be "1" but kludged for proper error message bang: true, completer: function (context) completion.file(context, true) }); @@ -878,7 +880,6 @@ lookup: let dirs = getPathsFromPathList(options["runtimepath"]); let found = false; - // FIXME: should use original arg string liberator.echomsg("Searching for \"" + paths.join(" ") + "\" in \"" + options["runtimepath"] + "\"", 2); outer: @@ -902,7 +903,7 @@ lookup: } if (!found) - liberator.echomsg("not found in 'runtimepath': \"" + paths.join(" ") + "\"", 1); // FIXME: should use original arg string + liberator.echomsg("not found in 'runtimepath': \"" + paths.join(" ") + "\"", 1); return found; }, @@ -1001,7 +1002,7 @@ lookup: { let lineNumber = i + 1; - // FIXME: messages need to be able to specify + // TODO: messages need to be able to specify // whether they can be cleared/overwritten or // should be appended to and the MOW opened liberator.echoerr("Error detected while processing " + file.path, commandline.FORCE_MULTILINE); diff --git a/common/content/liberator.js b/common/content/liberator.js index 8894cf3a..0b1cf852 100644 --- a/common/content/liberator.js +++ b/common/content/liberator.js @@ -1069,7 +1069,7 @@ const liberator = (function () //{{{ * Opens one or more URLs. Returns true when load was initiated, or * false on error. * - * @param {FIXME} urls Either a URL string or an array of URLs. + * @param {string|string[]} urls Either a URL string or an array of URLs. * The array can look like this: * ["url1", "url2", "url3", ...] * or: diff --git a/common/content/mappings.js b/common/content/mappings.js index d8502044..d110a4c2 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -30,33 +30,68 @@ the terms of any one of the MPL, the GPL or the LGPL. // Do NOT create instances of this class yourself, use the helper method // mappings.add() instead -function Map(modes, cmds, description, action, extraInfo) //{{{ +/** + * A class representing key mappings. Instances are created by the + * {@link Mappings} class. + * + * @param {number[]} modes The modes in which this mapping is active. + * @param {string[]} keys The key sequences which are bound to + * action. + * @param {string} description A short one line description of the key mapping. + * @param {function} action The action invoked by each key sequence. + * @param {Object} extraInfo An optional extra configuration hash. The + * following properties are supported. + * flags - See (@link Map#flags) + * noremap - See (@link Map#noremap) + * rhs - See (@link Map#rhs) + * silent - See (@link Map#silent) + * @private + */ +function Map(modes, keys, description, action, extraInfo) //{{{ { - if (!modes || (!cmds || !cmds.length) || !action) - return null; - if (!extraInfo) extraInfo = {}; + /** @property {number[]} All of the modes for which this mapping applies. */ this.modes = modes; - // only store keysyms with uppercase modifier strings - this.names = cmds.map(function (cmd) cmd.replace(/[casm]-/g, String.toUpperCase)); + /** @property {string[]} All of this mapping's names (key sequences). */ + this.names = keys.map(function (cmd) cmd.replace(/[casm]-/g, String.toUpperCase)); // only store keysyms with uppercase modifier strings + /** @property {function (number)} The function called to execute this mapping. */ this.action = action; + /** @property {number} See (@link Mappings#flags) */ this.flags = extraInfo.flags || 0; + /** @property {string} This mapping's description, as shown in :viusage. */ this.description = description || ""; + /** @property {string} The literal RHS expansion of this mapping. */ this.rhs = extraInfo.rhs || null; + /** @property {boolean} Whether the RHS of the mapping should expand mappings recursively. */ this.noremap = extraInfo.noremap || false; + /** @property {boolean} Whether any output from the mapping should be echoed on the command line. */ this.silent = extraInfo.silent || false; }; Map.prototype = { - hasName: function (name) - { - return this.names.indexOf(name) >= 0; - }, + /** + * Returns whether this mapping can be invoked by a key sequence matching + * name. + * + * @param {string} name The name to query. + * @returns {boolean} + */ + hasName: function (name) this.names.indexOf(name) >= 0, + /** + * Execute the action for this mapping. + * + * @param {string} motion The motion argument if accepted by this mapping. + * E.g. "w" for "dw" + * @param {number} count The associated count. E.g. "5" for "5j" + * @default -1 + * @param {string} argument The normal argument if accepted by this + * mapping. E.g. "a" for "ma" + */ execute: function (motion, count, argument) { let args = []; @@ -100,9 +135,9 @@ function Mappings() //{{{ { let where = userMap ? user : main; map.modes.forEach(function (mode) { - if (!(mode in where)) - where[mode] = []; - where[mode].push(map); + if (!(mode in where)) + where[mode] = []; + where[mode].push(map); }); } @@ -255,6 +290,7 @@ function Mappings() //{{{ /////////////////////////////////////////////////////////////////////////////{{{ addMapCommands("", [modes.NORMAL, modes.VISUAL], ""); + for (let mode in modes.mainModes) if (mode.char) addMapCommands(mode.char, @@ -296,13 +332,40 @@ function Mappings() //{{{ __iterator__: function () mappingsIterator([modes.NORMAL], main), // used by :mkvimperatorrc to save mappings + /** + * Returns a user-defined mappings iterator for the specified + * mode. + * + * @param {number} mode The mode to return mappings from. + * @returns {Iterator(Map)} + */ getUserIterator: function (mode) mappingsIterator(mode, user), + /** + * Add a new default key mapping. + * + * @param {number[]} modes The modes that this mapping applies to. + * @param {string[]} keys The key sequences which are bound to + * action. + * @param {string} description A description of the key mapping. + * @param {function} action The action invoked by each key sequence. + * @param {Object} extra An optional extra configuration hash. + */ add: function (modes, keys, description, action, extra) { addMap(new Map(modes, keys, description, action, extra), false); }, + /** + * Add a new user-defined key mapping. + * + * @param {number[]} modes The modes that this mapping applies to. + * @param {string[]} keys The key sequences which are bound to + * action. + * @param {string} description A description of the key mapping. + * @param {function} action The action invoked by each key sequence. + * @param {Object} extra An optional extra configuration hash. + */ addUserMap: function (modes, keys, description, action, extra) { keys = keys.map(expandLeader); @@ -318,20 +381,41 @@ function Mappings() //{{{ addMap(map, true); }, + /** + * Returns the map from mode named cmd. + * + * @param {number} mode The mode to search. + * @param {string} cmd The map name to match. + * @returns {Map} + */ get: function (mode, cmd) { mode = mode || modes.NORMAL; return getMap(mode, cmd, user) || getMap(mode, cmd, main); }, + /** + * Returns the default map from mode named cmd. + * + * @param {number} mode The mode to search. + * @param {string} cmd The map name to match. + * @returns {Map} + */ getDefault: function (mode, cmd) { mode = mode || modes.NORMAL; return getMap(mode, cmd, main); }, - // returns an array of mappings with names which START with "cmd" (but are NOT "cmd") - getCandidates: function (mode, cmd) + /** + * Returns an array of maps with names starting with but not equal to + * prefix. + * + * @param {number} mode The mode to search. + * @param {string} prefix The map prefix string to match. + * @returns {Map[]} + */ + getCandidates: function (mode, prefix) { let mappings = user[mode].concat(main[mode]); let matches = []; @@ -340,10 +424,10 @@ function Mappings() //{{{ { for (let [,name] in Iterator(map.names)) { - if (name.indexOf(cmd) == 0 && name.length > cmd.length) + if (name.indexOf(prefix) == 0 && name.length > prefix.length) { // for < only return a candidate if it doesn't look like a mapping - if (cmd != "<" || !/^<.+>/.test(name)) + if (prefix != "<" || !/^<.+>/.test(name)) matches.push(map); } } @@ -352,28 +436,57 @@ function Mappings() //{{{ return matches; }, + /* + * Returns the map leader string used to replace the special token + * "" when user mappings are defined. + * + * @returns {string} + */ + // FIXME: property getMapLeader: function () { let leaderRef = liberator.variableReference("mapleader"); return leaderRef[0] ? leaderRef[0][leaderRef[1]] : "\\"; }, - // returns whether the user added a custom user map - hasMap: function (mode, cmd) - { - return user[mode].some(function (map) map.hasName(cmd)); - }, + /** + * Returns whether there is a user-defined mapping cmd for the + * specified mode. + * + * @param {number} mode The mode to search. + * @param {string} cmd The candidate key mapping. + * @returns {boolean} + */ + hasMap: function (mode, cmd) user[mode].some(function (map) map.hasName(cmd)), + /** + * Remove the user-defined mapping named cmd for mode. + * + * @param {number} mode The mode to search. + * @param {string} cmd The map name to match. + */ remove: function (mode, cmd) { removeMap(mode, cmd); }, + /** + * Remove all user-defined mappings for mode. + * + * @param {number} mode The mode to remove all mappings from. + */ removeAll: function (mode) { user[mode] = []; }, + /** + * Lists all user-defined mappings matching filter for the + * specified modes. + * + * @param {number[]} modes An array of modes to search. + * @param {string} filter The filter string to match. + */ list: function (modes, filter) { let modeSign = ""; @@ -413,7 +526,6 @@ function Mappings() //{{{ } commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE); } - }; //}}} }; //}}} diff --git a/common/content/options.js b/common/content/options.js index 4973a957..da19b982 100644 --- a/common/content/options.js +++ b/common/content/options.js @@ -32,9 +32,6 @@ the terms of any one of the MPL, the GPL or the LGPL. // options.add() instead function Option(names, description, type, defaultValue, extraInfo) //{{{ { - if (!names || !type) - return null; - if (!extraInfo) extraInfo = {}; @@ -808,7 +805,7 @@ function Options() //{{{ .map(function (pref) [pref, ""])]); }); - return { + const self = { OPTION_SCOPE_GLOBAL: 1, OPTION_SCOPE_LOCAL: 2, @@ -820,6 +817,37 @@ function Options() //{{{ return (v for ([k, v] in Iterator(sorted))); }, + prefObserver: { + register: function () + { + // better way to monitor all changes? + this._branch = services.get("pref").getBranch("").QueryInterface(Ci.nsIPrefBranch2); + this._branch.addObserver("", this, false); + }, + + unregister: function () + { + if (this._branch) + this._branch.removeObserver("", this); + }, + + observe: function (subject, topic, data) + { + if (topic != "nsPref:changed") + return; + + // subject is the nsIPrefBranch we're observing (after appropriate QI) + // data is the name of the pref that's been changed (relative to subject) + switch (data) + { + case "accessibility.browsewithcaret": + let value = options.getPref("accessibility.browsewithcaret", false); + liberator.mode = value ? modes.CARET : modes.NORMAL; + break; + } + } + }, + add: function (names, description, type, defaultValue, extraInfo) { if (!extraInfo) @@ -1067,8 +1095,15 @@ function Options() //{{{ this.popContext(); } } - }; - //}}} + }; //}}} + + self.prefObserver.register(); + liberator.registerObserver("shutdown", function () { + self.prefObserver.unregister(); + }); + + return self; + }; //}}} // vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/tabs.js b/common/content/tabs.js index c3d9f059..6eb98d67 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -30,6 +30,9 @@ the terms of any one of the MPL, the GPL or the LGPL. // TODO: many methods do not work with Thunderbird correctly yet +/** + * @instance tabs + */ function Tabs() //{{{ { //////////////////////////////////////////////////////////////////////////////// @@ -307,7 +310,6 @@ function Tabs() //{{{ if (arg) { - arg = arg.toLowerCase(); let removed = 0; let matches = arg.match(/^(\d+):?/); @@ -318,15 +320,27 @@ function Tabs() //{{{ } else { + let str = arg.toLowerCase(); let browsers = getBrowser().browsers; + for (let i = browsers.length - 1; i >= 0; i--) { - let title = browsers[i].contentTitle.toLowerCase() || ""; - let uri = browsers[i].currentURI.spec.toLowerCase(); - let host = browsers[i].currentURI.host.toLowerCase(); + let host, title, uri = browsers[i].currentURI.spec; + if (browsers[i].currentURI.schemeIs("about")) + { + host = ""; + title = "(Untitled)"; + } + else + { + host = browsers[i].currentURI.host; + title = browsers[i].contentTitle; + } - if (host.indexOf(arg) >= 0 || uri == arg || - (special && (title.indexOf(arg) >= 0 || uri.indexOf(arg) >= 0))) + [host, title, uri] = [host, title, uri].map(String.toLowerCase); + + if (host.indexOf(str) >= 0 || uri == str || + (special && (title.indexOf(str) >= 0 || uri.indexOf(str) >= 0))) { tabs.remove(tabs.getTab(i)); removed++; @@ -398,7 +412,7 @@ function Tabs() //{{{ if (arg) { if (/^\d+$/.test(arg)) - tabs.select("-" + arg, true); // FIXME: urgh! + tabs.select("-" + arg, true); else liberator.echoerr("E488: Trailing characters"); } @@ -676,8 +690,16 @@ function Tabs() //{{{ return { + /** + * @property {Object} The previously accessed tab or null if no tab + * other than the current one has been accessed. + */ get alternate() alternates[1], + /** + * @property {Iterator(Object)} A genenerator that returns all browsers + * in the current window. + */ get browsers() { let browsers = getBrowser().browsers; @@ -685,10 +707,13 @@ function Tabs() //{{{ yield [i, browsers[i]]; }, - get tabsBound() { - return Boolean(styles.get(true, "tab-binding")) - }, - set tabsBound(val) { + /** + * @property {boolean} Whether the tab numbering XBL binding has been + * applied. + */ + get tabsBound() Boolean(styles.get(true, "tab-binding")), + set tabsBound(val) + { let fragment = liberator.has("MacUnix") ? "tab-mac" : "tab"; if (!val) styles.removeSheet(true, "tab-binding"); @@ -699,8 +724,14 @@ function Tabs() //{{{ ".tabbrowser-tab[busy] > .tab-icon > .tab-icon-image { list-style-image: url('chrome://global/skin/icons/loading_16.png') !important; }"); }, + /** + * @property {number} The number of tabs in the current window. + */ get count() getBrowser().mTabs.length, + /** + * @property {Object} The local options store for the current tab. + */ get options() { let store = this.localStore; @@ -709,6 +740,15 @@ function Tabs() //{{{ return store.options; }, + /** + * Returns the local state store for the tab at the specified + * tabIndex. If tabIndex is not specified then the + * current tab is used. + * + * @param {number} tabIndex + * @returns {Object} + */ + // FIXME: why not a tab arg? Why this and the property? getLocalStore: function (tabIndex) { let tab = this.getTab(tabIndex); @@ -717,8 +757,15 @@ function Tabs() //{{{ return tab.liberatorStore; }, + /** + * @property {Object} The local state store for the currently selected + * tab. + */ get localStore() this.getLocalStore(), + /** + * @property {Object} The tab browser strip. + */ get tabStrip() { let tabStrip = null; @@ -732,18 +779,40 @@ function Tabs() //{{{ return tabStrip; }, - // @returns the index of the currently selected tab starting with 0 + /** + * @property {Object[]} The array of closed tabs for the current + * session. + */ + get closedTabs() + { + return services.get("json").decode(services.get("sessionStore").getClosedTabData(window)); + }, + + /** + * Returns the index of tab or the index of the currently + * selected tab if tab is not specified. This is a 0-based + * index. + * + * @param {Object} tab A tab from the current tab list. + * @returns {number} + */ index: function (tab) { if (tab) return Array.indexOf(getBrowser().mTabs, tab); - - return getBrowser().mTabContainer.selectedIndex; + else + return getBrowser().mTabContainer.selectedIndex; }, // TODO: implement filter - // @returns an array of tabs which match filter - get: function (filter) + /** + * Returns an array of all tabs in the tab list. + * + * @returns {Object[]} + */ + // FIXME: why not return the tab element? + // : unused? Remove me. + get: function () { let buffers = []; for (let [i, browser] in this.browsers) @@ -756,6 +825,13 @@ function Tabs() //{{{ return buffers; }, + /** + * Returns the index of the tab containing content. + * + * @param {Object} content Either a content window or a content + * document. + */ + // FIXME: Only called once...necessary? getContentIndex: function (content) { for (let [i, browser] in this.browsers) @@ -766,6 +842,14 @@ function Tabs() //{{{ return -1; }, + /** + * Returns the tab at the specified index or the currently + * selected tab if index is not specified. This is a 0-based + * index. + * + * @param {number} index The index of the tab required. + * @returns {Object} + */ getTab: function (index) { if (index != undefined) @@ -774,26 +858,44 @@ function Tabs() //{{{ return getBrowser().mCurrentTab; }, - get closedTabs() - { - return services.get("json").decode(services.get("sessionStore").getClosedTabData(window)); - }, - + /** + * Lists all tabs matching filter. + * + * @param {string} filter A filter matching a substring of the tab's + * document title or URL. + */ list: function (filter) { completion.listCompleter("buffer", filter); }, - // wrap causes the movement to wrap around the start and end of the tab list - // NOTE: position is a 0 based index + /** + * Moves a tab to a new position in the tab list. + * + * @param {Object} tab The tab to move. + * @param {string} spec See {@link indexFromSpec}. + * @param {boolean} wrap Whether an out of bounds spec causes + * the destination position to wrap around the start/end of the tab + * list. + */ move: function (tab, spec, wrap) { let index = indexFromSpec(spec, wrap); getBrowser().moveTabTo(tab, index); }, - // quitOnLastTab = 1: quit without saving session - // quitOnLastTab = 2: quit and save session + /** + * Removes the specified tab from the tab list. + * + * @param {Object} tab + * @param {number} count + * @param {boolean} focusLeftTab Focus the tab to the left of the removed tab. + * @param {number} quitOnLastTab Whether to quit if the tab being + * deleted is the only tab in the tab list: + * 1 - quit without saving session + * 2 - quit and save session + */ + // FIXME: what is quitOnLastTab {1,2} all about then, eh? --djk remove: function (tab, count, focusLeftTab, quitOnLastTab) { let removeOrBlankTab = { @@ -874,25 +976,38 @@ function Tabs() //{{{ } }, + /** + * Removes all tabs from the tab list except the specified tab. + * + * @param {Object} tab The tab to keep. + */ keepOnly: function (tab) { getBrowser().removeAllTabsBut(tab); }, + /** + * Selects the tab at the position specified by spec. + * + * @param {string} spec See {@link indexFromSpec} + * @param {boolean} wrap Whether an out of bounds spec causes + * the selection position to wrap around the start/end of the tab + * list. + */ select: function (spec, wrap) { let index = indexFromSpec(spec, wrap); // FIXME: if (index == -1) { - liberator.beep(); // XXX: move to ex-handling? + liberator.beep(); return; } getBrowser().mTabContainer.selectedIndex = index; }, /** - * Reload the specified tab. + * Reloads the specified tab. * * @param {Object} tab The tab to reload. * @param {boolean} bypassCache Whether to bypass the cache when @@ -912,7 +1027,7 @@ function Tabs() //{{{ }, /** - * Reload all tabs. + * Reloads all tabs. * * @param {boolean} bypassCache Whether to bypass the cache when * reloading. @@ -941,7 +1056,7 @@ function Tabs() //{{{ }, /** - * Stop loading the specified tab. + * Stops loading the specified tab. * * @param {Object} tab The tab to stop loading. */ @@ -954,7 +1069,7 @@ function Tabs() //{{{ }, /** - * Stop loading all tabs. + * Stops loading all tabs. */ stopAll: function () { @@ -962,8 +1077,20 @@ function Tabs() //{{{ browser.stop(); }, - // "buffer" is a string which matches the URL or title of a buffer, if it - // is null, the last used string is used again + /** + * Selects the tab containing the specified buffer. + * + * @param {string} buffer A string which matches the URL or title of a + * buffer, if it is null, the last used string is used again. + * @param {boolean} allowNonUnique Whether to select the first of + * multiple matches. + * @param {number} count If there are multiple matches select the + * count'th match. + * @param {boolean} reverse Whether to search the buffer list in + * reverse order. + * + */ + // FIXME: help! switchTo: function (buffer, allowNonUnique, count, reverse) { if (buffer == "") @@ -1037,6 +1164,12 @@ function Tabs() //{{{ } }, + /** + * Clones the specified tab and append it to the tab list. + * + * @param {Object} tab The tab to clone. + * @param {boolean} activate Whether to select the newly cloned tab. + */ cloneTab: function (tab, activate) { let newTab = getBrowser().addTab(); @@ -1048,6 +1181,12 @@ function Tabs() //{{{ return newTab; }, + /** + * Detaches the specified tab and open it in a new window. If no + * tab is specified the currently selected tab is detached. + * + * @param {Object} tab The tab to detach. + */ detachTab: function (tab) { if (!tab) @@ -1060,6 +1199,9 @@ function Tabs() //{{{ this.remove(tab, 1, false, 1); }, + /** + * Selects the alternate tab. + */ selectAlternateTab: function () { if (tabs.alternate == null || tabs.getTab() == tabs.alternate) @@ -1085,6 +1227,10 @@ function Tabs() //{{{ // tab that was selected when the session was created. As a result the // alternate after a restart is often incorrectly tab 1 when there // shouldn't be one yet. + /** + * Called on each TabSelect event to update the tab selection history. + * See (@link tabs.alternate). + */ updateSelectionHistory: function () { alternates = [this.getTab(), alternates[0]]; diff --git a/common/content/template.js b/common/content/template.js index 85614d66..ee149ab2 100644 --- a/common/content/template.js +++ b/common/content/template.js @@ -44,12 +44,12 @@ const template = { return <>{xml}; }, - completionRow: function completionRow(item, class) + completionRow: function completionRow(item, highlightGroup) { if (typeof icon == "function") icon = icon(); - if (class) + if (highlightGroup) { var text = item[0] || ""; var desc = item[1] || ""; @@ -61,7 +61,7 @@ const template = { } // - return
+ return