diff options
author | Kris Maglione <kris@vimperator.org> | 2010-08-28 18:02:03 -0400 |
---|---|---|
committer | Kris Maglione <kris@vimperator.org> | 2010-08-28 18:02:03 -0400 |
commit | 4e40abe6b0521f0a4a031a1c02889b4e1dc50d83 (patch) | |
tree | b78b0b1358381685ea9e13995bee0b8c0948da70 | |
parent | e3a2df403dcd41dd2dcf0dbae7e0483c92938582 (diff) | |
download | pentadactyl-4e40abe6b0521f0a4a031a1c02889b4e1dc50d83.tar.gz |
Lots of rangefinder and Gecko 2 tabbing fixes.
30 files changed, 845 insertions, 872 deletions
diff --git a/common/content/autocommands.js b/common/content/autocommands.js index fe52f5b9..0e163b5c 100755 --- a/common/content/autocommands.js +++ b/common/content/autocommands.js @@ -119,7 +119,7 @@ const AutoCommands = Module("autocommands", { let autoCmds = this._store.filter(function (autoCmd) autoCmd.event == event); - dactyl.echomsg("Executing " + event + " Auto commands for \"*\"", 8); + dactyl.echomsg('Executing ' + event + ' Auto commands for "*"', 8); let lastPattern = null; let url = args.url || ""; @@ -127,7 +127,7 @@ const AutoCommands = Module("autocommands", { for (let [, autoCmd] in Iterator(autoCmds)) { if (autoCmd.pattern.test(url)) { if (!lastPattern || lastPattern.source != autoCmd.pattern.source) - dactyl.echomsg("Executing " + event + " Auto commands for \"" + autoCmd.pattern.source + "\"", 8); + dactyl.echomsg("Executing " + event + " Auto commands for " + autoCmd.pattern.source.quote(), 8); lastPattern = autoCmd.pattern; dactyl.echomsg("autocommand " + autoCmd.command, 9); @@ -167,7 +167,7 @@ const AutoCommands = Module("autocommands", { if (event) { // NOTE: event can only be a comma separated list for |:au {event} {pat} {cmd}| - let validEvents = config.autocommands.map(function (event) event[0]); + let validEvents = keys(config.autocommands); validEvents.push("*"); events = event.split(","); @@ -227,7 +227,7 @@ const AutoCommands = Module("autocommands", { let [event, url] = args; let defaultURL = url || buffer.URL; - let validEvents = config.autocommands.map(function (e) e[0]); + let validEvents = keys(config.autocommands); // TODO: add command validators dactyl.assert(event != "*", @@ -257,10 +257,10 @@ const AutoCommands = Module("autocommands", { }); }, completion: function () { - JavaScript.setCompleter(this.get, [function () config.autocommands]); + JavaScript.setCompleter(this.get, [function () Iterator(config.autocommands)]); completion.autocmdEvent = function autocmdEvent(context) { - context.completions = config.autocommands; + context.completions = Iterator(config.autocommands); }; completion.macro = function macro(context) { @@ -273,7 +273,7 @@ const AutoCommands = Module("autocommands", { "List of autocommand event names which should be ignored", "stringlist", "", { - completer: function () config.autocommands.concat([["all", "All events"]]) + completer: function () Iterator(update({ all: "All Events" }, config.autocommands)) }); options.add(["strictfocus", "sf"], diff --git a/common/content/base.js b/common/content/base.js index 4eab72e7..b13a1bf1 100644 --- a/common/content/base.js +++ b/common/content/base.js @@ -88,13 +88,18 @@ function dict(ary) { } function set(ary) { - var obj = {}; + let obj = {}; if (ary) for (var i = 0; i < ary.length; i++) obj[ary[i]] = true; return obj; } -set.add = function (set, key) { set[key] = true; } +set.add = function (set, key) { + let res = this.has(set, key); + set[key] = true; + return res; +} +set.has = function (set, key) Object.prototype.hasOwnProperty.call(set, key); set.remove = function (set, key) { delete set[key]; } function iter(obj) { @@ -142,6 +147,8 @@ function isinstance(targ, src) { for (var i = 0; i < src.length; i++) { if (targ instanceof src[i]) return true; + if (typeof src[i] == "string") + return Object.prototype.toString(targ) == "[object " + src[i] + "]"; var type = types[typeof targ]; if (type && issubclass(src[i], type)) return true; diff --git a/common/content/bindings.xml b/common/content/bindings.xml index 8c6f2ebe..297fa8b8 100644 --- a/common/content/bindings.xml +++ b/common/content/bindings.xml @@ -12,15 +12,18 @@ <children/> </content> </binding> + <binding id="compitem-td"> <!-- No white space. The table is white-space: pre; :( --> <content><html:span class="td-strut"/><html:span class="td-span"><children/></html:span></content> </binding> + <binding id="tab" display="xul:hbox" - extends="chrome://global/content/bindings/tabbox.xml#tab"> - <content chromedir="ltr" closetabtext="Close Tab"> + extends="chrome://browser/content/tabbrowser.xml#tabbrowser-tab"> + <content context="tabContextMenu" closetabtext="Close Tab"> <xul:stack class="tab-icon dactyl-tab-stack"> - <xul:image xbl:inherits="validate,src=image" class="tab-icon-image" dactyl:highlight="TabIcon"/> + <xul:image xbl:inherits="validate,src=image" role="presentation" + class="tab-icon-image" dactyl:highlight="TabIcon"/> <xul:vbox> <xul:spring flex="1"/> <xul:label xbl:inherits="value=ordinal" dactyl:highlight="TabIconNumber"/> @@ -28,10 +31,16 @@ </xul:vbox> </xul:stack> <xul:label xbl:inherits="value=ordinal" dactyl:highlight="TabNumber"/> - <xul:label flex="1" xbl:inherits="value=label,crop,accesskey" class="tab-text" dactyl:highlight="TabText"/> - <xul:toolbarbutton anonid="close-button" tabindex="-1" class="tab-close-button" dactyl:highlight="TabClose"/> + <xul:label flex="1" + xbl:inherits="value=label,crop,accesskey" + class="tab-text" dactyl:highlight="TabText" + role="presentation"/> + <xul:toolbarbutton anonid="close-button" + tabindex="-1" + class="tab-close-button" dactyl:highlight="TabClose"/> </content> </binding> + <binding id="tab-mac" extends="chrome://browser/content/tabbrowser.xml#tabbrowser-tab"> <content chromedir="ltr" closetabtext="Close Tab"> diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js index 5da9abfd..7f7f546b 100644 --- a/common/content/bookmarks.js +++ b/common/content/bookmarks.js @@ -435,11 +435,11 @@ const Bookmarks = Module("bookmarks", { return dactyl.open(items.map(function (i) i.url), dactyl.NEW_TAB); if (filter.length > 0 && tags.length > 0) - dactyl.echoerr("E283: No bookmarks matching tags: \"" + tags + "\" and string: \"" + filter + "\""); + dactyl.echoerr("E283: No bookmarks matching tags: " + tags.quote() + " and string: " + filter.quote()); else if (filter.length > 0) - dactyl.echoerr("E283: No bookmarks matching string: \"" + filter + "\""); + dactyl.echoerr("E283: No bookmarks matching string: " + filter.quote()); else if (tags.length > 0) - dactyl.echoerr("E283: No bookmarks matching tags: \"" + tags + "\""); + dactyl.echoerr("E283: No bookmarks matching tags: " + tags.quote()); else dactyl.echoerr("No bookmarks set"); return null; @@ -497,7 +497,7 @@ const Bookmarks = Module("bookmarks", { dactyl.echomsg("Added bookmark: " + url + extra, 1, commandline.FORCE_SINGLELINE); } else - dactyl.echoerr("Exxx: Could not add bookmark `" + title + "'", commandline.FORCE_SINGLELINE); + dactyl.echoerr("Exxx: Could not add bookmark " + title.quote(), commandline.FORCE_SINGLELINE); }, { argCount: "?", bang: true, diff --git a/common/content/buffer.js b/common/content/buffer.js index 6c26c87b..3e8c9e94 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -582,7 +582,7 @@ const Buffer = Module("buffer", { let offsetX = 1; let offsetY = 1; - if (isinstance(elem [HTMLFrameElement, HTMLIFrameElement])) { + if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement])) { buffer.focusElement(elem); return; } @@ -1308,39 +1308,55 @@ const Buffer = Module("buffer", { }; completion.buffer = function buffer(context) { - filter = context.filter.toLowerCase(); - context.anchored = false; - context.title = ["Buffer", "URL"]; - context.keys = { text: "text", description: "url", icon: "icon" }; - context.compare = CompletionContext.Sort.number; - let process = context.process[0]; - context.process = [function (item, text) - <> - <span highlight="Indicator" style="display: inline-block; width: 1.5em; text-align: center">{item.item.indicator}</span> - { process.call(this, item, text) } - </>]; - - context.completions = util.map(tabs.browsers, function ([i, browser]) { - let indicator = " "; - if (i == tabs.index()) - indicator = "%" - else if (i == tabs.index(tabs.alternate)) - indicator = "#"; - - let tab = tabs.getTab(i); - let url = browser.contentDocument.location.href; - i = i + 1; - - return { - text: [i + ": " + (tab.label || "(Untitled)"), i + ": " + url], - url: template.highlightURL(url), - indicator: indicator, - icon: tab.image || DEFAULT_FAVICON - }; + let filter = context.filter.toLowerCase(); + let defItem = { parent: { getTitle: function () "" } }; + let tabGroups = {}; + tabs.getGroups(); + tabs.allTabs.forEach(function (tab, i) { + let group = (tab.tabItem || defItem).parent || defItem.parent; + if (!set.has(tabGroups, group.id)) + tabGroups[group.id] = [group.getTitle(), []]; + group = tabGroups[group.id]; + group[1].push([i, tab.linkedBrowser]); }); + + let orig = context; + for (let [id, [name, browsers]] in Iterator(tabGroups)) { + context = orig.fork(id, 0); + context.anchored = false; + context.title = [name || "Buffers"]; + context.keys = { text: "text", description: "url", icon: "icon" }; + context.compare = CompletionContext.Sort.number; + let process = context.process[0]; + context.process = [function (item, text) + <> + <span highlight="Indicator" style="display: inline-block; width: 1.5em; text-align: center">{item.item.indicator}</span> + { process.call(this, item, text) } + </>]; + + context.completions = util.map(util.Array.itervalues(browsers), function ([i, browser]) { + let indicator = " "; + if (i == tabs.index()) + indicator = "%" + else if (i == tabs.index(tabs.alternate)) + indicator = "#"; + + let tab = tabs.getTab(i); + let url = browser.contentDocument.location.href; + i = i + 1; + + return { + text: [i + ": " + (tab.label || "(Untitled)"), i + ": " + url], + url: template.highlightURL(url), + indicator: indicator, + icon: tab.image || DEFAULT_FAVICON + }; + }); + } }; }, events: function () { + /* window.XULBrowserWindow = this.progressListener; window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) @@ -1349,6 +1365,7 @@ const Buffer = Module("buffer", { .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIXULWindow) .XULBrowserWindow = this.progressListener; + */ try { config.browser.addProgressListener(this.progressListener, Ci.nsIWebProgress.NOTIFY_ALL); @@ -1592,7 +1609,7 @@ const Buffer = Module("buffer", { function (count) { buffer.zoomOut(Math.max(count, 1) * 3, true); }, { count: true }); - mappings.add(myModes, ["ZZ", "zZ"], + mappings.add(myModes, ["zZ"], "Set full zoom value of current web page", function (count) { buffer.fullZoom = count > 1 ? count : 100; }, { count: true }); diff --git a/common/content/commandline.js b/common/content/commandline.js index 66506668..f3088985 100644 --- a/common/content/commandline.js +++ b/common/content/commandline.js @@ -613,78 +613,83 @@ const CommandLine = Module("commandline", { * @private */ onEvent: function onEvent(event) { - let command = this.command; - - if (event.type == "blur") { - // prevent losing focus, there should be a better way, but it just didn't work otherwise - this.setTimeout(function () { - if (this._commandShown() && event.originalTarget == this._commandWidget.inputField) - this._commandWidget.inputField.focus(); - }, 0); - } - else if (event.type == "focus") { - if (!this._commandShown() && event.target == this._commandWidget.inputField) { - event.target.blur(); - dactyl.beep(); + try { + let command = this.command; + + if (event.type == "blur") { + // prevent losing focus, there should be a better way, but it just didn't work otherwise + this.setTimeout(function () { + if (this._commandShown() && event.originalTarget == this._commandWidget.inputField) + this._commandWidget.inputField.focus(); + }, 0); } - } - else if (event.type == "input") { - this.resetCompletions(); - commandline.triggerCallback("change", this._currentExtendedMode, command); - } - else if (event.type == "keypress") { - let key = events.toString(event); - if (this._completions) - this._completions.previewClear(); - if (!this._currentExtendedMode) - return; - - // user pressed <Enter> to carry out a command - // user pressing <Esc> is handled in the global onEscape - // FIXME: <Esc> should trigger "cancel" event - if (events.isAcceptKey(key)) { - let mode = this._currentExtendedMode; // save it here, as modes.pop() resets it - this._keepCommand = !userContext.hidden_option_no_command_afterimage; - this._currentExtendedMode = null; // Don't let modes.pop trigger "cancel" - modes.pop(!this._silent); - commandline.triggerCallback("submit", mode, command); + else if (event.type == "focus") { + if (!this._commandShown() && event.target == this._commandWidget.inputField) { + event.target.blur(); + dactyl.beep(); + } } - // user pressed <Up> or <Down> arrow to cycle this._history completion - else if (/^<(Up|Down|S-Up|S-Down|PageUp|PageDown)>$/.test(key)) { - // prevent tab from moving to the next field - event.preventDefault(); - event.stopPropagation(); - - dactyl.assert(this._history); - this._history.select(/Up/.test(key), !/(Page|S-)/.test(key)); + else if (event.type == "input") { + this.resetCompletions(); + commandline.triggerCallback("change", this._currentExtendedMode, command); } - // user pressed <Tab> to get completions of a command - else if (/^<(Tab|S-Tab)>$/.test(key)) { - // prevent tab from moving to the next field - event.preventDefault(); - event.stopPropagation(); + else if (event.type == "keypress") { + let key = events.toString(event); + if (this._completions) + this._completions.previewClear(); + if (!this._currentExtendedMode) + return; - this._tabTimer.tell(event); - } - else if (key == "<BS>") { - // reset the tab completion - //this.resetCompletions(); + // user pressed <Enter> to carry out a command + // user pressing <Esc> is handled in the global onEscape + // FIXME: <Esc> should trigger "cancel" event + if (events.isAcceptKey(key)) { + let mode = this._currentExtendedMode; // save it here, as modes.pop() resets it + this._keepCommand = !userContext.hidden_option_no_command_afterimage; + this._currentExtendedMode = null; // Don't let modes.pop trigger "cancel" + modes.pop(!this._silent); + commandline.triggerCallback("submit", mode, command); + } + // user pressed <Up> or <Down> arrow to cycle this._history completion + else if (/^<(Up|Down|S-Up|S-Down|PageUp|PageDown)>$/.test(key)) { + // prevent tab from moving to the next field + event.preventDefault(); + event.stopPropagation(); - // and blur the command line if there is no text left - if (command.length == 0) { - commandline.triggerCallback("cancel", this._currentExtendedMode); - modes.pop(); + dactyl.assert(this._history); + this._history.select(/Up/.test(key), !/(Page|S-)/.test(key)); + } + // user pressed <Tab> to get completions of a command + else if (/^<(Tab|S-Tab)>$/.test(key)) { + // prevent tab from moving to the next field + event.preventDefault(); + event.stopPropagation(); + + this._tabTimer.tell(event); + } + else if (key == "<BS>") { + // reset the tab completion + //this.resetCompletions(); + + // and blur the command line if there is no text left + if (command.length == 0) { + commandline.triggerCallback("cancel", this._currentExtendedMode); + modes.pop(); + } + } + else { // any other key + //this.resetCompletions(); } + // allow this event to be handled by the host app } - else { // any other key - //this.resetCompletions(); + else if (event.type == "keyup") { + let key = events.toString(event); + if (/^<(Tab|S-Tab)>$/.test(key)) + this._tabTimer.flush(); } - // allow this event to be handled by the host app } - else if (event.type == "keyup") { - let key = events.toString(event); - if (/^<(Tab|S-Tab)>$/.test(key)) - this._tabTimer.flush(); + catch (e) { + dactyl.reportError(e); } }, @@ -1724,6 +1729,8 @@ const ItemList = Class("ItemList", { // if @param selectedItem is given, show the list and select that item setItems: function setItems(newItems, selectedItem) { + if (this._selItem > -1) + this._getCompletion(this._selItem).removeAttribute("selected"); if (this._container.collapsed) this._minHeight = 0; this._startIndex = this._endIndex = this._selIndex = -1; @@ -1737,9 +1744,6 @@ const ItemList = Class("ItemList", { // select index, refill list if necessary selectItem: function selectItem(index) { - //if (this._container.collapsed) // FIXME - // return; - //let now = Date.now(); if (this._div == null) diff --git a/common/content/completion.js b/common/content/completion.js index 510e7922..f902669b 100755 --- a/common/content/completion.js +++ b/common/content/completion.js @@ -670,8 +670,10 @@ const Completion = Module("completion", { let list = template.commandOutput( <div highlight="Completions"> - { template.completionRow(context.title, "CompTitle") } - { template.map(context.items, function (item) context.createRow(item), null, 100) } + { template.map(context.contextList.filter(function (c) c.hasItems), + function (context) + template.completionRow(context.title, "CompTitle") + + template.map(context.items, function (item) context.createRow(item), null, 100)) } </div>); commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE); }, diff --git a/common/content/dactyl-overlay.js b/common/content/dactyl-overlay.js index d7a00b41..32f23e9a 100644 --- a/common/content/dactyl-overlay.js +++ b/common/content/dactyl-overlay.js @@ -60,7 +60,6 @@ prefix.unshift("chrome://" + modules.Config.prototype.name.toLowerCase() + "/content/"); modules.Config.prototype.scripts.forEach(load); - })(); // vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/dactyl.js b/common/content/dactyl.js index fa6c91f1..f2912149 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -73,11 +73,11 @@ const Dactyl = Module("dactyl", { }, destroy: function () { - autocommands.trigger(config.name + "LeavePre", {}); + autocommands.trigger("LeavePre", {}); storage.saveAll(); dactyl.triggerObserver("shutdown", null); dactyl.dump("All dactyl modules destroyed\n"); - autocommands.trigger(config.name + "Leave", {}); + autocommands.trigger("Leave", {}); }, /** @@ -682,11 +682,12 @@ const Dactyl = Module("dactyl", { return; } - dactyl.echomsg('Searching for "plugin/**/*.{js,vimp}" in "' - + [dir.path.replace(/.plugin$/, "") for ([, dir] in Iterator(dirs))].join(",") + '"', 2); + dactyl.echomsg('Searching for "plugin/**/*.{js,vimp}" in ' + + [dir.path.replace(/.plugin$/, "") for ([, dir] in Iterator(dirs))] + .join(",").quote(), 2); dirs.forEach(function (dir) { - dactyl.echomsg("Searching for \"" + (dir.path + "/**/*.{js,vimp}") + "\"", 3); + dactyl.echomsg("Searching for " + (dir.path + "/**/*.{js,vimp}").quote(), 3); sourceDirectory(dir); }); }, @@ -735,11 +736,6 @@ const Dactyl = Module("dactyl", { * @returns {boolean} */ open: function (urls, params, force) { - // convert the string to an array of converted URLs - // -> see dactyl.stringToURLArray for more details - // - // This is strange. And counterintuitive. Is it really - // necessary? --Kris if (typeof urls == "string") urls = dactyl.stringToURLArray(urls); @@ -867,7 +863,7 @@ const Dactyl = Module("dactyl", { urls = [str]; return urls.map(function (url) { - if (/^\.?\//.test(url)) { + if (/^[.~]?\//.test(url)) { try { // Try to find a matching file. let file = io.File(url); @@ -1176,7 +1172,7 @@ const Dactyl = Module("dactyl", { else styles.removeSheet(true, "scrollbar"); options.safeSetPref("layout.scrollbar.side", opts.indexOf("l") >= 0 ? 3 : 2, - "See 'guioptions' scrollbar flags."); + "See 'guioptions' scrollbar flags."); }, validator: function (opts) (opts.indexOf("l") < 0 || opts.indexOf("r") < 0) }, @@ -1271,7 +1267,7 @@ const Dactyl = Module("dactyl", { { setter: function (value) { options.safeSetPref("accessibility.typeaheadfind.enablesound", !value, - "See 'visualbell' option"); + "See 'visualbell' option"); return value; } }); @@ -1444,7 +1440,7 @@ const Dactyl = Module("dactyl", { else if (file.isReadable() && file.isFile()) AddonManager.getInstallForFile(file, callResult("install"), "application/x-xpinstall"); else if (file.isDirectory()) - dactyl.echomsg("Cannot install a directory: \"" + file.path + "\"", 0); + dactyl.echomsg("Cannot install a directory: " + file.path.quote(), 0); else dactyl.echoerr("E484: Can't open file " + file.path); }, { @@ -1552,7 +1548,7 @@ const Dactyl = Module("dactyl", { } else { if (filter) - dactyl.echoerr("Exxx: No extension matching \"" + filter + "\""); + dactyl.echoerr("Exxx: No extension matching " + filter.quote()); else dactyl.echoerr("No extensions installed"); } @@ -1953,7 +1949,7 @@ const Dactyl = Module("dactyl", { }); dactyl.triggerObserver("enter", null); - autocommands.trigger(config.name + "Enter", {}); + autocommands.trigger("Enter", {}); }, 0); statusline.update(); diff --git a/common/content/finder.js b/common/content/finder.js index 9fd72cca..5f139efc 100644 --- a/common/content/finder.js +++ b/common/content/finder.js @@ -18,7 +18,7 @@ const RangeFinder = Module("rangefinder", { let backwards = mode == modes.FIND_BACKWARD; commandline.open(backwards ? "?" : "/", "", mode); - if (this.rangeFind) + if (this.rangeFind && this.rangeFind.window.get() == window) this.rangeFind.reset(); this.find("", backwards); }, @@ -28,6 +28,7 @@ const RangeFinder = Module("rangefinder", { this.rangeFind = null; let highlighted = this.rangeFind && this.rangeFind.highlighted; + let selections = this.rangeFind && this.rangeFind.selections; let matchCase = !(options["ignorecase"] || options["smartcase"] && !/[A-Z]/.test(str)); let linksOnly = options["linksearch"]; @@ -45,15 +46,19 @@ const RangeFinder = Module("rangefinder", { return ""; }); - // It's possible, with :tabdetach, for the rangeFind to actually move - // from one window to another, which breaks things. - if (!this.rangeFind || this.rangeFind.window.get() != window || - linksOnly ^ !!this.rangeFind.elementPath || - matchCase ^ this.rangeFind.matchCase || backward ^ this.rangeFind.reverse) { + // It's possible, with :tabdetach for instance, for the rangeFind to + // actually move from one window to another, which breaks things. + if (!this.rangeFind + || this.rangeFind.window.get() != window + || linksOnly != !!this.rangeFind.elementPath + || matchCase != this.rangeFind.matchCase + || !!backward != this.rangeFind.reverse) { + if (this.rangeFind) this.rangeFind.cancel(); this.rangeFind = RangeFind(matchCase, backward, linksOnly && options["hinttags"]); this.rangeFind.highlighted = highlighted; + this.rangeFind.selections = selections; } return str; }, @@ -71,15 +76,15 @@ const RangeFinder = Module("rangefinder", { this.find(this.lastSearchPattern); else if (!this.rangeFind.search(null, reverse)) dactyl.echoerr("E486: Pattern not found: " + this.lastSearchPattern); - else if (this.rangeFind.wrapped) { - // hack needed, because wrapping causes a "scroll" event which clears - // our command line + else if (this.rangeFind.wrapped) + // hack needed, because wrapping causes a "scroll" event which + // clears our command line this.setTimeout(function () { let msg = this.rangeFind.backward ? "search hit TOP, continuing at BOTTOM" : "search hit BOTTOM, continuing at TOP"; - commandline.echo(msg, commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES); + commandline.echo(msg, commandline.HL_WARNINGMSG, + commandline.APPEND_TO_MESSAGES | commandline.FORCE_SINGLELINE); }, 0); - } else commandline.echo((this.rangeFind.backward ? "?" : "/") + this.lastSearchPattern, null, commandline.FORCE_SINGLELINE); @@ -197,6 +202,10 @@ const RangeFinder = Module("rangefinder", { modes.addMode("FIND_BACKWARD", true); }, options: function () { + options.safeSetPref("accessibility.typeaheadfind.autostart", false); + // The above should be sufficient, but: https://bugzilla.mozilla.org/show_bug.cgi?id=348187 + options.safeSetPref("accessibility.typeaheadfind", false); + options.add(["hlsearch", "hls"], "Highlight previous search pattern matches", "boolean", "false", { @@ -267,26 +276,30 @@ const RangeFind = Class("RangeFind", { this.reset(); this.highlighted = null; + this.selections = []; this.lastString = ""; }, + get backward() this.finder.findBackwards, + + get searchString() this.lastString, + get selectedRange() { - let range = RangeFind.Range(tabs.localStore.focusedFrame || content); - return (range.selection.rangeCount ? range.selection.getRangeAt(0) : this.ranges[0].range).cloneRange(); + let selection = (tabs.localStore.focusedFrame || content).getSelection(); + return (selection.rangeCount ? selection.getRangeAt(0) : this.ranges[0].range).cloneRange(); }, - - reset: function () { - this.startRange = this.selectedRange; - this.startRange.collapse(!this.reverse); - this.lastRange = this.selectedRange; - this.range = this.findRange(this.startRange); - this.ranges.first = this.range; - this.ranges.forEach(function (range) range.save()); - this.forward = null; - this.found = false; + set selectedRange(range) { + this.range.selection.removeAllRanges(); + this.range.selection.addRange(range); + this.range.selectionController.scrollSelectionIntoView( + this.range.selectionController.SELECTION_NORMAL, 0, false); }, - sameDocument: function (r1, r2) r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument, + cancel: function () { + this.purgeListeners(); + this.range.deselect(); + this.range.descroll(); + }, compareRanges: function (r1, r2) this.backward ? r1.compareBoundaryPoints(Range.END_TO_START, r2) @@ -296,9 +309,7 @@ const RangeFind = Class("RangeFind", { let doc = range.startContainer.ownerDocument; let win = doc.defaultView; let ranges = this.ranges.filter(function (r) - r.window == win && - r.range.compareBoundaryPoints(Range.START_TO_END, range) >= 0 && - r.range.compareBoundaryPoints(Range.END_TO_START, range) <= 0); + r.window == win && RangeFind.contains(r.range, range)); if (this.backward) return ranges[ranges.length - 1]; @@ -308,10 +319,8 @@ const RangeFind = Class("RangeFind", { findSubRanges: function (range) { let doc = range.startContainer.ownerDocument; for (let elem in util.evaluateXPath(this.elementPath, doc)) { - let r = doc.createRange(); - r.selectNode(elem); - if (range.compareBoundaryPoints(Range.START_TO_END, r) >= 0 && - range.compareBoundaryPoints(Range.END_TO_START, r) <= 0) + let r = RangeFind.nodeRange(elem); + if (RangeFind.contains(range, r)) yield r; } }, @@ -322,7 +331,62 @@ const RangeFind = Class("RangeFind", { this.lastRange.commonAncestorContainer).snapshotItem(0); if(node) { node.focus(); - this.search(null, false); // Rehighlight collapsed range + // Rehighlight collapsed selection + this.selectedRange = this.lastRange; + } + }, + + highlight: function (clear) { + + if (!clear && (!this.lastString || this.lastString == this.highlighted)) + return; + if (clear && !this.highlighted) + return; + + if (!clear && this.highlighted) + this.highlight(true); + + if (clear) { + this.selections.forEach(function (selection) { + selection.removeAllRanges(); + }); + this.selections = []; + this.highlighted = null; + } + else { + this.selections = []; + let string = this.lastString; + for (let r in this.iter(string)) { + let controller = this.range.selectionController; + for (let node=r.startContainer; node; node=node.parentNode) + if (node instanceof Ci.nsIDOMNSEditableElement) { + controller = node.editor.selectionController; + break; + } + + let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND); + sel.addRange(r); + if (this.selections.indexOf(sel) < 0) + this.selections.push(sel); + } + this.highlighted = this.lastString; + this.selectedRange = this.lastRange; + this.addListeners(); + } + }, + + iter: function (word) { + let saved = ["range", "lastRange", "lastString"].map(function (s) [s, this[s]], this); + try { + this.range = this.ranges[0]; + this.lastRange = null; + this.lastString = word; + var res; + while ((res = this.search(null, this.reverse, true))) + yield res; + } + finally { + saved.forEach(function ([k, v]) this[k] = v, this); } }, @@ -334,8 +398,7 @@ const RangeFind = Class("RangeFind", { function pushRange(start, end) { function push(r) { - r = RangeFind.Range(r, frames.length); - if (r) + if (r = RangeFind.Range(r, frames.length)) frames.push(r); } @@ -351,18 +414,19 @@ const RangeFind = Class("RangeFind", { } function rec(win) { let doc = win.document; - let pageRange = doc.createRange(); - pageRange.selectNode(doc.body || doc.documentElement.lastChild); + let pageRange = RangeFind.nodeRange(doc.body || doc.documentElement.lastChild); backup = backup || pageRange; let pageStart = RangeFind.endpoint(pageRange, true); let pageEnd = RangeFind.endpoint(pageRange, false); for (let frame in util.Array.itervalues(win.frames)) { let range = doc.createRange(); - range.selectNode(frame.frameElement); - pushRange(pageStart, RangeFind.endpoint(range, true)); - pageStart = RangeFind.endpoint(range, false); - rec(frame); + if (util.computedStyle(frame.frameElement).visibility == "visible") { + range.selectNode(frame.frameElement); + pushRange(pageStart, RangeFind.endpoint(range, true)); + pageStart = RangeFind.endpoint(range, false); + rec(frame); + } } pushRange(pageStart, pageEnd); } @@ -372,6 +436,17 @@ const RangeFind = Class("RangeFind", { return frames; }, + reset: function () { + this.startRange = this.selectedRange; + this.startRange.collapse(!this.reverse); + this.lastRange = this.selectedRange; + this.range = this.findRange(this.startRange); + this.ranges.first = this.range; + this.ranges.forEach(function (range) range.save()); + this.forward = null; + this.found = false; + }, + // This doesn't work yet. resetCaret: function () { let equal = RangeFind.equal; @@ -406,24 +481,6 @@ const RangeFind = Class("RangeFind", { return null; }, - get searchString() this.lastString, - get backward() this.finder.findBackwards, - - iter: function (word) { - let saved = ["range", "lastRange", "lastString"].map(this.closure(function (s) [s, this[s]])); - try { - this.range = this.ranges[0]; - this.lastRange = null; - this.lastString = word; - var res; - while ((res = this.search(null, this.reverse, true))) - yield res; - } - finally { - saved.forEach(function ([k, v]) this[k] = v, this); - } - }, - search: function (word, reverse, private_) { if (!private_ && this.lastRange && !RangeFind.equal(this.selectedRange, this.lastRange)) this.reset(); @@ -450,110 +507,57 @@ const RangeFind = Class("RangeFind", { else { function indices() { let idx = this.range.index; - for (let i in this.backward ? util.range(idx + 1, 0, -1) : util.range(idx, this.ranges.length)) - yield i; - if (private_) - return; - this.wrapped = true; - this.lastRange = null; - for (let i in this.backward ? util.range(this.ranges.length, idx, -1) : util.range(0, idx + 1)) + if (this.backward) + var groups = [util.range(idx + 1, 0, -1), util.range(this.ranges.length, idx, -1)]; + else + var groups = [util.range(idx, this.ranges.length), util.range(0, idx + 1)]; + + for (let i in groups[0]) yield i; + + if (!private_) { + this.wrapped = true; + this.lastRange = null; + for (let i in groups[1]) + yield i; + } } for (let i in indices.call(this)) { + if (!private_ && this.range.window != this.ranges[i].window && this.range.window != this.ranges[i].window.parent) { + this.range.descroll(); + this.range.deselect(); + } this.range = this.ranges[i]; - let start = this.sameDocument(this.lastRange, this.range.range) && this.range.intersects(this.lastRange) ? - RangeFind.endpoint(this.lastRange, !(again ^ this.backward)) : - RangeFind.endpoint(this.range.range, !this.backward);; + let start = RangeFind.sameDocument(this.lastRange, this.range.range) && this.range.intersects(this.lastRange) ? + RangeFind.endpoint(this.lastRange, !(again ^ this.backward)) : + RangeFind.endpoint(this.range.range, !this.backward);; + if (this.backward && !again) start = RangeFind.endpoint(this.startRange, false); var range = this.finder.Find(word, this.range.range, start, this.range.range); if (range) break; - if (!private_) { - this.range.descroll(); - this.range.deselect(); - } } } if (range) this.lastRange = range.cloneRange(); - if (private_) - return range; - - this.lastString = word; - if (range == null) { - this.cancel(); - this.found = false; - return null; + if (!private_) { + this.lastString = word; + if (range == null) { + this.cancel(); + this.found = false; + return null; + } + this.found = true; } - this.range.selection.removeAllRanges(); - this.range.selection.addRange(range); - this.range.selectionController.scrollSelectionIntoView( - this.range.selectionController.SELECTION_NORMAL, 0, false); - this.found = true; + if (range && (!private_ || private_ < 0)) + this.selectedRange = range; return range; }, - highlight: function (clear) { - - if (!clear && (!this.lastString || this.lastString == this.highlighted)) - return; - - if (!clear && this.highlighted) - this.highlight(true); - - if (clear && !this.highlighted) - return; - - let span = util.xmlToDom(<span highlight="Search"/>, this.range.document); - - function highlight(range) { - let startContainer = range.startContainer; - let startOffset = range.startOffset; - let node = startContainer.ownerDocument.importNode(span, true); - - let docfrag = range.extractContents(); - let before = startContainer.splitText(startOffset); - let parent = before.parentNode; - node.appendChild(docfrag); - parent.insertBefore(node, before); - range.selectNode(node); - } - - function unhighlight(range) { - let elem = range.startContainer; - while (!(elem instanceof Element) && elem.parentNode) - elem = elem.parentNode; - if (elem.getAttributeNS(NS.uri, "highlight") != "Search") - return; - - let docfrag = range.extractContents(); - - let parent = elem.parentNode; - parent.replaceChild(docfrag, elem); - parent.normalize(); - } - - let action = clear ? unhighlight : highlight; - let string = this[clear ? "highlighted" : "lastString"]; - for (let r in this.iter(string)) { - action(r); - this.lastRange = r; - } - if (clear) { - this.highlighted = null; - this.purgeListeners(); - } - else { - this.highlighted = this.lastString; - this.addListeners(); - this.search(null, false); // Rehighlight collapsed range - } - }, - addListeners: function () { for (let range in values(this.ranges)) range.window.addEventListener("unload", this.closure.onUnload, true); @@ -562,32 +566,23 @@ const RangeFind = Class("RangeFind", { for (let range in values(this.ranges)) range.window.removeEventListener("unload", this.closure.onUnload, true); }, - onUnload: function (event) { this.purgeListeners(); if (this.highlighted) this.highlight(false); this.stale = true; - }, - - cancel: function () { - this.purgeListeners(); - this.range.deselect(); - this.range.descroll(); } }, { Range: Class("RangeFind.Range", { init: function (range, index) { - if (range instanceof Ci.nsIDOMWindow) { // Kludge - this.document = range.document; - return; - } - this.index = index; - this.document = range.startContainer.ownerDocument; - this.window = this.document.defaultView; this.range = range; + this.document = range.startContainer.ownerDocument; + this.window = this.document.defaultView; + this.docShell = this.window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); if (this.selection == null) return false; @@ -617,14 +612,6 @@ const RangeFind = Class("RangeFind", { this.selection.addRange(this.initialSelection); }, - get docShell() { - if (this._docShell) - return this._docShell; - for (let shell in iter(config.browser.docShell.getDocShellEnumerator(Ci.nsIDocShellTreeItem.typeAll, Ci.nsIDocShell.ENUMERATE_FORWARDS))) - if (shell.QueryInterface(nsIWebNavigation).document == this.document) - return this._docShell = shell; - throw Error(); - }, get selectionController() this.docShell .QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsISelectionDisplay) @@ -637,8 +624,9 @@ const RangeFind = Class("RangeFind", { }} }), - selectNodePath: ["ancestor-or-self::" + s for ([i, s] in Iterator( - ["a", "xhtml:a", "*[@onclick]"]))].join(" | "), + contains: function (range, r) + range.compareBoundaryPoints(Range.START_TO_END, r) >= 0 && + range.compareBoundaryPoints(Range.END_TO_START, r) <= 0, endpoint: function (range, before) { range = range.cloneRange(); range.collapse(before); @@ -650,7 +638,15 @@ const RangeFind = Class("RangeFind", { } catch (e) {} return false; - } + }, + nodeRange: function (node) { + let range = node.ownerDocument.createRange(); + range.selectNode(node); + return range; + }, + sameDocument: function (r1, r2) r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument, + selectNodePath: ["a", "xhtml:a", "*[@onclick]"].map( + function (p) "ancestor-or-self::" + p).join(" | ") }); // vim: set fdm=marker sw=4 ts=4 et: diff --git a/common/content/history.js b/common/content/history.js index 33ecb7e2..262df4c3 100644 --- a/common/content/history.js +++ b/common/content/history.js @@ -104,7 +104,7 @@ const History = Module("history", { return dactyl.open(items.map(function (i) i.url), dactyl.NEW_TAB); if (filter.length > 0) - dactyl.echoerr("E283: No history matching \"" + filter + "\""); + dactyl.echoerr("E283: No history matching " + filter.quote()); else dactyl.echoerr("No history set"); return null; diff --git a/common/content/io.js b/common/content/io.js index ff958296..dfe16e5c 100755 --- a/common/content/io.js +++ b/common/content/io.js @@ -426,7 +426,7 @@ const IO = Module("io", { let dir = File(newDir); if (!dir.exists() || !dir.isDirectory()) { - dactyl.echoerr("E344: Can't find directory \"" + dir.path + "\" in path"); + dactyl.echoerr("E344: Can't find directory " + dir.path.quote()); return null; } @@ -564,14 +564,14 @@ lookup: let dirs = File.getPathsFromPathList(options["runtimepath"]); let found = false; - dactyl.echomsg("Searching for \"" + paths.join(" ") + "\" in \"" + options["runtimepath"] + "\"", 2); + dactyl.echomsg("Searching for " + paths.join(" ").quote() + " in " + options["runtimepath"].quote(), 2); outer: for (let [, dir] in Iterator(dirs)) { for (let [, path] in Iterator(paths)) { let file = File.joinPaths(dir, path); - dactyl.echomsg("Searching for \"" + file.path + "\"", 3); + dactyl.echomsg("Searching for " + file.path.quote(), 3); if (file.exists() && file.isFile() && file.isReadable()) { io.source(file.path, false); @@ -584,7 +584,7 @@ lookup: } if (!found) - dactyl.echomsg("not found in 'runtimepath': \"" + paths.join(" ") + "\"", 1); + dactyl.echomsg("not found in 'runtimepath': " + paths.join(" ").quote(), 1); return found; }, @@ -609,9 +609,9 @@ lookup: if (!file.exists() || !file.isReadable() || file.isDirectory()) { if (!silent) { if (file.exists() && file.isDirectory()) - dactyl.echomsg("Cannot source a directory: \"" + filename + "\"", 0); + dactyl.echomsg("Cannot source a directory: " + filename.quote(), 0); else - dactyl.echomsg("could not source: \"" + filename + "\"", 1); + dactyl.echomsg("could not source: " + filename.quote(), 1); dactyl.echoerr("E484: Can't open file " + filename); } @@ -619,7 +619,7 @@ lookup: return; } - dactyl.echomsg("sourcing \"" + filename + "\"", 2); + dactyl.echomsg("sourcing " + filename.quote(), 2); let str = file.read(); let uri = services.get("io").newFileURI(file); @@ -705,7 +705,7 @@ lookup: if (this._scriptNames.indexOf(file.path) == -1) this._scriptNames.push(file.path); - dactyl.echomsg("finished sourcing \"" + filename + "\"", 2); + dactyl.echomsg("finished sourcing " + filename.quote(), 2); dactyl.log("Sourced: " + filename, 3); } @@ -880,7 +880,7 @@ lookup: let file = File(filename); dactyl.assert(!file.exists() || args.bang, - "E189: \"" + filename + "\" exists (add ! to override)"); + "E189: " + filename.quote() + " exists (add ! to override)"); // TODO: Use a set/specifiable list here: let lines = [cmd.serial().map(commands.commandToString) for (cmd in commands) if (cmd.serial)]; @@ -903,7 +903,7 @@ lookup: file.write(lines.join("\n")); } catch (e) { - dactyl.echoerr("E190: Cannot open \"" + filename + "\" for writing"); + dactyl.echoerr("E190: Cannot open " + filename.quote() + " for writing"); dactyl.log("Could not write to " + file.path + ": " + e.message); // XXX } }, { diff --git a/common/content/javascript.js b/common/content/javascript.js index bd057bc2..686929ba 100644 --- a/common/content/javascript.js +++ b/common/content/javascript.js @@ -49,10 +49,8 @@ const JavaScript = Module("javascript", { for (; obj; obj = !toplevel && obj.__proto__) { services.get("debugger").wrapValue(obj).getProperties(ret, {}); for (let prop in values(ret.value)) { - let name = '|' + prop.name.stringValue; - if (name in seen) + if (set.add(seen, prop.name.stringValue)) continue; - seen[name] = 1; if (toplevel || obj !== orig) yield [prop.name.stringValue, top.getProperty(prop.name.stringValue).value.getWrappedValue()] } @@ -61,7 +59,7 @@ const JavaScript = Module("javascript", { // This only lists ENUMERABLE properties. try { for (let k in orig) - if (k in orig && !('|' + k in seen) + if (k in orig && !(set.has(seen, k)) && Object.hasOwnProperty(orig, k) == toplevel) yield [k, this.getKey(orig, k)] } @@ -298,14 +296,18 @@ const JavaScript = Module("javascript", { continue; if (dot > stop || dot <= prev) break; - let s = this._str.substring(prev, dot); + let s = this._str.substring(prev, dot); if (prev != statement) s = JavaScript.EVAL_TMP + "." + s; cacheKey = this._str.substring(statement, dot); if (this._checkFunction(prev, dot, cacheKey)) return []; + if (prev != statement && obj == null) { + this.context.message = "Error: " + cacheKey.quote() + " is " + String(obj); + return []; + } prev = dot + 1; obj = this.eval(s, cacheKey, obj); @@ -338,7 +340,7 @@ const JavaScript = Module("javascript", { context.anchored = anchored; context.filter = key; context.itemCache = context.parent.itemCache; - context.key = name; + context.key = name + last; if (last != null) context.quote = [last, function (text) util.escapeString(text.substr(offset), ""), last]; @@ -435,7 +437,7 @@ const JavaScript = Module("javascript", { } this.context.getCache("eval", Object); - this.context.getCache("evalContext", function () ({ __proto__: userContext })); + this.context.getCache("evalContext", function () ({ __proto__: userContext }));; // Okay, have parse stack. Figure out what we're completing. diff --git a/common/content/options.js b/common/content/options.js index 43174ac2..c7cda5c8 100644 --- a/common/content/options.js +++ b/common/content/options.js @@ -792,10 +792,11 @@ const Options = Module("options", { */ // FIXME: Well it used to. I'm looking at you mst! --djk safeSetPref: function (name, value, message) { - let val = this._loadPreference(name, null, false); - let def = this._loadPreference(name, null, true); - let lib = this._loadPreference(Options.SAVED + name); - if (lib == null && val != def || val != lib) { + let curval = this._loadPreference(name, null, false); + let defval = this._loadPreference(name, null, true); + let saved = this._loadPreference(Options.SAVED + name); + + if (saved == null && curval != defval || curval != saved) { let msg = "Warning: setting preference " + name + ", but it's changed from its default value."; if (message) msg += " " + message; @@ -952,14 +953,6 @@ const Options = Module("options", { SAVED: "extensions.dactyl.saved.", OLD_SAVED: "dactyl.saved." }, { - commandline: function () { - // TODO: maybe reset in .destroy()? - // TODO: move to buffer.js - // we have our own typeahead find implementation - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=348187 - options.safeSetPref("accessibility.typeaheadfind.autostart", false); - options.safeSetPref("accessibility.typeaheadfind", false); // actually the above setting should do it, but has no effect in Firefox - }, commands: function () { function setAction(args, modifiers) { let bang = args.bang; diff --git a/common/content/quickmarks.js b/common/content/quickmarks.js index 9000bd23..fc44eb79 100644 --- a/common/content/quickmarks.js +++ b/common/content/quickmarks.js @@ -89,7 +89,7 @@ const QuickMarks = Module("quickmarks", { if (filter.length > 0) { marks = marks.filter(function (qmark) filter.indexOf(qmark) >= 0); - dactyl.assert(marks.length >= 0, "E283: No QuickMarks matching \"" + filter + "\""); + dactyl.assert(marks.length >= 0, "E283: No QuickMarks matching " + filter.quote()); } let items = [[mark, this._qmarks.get(mark)] for ([k, mark] in Iterator(marks))]; @@ -138,7 +138,7 @@ const QuickMarks = Module("quickmarks", { args = args.string; // ignore invalid qmark characters unless there are no valid qmark chars - dactyl.assert(!args || /[a-zA-Z0-9]/.test(args), "E283: No QuickMarks matching \"" + args + "\""); + dactyl.assert(!args || /[a-zA-Z0-9]/.test(args), "E283: No QuickMarks matching " + args.quote()); let filter = args.replace(/[^a-zA-Z0-9]/g, ""); quickmarks.list(filter); diff --git a/common/content/sanitizer.js b/common/content/sanitizer.js index c061bc25..68b5ed69 100644 --- a/common/content/sanitizer.js +++ b/common/content/sanitizer.js @@ -7,7 +7,7 @@ // TODO: // - fix Sanitize autocommand // - add warning for TIMESPAN_EVERYTHING? -// - respect privacy.clearOnShutdown et al or recommend PentadactylLeave autocommand? +// - respect privacy.clearOnShutdown et al or recommend Leave autocommand? // - add support for :set sanitizeitems=all like 'eventignore'? // - integrate with the Clear Private Data dialog? diff --git a/common/content/services.js b/common/content/services.js index f95f29a2..33b6acab 100644 --- a/common/content/services.js +++ b/common/content/services.js @@ -22,7 +22,7 @@ const Services = Module("services", { this.add("browserSearch", "@mozilla.org/browser/search-service;1", Ci.nsIBrowserSearchService); this.add("cache", "@mozilla.org/network/cache-service;1", Ci.nsICacheService); this.add("console", "@mozilla.org/consoleservice;1", Ci.nsIConsoleService); - this.add("dactyl:", "@mozilla.org/network/protocol;1?name=dactyl"); + this.add("dactyl:", "@mozilla.org/network/protocol;1?name=dactyl"); this.add("debugger", "@mozilla.org/js/jsd/debugger-service;1", Ci.jsdIDebuggerService); this.add("directory", "@mozilla.org/file/directory_service;1", Ci.nsIProperties); this.add("downloadManager", "@mozilla.org/download-manager;1", Ci.nsIDownloadManager); diff --git a/common/content/statusline.js b/common/content/statusline.js index c43ea4be..b40374ce 100755 --- a/common/content/statusline.js +++ b/common/content/statusline.js @@ -188,10 +188,10 @@ const StatusLine = Module("statusline", { // update the ordinal which is used for numbered tabs if (options.get("guioptions").has("n", "N")) - for (let [i, tab] in util.Array.iteritems(config.browser.mTabs)) + for (let [i, tab] in Iterator(tabs.visibleTabs)) tab.setAttribute("ordinal", i + 1); - this.widgets.tabcount.value = "[" + (tabs.index() + 1) + "/" + tabs.count + "]"; + this.widgets.tabcount.value = "[" + (tabs.index(null, true) + 1) + "/" + tabs.visibleTabs.length + "]"; } }, diff --git a/common/content/tabs.js b/common/content/tabs.js index dbabc928..a05a2979 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -43,6 +43,8 @@ const Tabs = Module("tabs", { setTimeout(function () { dactyl.focusContent(true); }, 10); // just make sure, that no widget has focus }, + get allTabs() Array.slice(config.tabbrowser.tabContainer.childNodes), + /** * @property {Object} The previously accessed tab or null if no tab * other than the current one has been accessed. @@ -60,6 +62,21 @@ const Tabs = Module("tabs", { }, /** + * @property {number} The number of tabs in the current window. + */ + get count() config.tabbrowser.mTabs.length, + + /** + * @property {Object} The local options store for the current tab. + */ + get options() { + let store = this.localStore; + if (!("options" in store)) + store.options = {}; + return store.options; + }, + + /** * @property {boolean} Whether the tab numbering XBL binding has been * applied. */ @@ -75,20 +92,7 @@ const Tabs = Module("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() config.tabbrowser.mTabs.length, - - /** - * @property {Object} The local options store for the current tab. - */ - get options() { - let store = this.localStore; - if (!("options" in store)) - store.options = {}; - return store.options; - }, + get visibleTabs() config.tabbrowser.visibleTabs || this.allTabs.filter(function (tab) !tab.hidden), /** * Returns the local state store for the tab at the specified @@ -123,37 +127,33 @@ const Tabs = Module("tabs", { get closedTabs() services.get("json").decode(services.get("sessionStore").getClosedTabData(window)), /** - * Returns the index of <b>tab</b> or the index of the currently - * selected tab if <b>tab</b> is not specified. This is a 0-based - * index. + * Clones the specified <b>tab</b> and append it to the tab list. * - * @param {Object} tab A tab from the current tab list. - * @returns {number} + * @param {Object} tab The tab to clone. + * @param {boolean} activate Whether to select the newly cloned tab. */ - index: function (tab) { - if (tab) - return Array.indexOf(config.tabbrowser.mTabs, tab); - else - return config.tabbrowser.mTabContainer.selectedIndex; + cloneTab: function (tab, activate) { + let newTab = config.tabbrowser.addTab(); + Tabs.copyTab(newTab, tab); + + if (activate) + config.tabbrowser.mTabContainer.selectedItem = newTab; + + return newTab; }, - // TODO: implement filter /** - * Returns an array of all tabs in the tab list. + * Detaches the specified <b>tab</b> and open it in a new window. If no + * tab is specified the currently selected tab is detached. * - * @returns {Object[]} + * @param {Object} tab The tab to detach. */ - // FIXME: why not return the tab element? - // : unused? Remove me. - get: function () { - let buffers = []; - for (let [i, browser] in this.browsers) { - let title = browser.contentTitle || "(Untitled)"; - let uri = browser.currentURI.spec; - let number = i + 1; - buffers.push([number, title, uri]); - } - return buffers; + detachTab: function (tab) { + if (!tab) + tab = config.tabbrowser.mTabContainer.selectedItem; + + services.get("windowWatcher") + .openWindow(window, window.getBrowserURL(), null, "chrome,dialog=no,all", tab); }, /** @@ -172,6 +172,26 @@ const Tabs = Module("tabs", { }, /** + * If TabView exists, returns the Panorama window. If the Panorama + * is has not yet initialized, this function will not return until + * it has. + * + * @returns {Window} + */ + getGroups: function () { + if ("_groups" in this) + return this._groups; + if (window.TabView && TabView._initFrame) + TabView._initFrame(); + let iframe = document.getElementById("tab-view"); + this._groups = this._groups = iframe ? iframe.contentWindow : null; + if (this._groups) + while (!this._groups.TabItems) + dactyl.threadYield(false, true); + return this._groups; + }, + + /** * Returns the tab at the specified <b>index</b> or the currently * selected tab if <b>index</b> is not specified. This is a 0-based * index. @@ -180,13 +200,73 @@ const Tabs = Module("tabs", { * @returns {Object} */ getTab: function (index) { - if (index != undefined) + if (index != null) return config.tabbrowser.mTabs[index]; else return config.tabbrowser.mCurrentTab; }, /** + * Returns the index of <b>tab</b> or the index of the currently + * selected tab if <b>tab</b> is not specified. This is a 0-based + * index. + * + * @param {<xul:tab/>} tab A tab from the current tab list. + * @param {boolean} visible Whether to consider only visible tabs. + * @returns {number} + */ + index: function (tab, visible) { + let tabs = this[visible ? "visibleTabs" : "allTabs"]; + return tabs.indexOf(tab || config.tabbrowser.mCurrentTab); + }, + + /** + * @param spec can either be: + * - an absolute integer + * - "" for the current tab + * - "+1" for the next tab + * - "-3" for the tab, which is 3 positions left of the current + * - "$" for the last tab + */ + indexFromSpec: function (spec, wrap) { + if (spec instanceof Node) + return this.allTabs.indexOf(spec); + + let tabs = this.visibleTabs; + let position = this.index(null, true); + + if (spec == null || spec === "") + return position; + + if (typeof spec === "number") + position = spec; + else if (spec === "$") + position = tabs.length - 1; + else if (/^[+-]\d+$/.test(spec)) + position += parseInt(spec, 10); + else if (/^\d+$/.test(spec)) + position = parseInt(spec, 10); + else + return -1; + + if (position >= tabs.length) + position = wrap ? position % tabs.length : tabs.length - 1; + else if (position < 0) + position = wrap ? (position % tabs.length) + tabs.length : 0; + + return this.allTabs.indexOf(tabs[position]); + }, + + /** + * Removes all tabs from the tab list except the specified <b>tab</b>. + * + * @param {Object} tab The tab to keep. + */ + keepOnly: function (tab) { + config.tabbrowser.removeAllTabsBut(tab); + }, + + /** * Lists all tabs matching <b>filter</b>. * * @param {string} filter A filter matching a substring of the tab's @@ -206,7 +286,7 @@ const Tabs = Module("tabs", { * list. */ move: function (tab, spec, wrap) { - let index = Tabs.indexFromSpec(spec, wrap); + let index = tabs.indexFromSpec(spec, wrap); config.tabbrowser.moveTabTo(tab, index); }, @@ -223,96 +303,31 @@ const Tabs = Module("tabs", { */ // FIXME: what is quitOnLastTab {1,2} all about then, eh? --djk remove: function (tab, count, focusLeftTab, quitOnLastTab) { - let removeOrBlankTab = { - Firefox: function (tab) { - if (config.tabbrowser.mTabs.length > 1) - config.tabbrowser.removeTab(tab); - else { - if (buffer.URL != "about:blank" || - window.getWebNavigation().sessionHistory.count > 0) { - dactyl.open("about:blank", dactyl.NEW_BACKGROUND_TAB); - config.tabbrowser.removeTab(tab); - } - else - dactyl.beep(); - } - }, - Thunderbird: function (tab) { - if (config.tabbrowser.mTabs.length > 1) - config.tabbrowser.removeTab(tab); - else - dactyl.beep(); - }, - Songbird: function (tab) { - if (config.tabbrowser.mTabs.length > 1) - config.tabbrowser.removeTab(tab); - else { - if (buffer.URL != "about:blank" || window.getWebNavigation().sessionHistory.count > 0) { - dactyl.open("about:blank", dactyl.NEW_BACKGROUND_TAB); - config.tabbrowser.removeTab(tab); - } - else - dactyl.beep(); - } - } - }[config.hostApplication] || function () {}; - - if (typeof count != "number" || count < 1) - count = 1; + count = Math.max(count, 1); - if (quitOnLastTab >= 1 && config.tabbrowser.mTabs.length <= count) { + if (quitOnLastTab >= 1 && this.count <= count) { if (dactyl.windows.length > 1) window.close(); else dactyl.quit(quitOnLastTab == 2); - return; } - let index = this.index(tab); - if (focusLeftTab) { - let lastRemovedTab = 0; - for (let i = index; i > index - count && i >= 0; i--) { - removeOrBlankTab(this.getTab(i)); - lastRemovedTab = i > 0 ? i : 1; - } - config.tabbrowser.mTabContainer.selectedIndex = lastRemovedTab - 1; - } - else { - let i = index + count - 1; - if (i >= this.count) - i = this.count - 1; + let tabs = this.visibleTabs + if (tabs.indexOf(tab) < 0) + tabs = this.allTabs; + let index = tabs.indexOf(tab); - for (; i >= index; i--) - removeOrBlankTab(this.getTab(i)); - config.tabbrowser.mTabContainer.selectedIndex = index; - } - }, + let next = index + (focusLeftTab ? -count : count); + if (!(next in tabs)) + next = index + (focusLeftTab ? 1 : -1); + if (next in tabs) + config.tabbrowser.mTabContainer.selectedItem = tabs[next]; - /** - * Removes all tabs from the tab list except the specified <b>tab</b>. - * - * @param {Object} tab The tab to keep. - */ - keepOnly: function (tab) { - config.tabbrowser.removeAllTabsBut(tab); - }, - - /** - * Selects the tab at the position specified by <b>spec</b>. - * - * @param {string} spec See {@link Tabs.indexFromSpec} - * @param {boolean} wrap Whether an out of bounds <b>spec</b> causes - * the selection position to wrap around the start/end of the tab - * list. - */ - select: function (spec, wrap) { - let index = Tabs.indexFromSpec(spec, wrap); - // FIXME: - if (index == -1) - dactyl.beep(); + if (focusLeftTab) + tabs.slice(Math.max(0, index+1 - count), index+1).forEach(config.removeTab); else - config.tabbrowser.mTabContainer.selectedIndex = index; + tabs.slice(index, index + count).forEach(config.removeTab); }, /** @@ -354,6 +369,40 @@ const Tabs = Module("tabs", { }, /** + * Selects the tab at the position specified by <b>spec</b>. + * + * @param {string} spec See {@link Tabs.indexFromSpec} + * @param {boolean} wrap Whether an out of bounds <b>spec</b> causes + * the selection position to wrap around the start/end of the tab + * list. + */ + select: function (spec, wrap) { + let index = tabs.indexFromSpec(spec, wrap); + if (index == -1) + dactyl.beep(); + else + config.tabbrowser.mTabContainer.selectedIndex = index; + }, + + /** + * Selects the alternate tab. + */ + selectAlternateTab: function () { + dactyl.assert(tabs.alternate != null && tabs.getTab() != tabs.alternate, + "E23: No alternate page"); + + // NOTE: this currently relies on v.tabs.index() returning the + // currently selected tab index when passed null + let index = tabs.index(tabs.alternate); + + // TODO: since a tab close is more like a bdelete for us we + // should probably reopen the closed tab when a 'deleted' + // alternate is selected + dactyl.assert(index >= 0, "E86: Buffer does not exist"); // TODO: This should read "Buffer N does not exist" + tabs.select(index); + }, + + /** * Stops loading the specified tab. * * @param {Object} tab The tab to stop loading. @@ -398,7 +447,7 @@ const Tabs = Module("tabs", { } else { buffer = this._lastBufferSwitchArgs; - if (allowNonUnique === undefined || allowNonUnique == null) // XXX + if (allowNonUnique == null) // XXX allowNonUnique = this._lastBufferSwitchSpecial; } @@ -407,14 +456,12 @@ const Tabs = Module("tabs", { return; } - if (!count || count < 1) - count = 1; - if (typeof reverse != "boolean") - reverse = false; + count = Math.max(1, count || 1); + reverse = Boolean(reverse); let matches = buffer.match(/^(\d+):?/); if (matches) { - tabs.select(parseInt(matches[1], 10) - 1, false); // make it zero-based + tabs.select(this.allTabs[parseInt(matches[1], 10) - 1], false); return; } @@ -424,8 +471,9 @@ const Tabs = Module("tabs", { let nbrowsers = config.tabbrowser.browsers.length; for (let [i, ] in tabs.browsers) { let index = (i + first) % nbrowsers; - let url = config.tabbrowser.getBrowserAtIndex(index).contentDocument.location.href; - let title = config.tabbrowser.getBrowserAtIndex(index).contentDocument.title.toLowerCase(); + let browser = config.tabbrowser.getBrowserAtIndex(index); + let url = browser.contentDocument.location.href; + let title = browser.contentDocument.title.toLowerCase(); if (url == buffer) { tabs.select(index, false); return; @@ -434,71 +482,21 @@ const Tabs = Module("tabs", { if (url.indexOf(buffer) >= 0 || title.indexOf(lowerBuffer) >= 0) matches.push(index); } + if (matches.length == 0) dactyl.echoerr("E94: No matching buffer for " + buffer); else if (matches.length > 1 && !allowNonUnique) dactyl.echoerr("E93: More than one match for " + buffer); else { - if (reverse) { + let index = (count - 1) % matches.length; + if (reverse) index = matches.length - count; - while (index < 0) - index += matches.length; - } - else - index = (count - 1) % matches.length; - tabs.select(matches[index], false); + index = Array.indexOf(config.tabbrowser.browsers, matches[index]); + tabs.select(index, false); } }, - /** - * Clones the specified <b>tab</b> 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 = config.tabbrowser.addTab(); - Tabs.copyTab(newTab, tab); - - if (activate) - config.tabbrowser.mTabContainer.selectedItem = newTab; - - return newTab; - }, - - /** - * Detaches the specified <b>tab</b> 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) - tab = config.tabbrowser.mTabContainer.selectedItem; - - services.get("windowWatcher") - .openWindow(window, window.getBrowserURL(), null, "chrome,dialog=no,all", tab); - }, - - /** - * Selects the alternate tab. - */ - selectAlternateTab: function () { - dactyl.assert(tabs.alternate != null && tabs.getTab() != tabs.alternate, - "E23: No alternate page"); - - // NOTE: this currently relies on v.tabs.index() returning the - // currently selected tab index when passed null - let index = tabs.index(tabs.alternate); - - // TODO: since a tab close is more like a bdelete for us we - // should probably reopen the closed tab when a 'deleted' - // alternate is selected - dactyl.assert(index >= 0, "E86: Buffer does not exist"); // TODO: This should read "Buffer N does not exist" - tabs.select(index); - }, - // NOTE: when restarting a session FF selects the first tab and then the // tab that was selected when the session was created. As a result the // alternate after a restart is often incorrectly tab 1 when there @@ -520,41 +518,6 @@ const Tabs = Module("tabs", { let tabState = services.get("sessionStore").getTabState(from); services.get("sessionStore").setTabState(to, tabState); - }, - - /** - * @param spec can either be: - * - an absolute integer - * - "" for the current tab - * - "+1" for the next tab - * - "-3" for the tab, which is 3 positions left of the current - * - "$" for the last tab - */ - indexFromSpec: function (spec, wrap) { - let position = config.tabbrowser.mTabContainer.selectedIndex; - let length = config.tabbrowser.mTabs.length; - let last = length - 1; - - if (spec === undefined || spec === "") - return position; - - if (typeof spec === "number") - position = spec; - else if (spec === "$") - position = last; - else if (/^[+-]\d+$/.test(spec)) - position += parseInt(spec, 10); - else if (/^\d+$/.test(spec)) - position = parseInt(spec, 10); - else - return -1; - - if (position > last) - position = wrap ? position % length : last; - else if (position < 0) - position = wrap ? (position % length) + length : 0; - - return position; } }, { commands: function () { @@ -1037,7 +1000,7 @@ const Tabs = Module("tabs", { let pref = "browser.tabStrip.autoHide"; if (options.getPref(pref) == null) // Try for FF 3.0 & 3.1 pref = "browser.tabs.autoHide"; - options.safeSetPref(pref, value == 1); + options.safeSetPref(pref, value == 1, "See 'showtabline' option."); tabStrip.collapsed = false; } @@ -1098,8 +1061,10 @@ const Tabs = Module("tabs", { restriction = 2; } - options.safeSetPref("browser.link.open_newwindow", open, "See 'popups' option."); - options.safeSetPref("browser.link.open_newwindow.restriction", restriction, "See 'popups' option."); + options.safeSetPref("browser.link.open_newwindow", open, + "See 'popups' option."); + options.safeSetPref("browser.link.open_newwindow.restriction", restriction, + "See 'popups' option."); return values; }, completer: function (context) [ diff --git a/common/content/util.js b/common/content/util.js index 8061534b..f72c6dbc 100644 --- a/common/content/util.js +++ b/common/content/util.js @@ -452,7 +452,8 @@ const Util = Module("util", { let result = doc.evaluate(expression, elem, function lookupNamespaceURI(prefix) { return { - xhtml: "http://www.w3.org/1999/xhtml", + xul: XUL.uri, + xhtml: XHTML.uri, xhtml2: "http://www.w3.org/2002/06/xhtml2", dactyl: NS.uri }[prefix] || null; diff --git a/common/locale/en-US/pattern.xml b/common/locale/en-US/pattern.xml index 403df7de..ace1c205 100644 --- a/common/locale/en-US/pattern.xml +++ b/common/locale/en-US/pattern.xml @@ -25,14 +25,20 @@ <p>Search forward for the first occurrence of <a>pattern</a>.</p> <p> - If <str>\c</str> appears anywhere in the pattern the whole pattern is handled as though - <o>ignorecase</o> is on. <str>\C</str> forces case-sensitive matching for the whole pattern. - </p> - <p> - If <str>\l</str> appears in the pattern only the text of links is searched for a - match as though <o>linksearch</o> is on. <str>\L</str> forces the entire page to be searched - for a match. + The following escape sequences can be used to modify the + behavior of the search. When flags conflict, the last to + appear is the one that takes effect. </p> + + <dl> + <dt>\c</dt> <dd>Perform case insensitive search (default + if <o>ignorecase</o> is set).</dd> + <dt>\C</dt> <dd>Perform case sensitive search</dd> + <dt>\l</dt> <dd>Search only in links, as defined by + <o>hinttags</o>. (default if <o>linksearch</o> is + set).</dd> + <dt>\L</dt> <dd>Do not search only in links.</dd> + </dl> </description> </item> @@ -54,7 +60,7 @@ <tags>n</tags> <spec>n</spec> <description> - <p>Find next. Repeat the last search 1 time (until count is supported).</p> + <p>Find next. Repeat the last search.</p> </description> </item> @@ -63,10 +69,7 @@ <tags>N</tags> <spec>N</spec> <description> - <p> - Find previous. Repeat the last search 1 time (until count is supported) in the - opposite direction. - </p> + <p>Find previous. Repeat the last search in the opposite direction.</p> </description> </item> diff --git a/muttator/content/config.js b/muttator/content/config.js index 0f62c89e..b2ed159c 100644 --- a/muttator/content/config.js +++ b/muttator/content/config.js @@ -14,83 +14,53 @@ const Config = Module("config", ConfigBase, { hostApplication: "Thunderbird", // TODO: can this be found out otherwise? gBrandBundle.getString("brandShortName"); // Yes, but it will be localized unlike all other strings. So, it's best left until we i18n dactyl. --djk - get mainWindowId() this.isComposeWindow ? "msgcomposeWindow" : "messengerWindow", - - /*** optional options, there are checked for existence and a fallback provided ***/ - features: ["hints", "mail", "marks", "addressbook", "tabs"], - defaults: { - guioptions: "frb", - showtabline: 1, - titlestring: "Muttator" - }, - - guioptions: { - m: ["MenuBar", ["mail-toolbar-menubar2"]], - T: ["Toolbar" , ["mail-bar2"]], - f: ["Folder list", ["folderPaneBox", "folderpane_splitter"]], - F: ["Folder list header", ["folderPaneHeader"]] + autocommands: { + DOMLoad: "Triggered when a page's DOM content has fully loaded", + FolderLoad: "Triggered after switching folders in Thunderbird", + PageLoadPre: "Triggered after a page load is initiated", + PageLoad: "Triggered when a page gets (re)loaded/opened", + Enter: "Triggered after Thunderbird starts", + Leave: "Triggered before exiting Thunderbird", + LeavePre: "Triggered before exiting Thunderbird", }, - get isComposeWindow() window.wintype == "msgcompose", - get browserModes() [modes.MESSAGE], - get mailModes() [modes.NORMAL], - // focusContent() focuses this widget - get mainWidget() this.isComposeWindow ? document.getElementById("content-frame") : GetThreadTree(), - get visualbellWindow() document.getElementById(this.mainWindowId), - styleableChrome: ["chrome://messenger/content/messenger.xul", - "chrome://messenger/content/messengercompose/messengercompose.xul"], + get browser() getBrowser(), - autocommands: [["DOMLoad", "Triggered when a page's DOM content has fully loaded"], - ["FolderLoad", "Triggered after switching folders in Thunderbird"], - ["PageLoadPre", "Triggered after a page load is initiated"], - ["PageLoad", "Triggered when a page gets (re)loaded/opened"], - ["MuttatorEnter", "Triggered after Thunderbird starts"], - ["MuttatorLeave", "Triggered before exiting Thunderbird"], - ["MuttatorLeavePre", "Triggered before exiting Thunderbird"]], - dialogs: [ - ["about", "About Thunderbird", + dialogs: { + about: ["About Thunderbird", function () { window.openAboutDialog(); }], - ["addons", "Manage Add-ons", + addons: ["Manage Add-ons", function () { window.openAddonsMgr(); }], - ["addressbook", "Address book", + addressbook: ["Address book", function () { window.toAddressBook(); }], - ["checkupdates", "Check for updates", + checkupdates: ["Check for updates", function () { window.checkForUpdates(); }], - /*["cleardata", "Clear private data", - function () { Cc[GLUE_CID].getService(Ci.nsIBrowserGlue).sanitize(window || null); }],*/ - ["console", "JavaScript console", + console: ["JavaScript console", function () { window.toJavaScriptConsole(); }], - /*["customizetoolbar", "Customize the Toolbar", - function () { BrowserCustomizeToolbar(); }],*/ - ["dominspector", "DOM Inspector", + dominspector: ["DOM Inspector", function () { window.inspectDOMDocument(content.document); }], - ["downloads", "Manage Downloads", + downloads: ["Manage Downloads", function () { window.toOpenWindowByType('Download:Manager', 'chrome://mozapps/content/downloads/downloads.xul', 'chrome,dialog=no,resizable'); }], - /*["import", "Import Preferences, Bookmarks, History, etc. from other browsers", - function () { BrowserImport(); }], - ["openfile", "Open the file selector dialog", - function () { BrowserOpenFileWindow(); }], - ["pageinfo", "Show information about the current page", - function () { BrowserPageInfo(); }], - ["pagesource", "View page source", - function () { BrowserViewSourceOfDocument(content.document); }],*/ - ["preferences", "Show Thunderbird preferences dialog", + preferences: ["Show Thunderbird preferences dialog", function () { openOptionsDialog(); }], - /*["printpreview", "Preview the page before printing", - function () { PrintUtils.printPreview(onEnterPrintPreview, onExitPrintPreview); }],*/ - ["printsetup", "Setup the page size and orientation before printing", + printsetup: ["Setup the page size and orientation before printing", function () { PrintUtils.showPageSetup(); }], - ["print", "Show print dialog", + print: ["Show print dialog", function () { PrintUtils.print(); }], - ["saveframe", "Save frame to disk", + saveframe: ["Save frame to disk", function () { window.saveFrameDocument(); }], - ["savepage", "Save page to disk", + savepage: ["Save page to disk", function () { window.saveDocument(window.content.document); }], - /*["searchengines", "Manage installed search engines", - function () { openDialog("chrome://browser/content/search/engineManager.xul", "_blank", "chrome,dialog,modal,centerscreen"); }], - ["selectionsource", "View selection source", - function () { buffer.viewSelectionSource(); }]*/ - ], + }, + + defaults: { + guioptions: "frb", + showtabline: 1, + titlestring: "Muttator" + }, + + /*** optional options, there are checked for existence and a fallback provided ***/ + features: ["hints", "mail", "marks", "addressbook", "tabs"], focusChange: function (win) { // we switch to -- MESSAGE -- mode for Muttator, when the main HTML widget gets focus @@ -102,23 +72,28 @@ const Config = Module("config", ConfigBase, { } }, - get browser() getBrowser(), - tabbrowser: { - __proto__: document.getElementById("tabmail"), - get mTabContainer() this.tabContainer, - get mTabs() this.tabContainer.childNodes, - get mCurrentTab() this.tabContainer.selectedItem, - get mStrip() this.tabStrip, - get browsers() [browser for (browser in Iterator(this.mTabs))] + guioptions: { + m: ["MenuBar", ["mail-toolbar-menubar2"]], + T: ["Toolbar" , ["mail-bar2"]], + f: ["Folder list", ["folderPaneBox", "folderpane_splitter"]], + F: ["Folder list header", ["folderPaneHeader"]] }, // they are sorted by relevance, not alphabetically helpFiles: ["intro.html", "version.html"], + get isComposeWindow() window.wintype == "msgcompose", + + get mainWidget() this.isComposeWindow ? document.getElementById("content-frame") : GetThreadTree(), + + get mainWindowId() this.isComposeWindow ? "msgcomposeWindow" : "messengerWindow", + modes: [ ["MESSAGE", { char: "m" }], ["COMPOSE"] ], + get browserModes() [modes.MESSAGE], + get mailModes() [modes.NORMAL], // NOTE: as I don't use TB I have no idea how robust this is. --djk get outputHeight() { @@ -137,15 +112,35 @@ const Config = Module("config", ConfigBase, { return document.getElementById("appcontent").boxObject.height; }, + removeTab: function (tab) { + if (config.tabbrowser.mTabs.length > 1) + config.tabbrowser.removeTab(tab); + else + dactyl.beep(); + }, + get scripts() this.isComposeWindow ? ["compose/compose.js"] : [ "addressbook.js", "mail.js", "tabs.js", ], + styleableChrome: ["chrome://messenger/content/messenger.xul", + "chrome://messenger/content/messengercompose/messengercompose.xul"], + + tabbrowser: { + __proto__: document.getElementById("tabmail"), + get mTabContainer() this.tabContainer, + get mTabs() this.tabContainer.childNodes, + get mCurrentTab() this.tabContainer.selectedItem, + get mStrip() this.tabStrip, + get browsers() [browser for (browser in Iterator(this.mTabs))] + }, + // to allow Vim to :set ft=mail automatically tempFile: "mutt-ator-mail", + get visualbellWindow() document.getElementById(this.mainWindowId), }, { }, { commands: function () { diff --git a/pentadactyl/NEWS b/pentadactyl/NEWS index 351c91f5..d0b1eb64 100755 --- a/pentadactyl/NEWS +++ b/pentadactyl/NEWS @@ -1,9 +1,11 @@ 2009-XX-XX: + * Use only visible tabs for tab numbering, gt/gn/gN, etc. + * Group tabs in :buffer completions by panorama groups * Replaced 'focuscontent' with 'strictfocus' * Replaced previous incremental search implementation * gf now toggles between source and content view. '|' key binding has been removed. - * :open now only opens files begining with / or ./ + * :open now only opens files begining with /, ./, or ~/ * Page zoom information is now shown in the status bar * Added ZO, ZI, ZM, and ZR as aliases for zO, zI, zM, and zR * Add basic plugin authorship documentation diff --git a/pentadactyl/content/config.js b/pentadactyl/content/config.js index 2581e547..64bebd44 100644 --- a/pentadactyl/content/config.js +++ b/pentadactyl/content/config.js @@ -13,8 +13,26 @@ const Config = Module("config", ConfigBase, { name: "Pentadactyl", hostApplication: "Firefox", - /*** optional options, there are checked for existence and a fallback provided ***/ - features: ["bookmarks", "hints", "history", "marks", "quickmarks", "sanitizer", "session", "tabs", "tabs_undo", "windows"], + get visualbellWindow() getBrowser().mPanelContainer, + styleableChrome: ["chrome://browser/content/browser.xul"], + + autocommands: { + BookmarkAdd: "Triggered after a page is bookmarked", + ColorScheme: "Triggered after a color scheme has been loaded", + DOMLoad: "Triggered when a page's DOM content has fully loaded", + DownloadPost: "Triggered when a download has completed", + Fullscreen: "Triggered when the browser's fullscreen state changes", + LocationChange: "Triggered when changing tabs or when navigation to a new location", + PageLoadPre: "Triggered after a page load is initiated", + PageLoad: "Triggered when a page gets (re)loaded/opened", + PrivateMode: "Triggered when private mode is activated or deactivated", + Sanitize: "Triggered when a sanitizeable item is cleared", + ShellCmdPost: "Triggered after executing a shell command with :!cmd", + Enter: "Triggered after Firefox starts", + LeavePre: "Triggered before exiting Firefox, just before destroying each module", + Leave: "Triggered before exiting Firefox", + }, + defaults: { complete: "slf", guioptions: "rb", @@ -22,30 +40,6 @@ const Config = Module("config", ConfigBase, { titlestring: "Pentadactyl" }, - guioptions: { - m: ["Menubar", ["toolbar-menubar"]], - T: ["Toolbar", ["nav-bar"]], - B: ["Bookmark bar", ["PersonalToolbar"]] - }, - - get visualbellWindow() getBrowser().mPanelContainer, - styleableChrome: ["chrome://browser/content/browser.xul"], - - autocommands: [["BookmarkAdd", "Triggered after a page is bookmarked"], - ["ColorScheme", "Triggered after a color scheme has been loaded"], - ["DOMLoad", "Triggered when a page's DOM content has fully loaded"], - ["DownloadPost", "Triggered when a download has completed"], - ["Fullscreen", "Triggered when the browser's fullscreen state changes"], - ["LocationChange", "Triggered when changing tabs or when navigation to a new location"], - ["PageLoadPre", "Triggered after a page load is initiated"], - ["PageLoad", "Triggered when a page gets (re)loaded/opened"], - ["PrivateMode", "Triggered when private mode is activated or deactivated"], - ["Sanitize", "Triggered when a sanitizeable item is cleared"], - ["ShellCmdPost", "Triggered after executing a shell command with :!cmd"], - ["PentadactylEnter", "Triggered after Firefox starts"], - ["PentadactylLeavePre","Triggered before exiting Firefox, just before destroying each module"], - ["PentadactylLeave", "Triggered before exiting Firefox"]], - dialogs: { about: ["About Firefox", function () { window.openDialog("chrome://browser/content/aboutDialog.xul", "_blank", "chrome,dialog,modal,centerscreen"); }], @@ -99,10 +93,34 @@ const Config = Module("config", ConfigBase, { function () { buffer.viewSelectionSource(); }] }, + features: [ + "bookmarks", "hints", "history", "marks", "quickmarks", "sanitizer", + "session", "tabs", "tabs_undo", "windows" + ], + + guioptions: { + m: ["Menubar", ["toolbar-menubar"]], + T: ["Toolbar", ["nav-bar"]], + B: ["Bookmark bar", ["PersonalToolbar"]] + }, + hasTabbrowser: true, ignoreKeys: {}, + removeTab: function (tab) { + if (config.tabbrowser.mTabs.length > 1) + config.tabbrowser.removeTab(tab); + else { + if (buffer.URL != "about:blank" || window.getWebNavigation().sessionHistory.count > 0) { + dactyl.open("about:blank", dactyl.NEW_BACKGROUND_TAB); + config.tabbrowser.removeTab(tab); + } + else + dactyl.beep(); + } + }, + scripts: [ "browser.js", "bookmarks.js", @@ -114,7 +132,6 @@ const Config = Module("config", ConfigBase, { get tempFile() { let prefix = this.name.toLowerCase(); - try { prefix += "-" + window.content.document.location.hostname; } diff --git a/pentadactyl/content/dactyl.dtd b/pentadactyl/content/dactyl.dtd index 88260bab..dff1ecf0 100644 --- a/pentadactyl/content/dactyl.dtd +++ b/pentadactyl/content/dactyl.dtd @@ -6,6 +6,8 @@ <!ENTITY dactyl.name "pentadactyl"> <!ENTITY dactyl.idname "PENTADACTYL"> <!ENTITY dactyl.appname "Pentadactyl"> +<!ENTITY dactyl.apphome "http://dactyl.googlecode.com/"> +<!ENTITY dactyl.maillist "pentadactyl@googlegroups.com"> <!ENTITY dactyl.host "&brandShortName;"> <!ENTITY dactyl.hostbin "firefox"> <!ENTITY dactyl.statusBefore "statusbar-display"> diff --git a/pentadactyl/locale/en-US/autocommands.xml b/pentadactyl/locale/en-US/autocommands.xml index 637ce318..78539485 100644 --- a/pentadactyl/locale/en-US/autocommands.xml +++ b/pentadactyl/locale/en-US/autocommands.xml @@ -19,9 +19,9 @@ <dt>PrivateMode</dt> <dd>Triggered when private mode is activated or deactivated</dd> <dt>Sanitize</dt> <dd>Triggered when privata data are sanitized</dd> <dt>ShellCmdPost</dt> <dd>Triggered after executing a shell command with <ex>:!</ex><a>cmd</a></dd> - <dt>&dactyl.appname;Enter</dt> <dd>Triggered after &dactyl.host; starts</dd> - <dt>&dactyl.appname;LeavePre</dt><dd>Triggered before exiting &dactyl.host;, just before destroying each module</dd> - <dt>&dactyl.appname;Leave</dt> <dd>Triggered before exiting &dactyl.host;</dd> + <dt>Enter</dt> <dd>Triggered after &dactyl.host; starts</dd> + <dt>LeavePre</dt> <dd>Triggered before exiting &dactyl.host;, just before destroying each module</dd> + <dt>Leave</dt> <dd>Triggered before exiting &dactyl.host;</dd> </dl> <dl tag="autocommand-args" replace="autocommand-args"> diff --git a/pentadactyl/locale/en-US/intro.xml b/pentadactyl/locale/en-US/intro.xml index 07085326..da125455 100644 --- a/pentadactyl/locale/en-US/intro.xml +++ b/pentadactyl/locale/en-US/intro.xml @@ -14,49 +14,29 @@ <link topic="http://vimperator.org">&dactyl.appname;</link> is a free browser add-on for &dactyl.host;, which makes it look and behave like the <link topic="http://www.vim.org">Vim</link> -text editor. It has similar key bindings, and you could call it a modal -web browser, as key bindings differ according to which mode you are in. +text editor. <warning tag="warning"> - To provide the most authentic Vim experience, the &dactyl.host; menubar and toolbar are hidden. + To provide the most authentic Vim experience, the &dactyl.host; + menubar and toolbar are hidden. + + <p> + If you really need them, type: <ex>:set guioptions+=mT</ex> to + get them back. + </p> + + <p> + If you don't like &dactyl.appname; at all, you can uninstall it + by typing <ex>:extdelete &dactyl.appname;</ex> or disable it + with <ex>:extdisable &dactyl.appname;</ex> + </p> + + <p> + If you like it but can't remember the shortcuts, then type + <key name="F1"/> or <ex>:help</ex> to get this help window back. + </p> </warning> -<p>If you really need them, type: <ex>:set guioptions+=mT</ex> to get them back.</p> -<p> - If you don't like &dactyl.appname; at all, you can uninstall it by typing - <ex>:extdelete &dactyl.appname;</ex> or <ex>:extdisable &dactyl.appname;</ex> to disable it. -</p> -<p> - If you like it but can't remember the shortcuts, then press - <key name="F1"/> or <ex>:help</ex> to get this help window back. -</p> - -<tags>author donaton sponsor</tags> -<strut/> -<p> - &dactyl.appname; was initially written by - <link topic="mailto:stubenschrott@vimperator.org">Martin - Stubenschrott</link> but has found many other - <link topic="http://vimperator.org/trac/wiki/&dactyl.appname;/Authors">contributors</link> - in the meanwhile. If you appreciate the work on &dactyl.appname; and want to - encourage us working on it more, you can send us greetings, patches, or - donations (thanks a lot to - <link topic="http://vimperator.org/trac/wiki/&dactyl.appname;/Donors">these - people</link> - who already did): -</p> - -<pan><handle/></pan> - -<p> - If you prefer getting some nice products for your money, you can also support - us by buying some cool - <link topic="http://www.zazzle.com/maxauthority*">merchandise</link> like - t-shirts or mugs. Of course, as we believe in free, open source software, only - support us financially if you really like &dactyl.appname; and the money doesn't hurt - — otherwise just use it, recommend it, and like it :) -</p> - <h2 tag="overview">Help topics</h2> <ol> @@ -156,10 +136,11 @@ web browser, as key bindings differ according to which mode you are in. </ol> <p> -You can also jump directly to the help of a specific command with <ex>:help o</ex> -or <ex>:help :set</ex>. + You can also jump directly to the help of a specific command via the <ex>:help</ex> command. </p> +<example><ex>:help :help</ex></example> + <h2 tag="features">Features</h2> <ul> @@ -188,15 +169,14 @@ or <ex>:help :set</ex>. <h2 tag="contact">Contact</h2> <p> - Please send comments/bug reports/patches to the mailing list, where we will - properly answer any questions. You can also join the - <link topic="irc://irc.freenode.net/pentadactyl">#pentadactyl</link> IRC channel - on <link target="http://www.freenode.net/">Freenode</link> or check the - <link topic="http://vimperator.org/trac/wiki/&dactyl.appname;/Wiki">Wiki</link> for - <link topic="http://vimperator.org/trac/wiki/&dactyl.appname;/FAQ"> - frequently asked questions (FAQ) - </link>. Make sure, you have read the TODO file first, as we are aware of many - things which can be improved when we find time for it or receive patches. + Please send comments, questions, or patches to the + <link topic="mailto:&dactyl.maillist;">mailing list</link>, + where we will do our best to answer any questions. You can also + check the + <link topic="http://code.google.com/p/dactyl/wiki">wiki</link> for + <link topic="http://code.google.com/p/dactyl/wiki/FAQ">FAQ</link>. + Issue reports can be entered in the + <link topic="http://code.google.com/p/dactyl/issues/list">issue tracker</link>. </p> </document> diff --git a/pentadactyl/locale/en-US/options.xml b/pentadactyl/locale/en-US/options.xml deleted file mode 100644 index e795c92f..00000000 --- a/pentadactyl/locale/en-US/options.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<?xml-stylesheet type="text/xsl" href="chrome://dactyl/content/help.xsl"?> - -<!DOCTYPE overlay SYSTEM "chrome://dactyl/content/dactyl.dtd"> - -<overlay - xmlns="http://vimperator.org/namespaces/liberator" - xmlns:html="http://www.w3.org/1999/xhtml"> - -<item insertbefore=":set"> - <tags>:seg :way</tags> - <spec>:se[a]</spec> - <description> - <p>Show all options that differ from their default value.</p> - </description> -</item> - -<item insertafter=":set-default"> - <tags>:sturm :drang</tags> - <spec>:und</spec> - <description> - <p>Show all options that differ from their default value.</p> - </description> -</item> -</overlay> -<!-- vim:se sts=4 sw=4 et: --> diff --git a/pentadactyl/locale/en-US/tutorial.xml b/pentadactyl/locale/en-US/tutorial.xml index 782d5316..ab522e10 100644 --- a/pentadactyl/locale/en-US/tutorial.xml +++ b/pentadactyl/locale/en-US/tutorial.xml @@ -122,20 +122,24 @@ Scrolling the browser window is done with simple keystrokes: </p> -<ul> - <li><k>j</k>/<k>k</k> – +<dl> + <dt><k>j</k>/<k>k</k></dt> + <dd> scroll window down/up by one line, respectively - </li> - <li><k>h</k>/<k>l</k> – + </dd> + <dt><k>h</k>/<k>l</k></dt> + <dd> scroll window left/right - </li> - <li><k name="Space"/>/<k name="C-b"/> – + </dd> + <dt><k name="Space"/>/<k name="C-b"/></dt> + <dd> scroll down/up by one page - </li> - <li><k name="C-d"/>/<k name="C-u"/> – + </dd> + <dt><k name="C-d"/>/<k name="C-u"/></dt> + <dd> scroll down/up by 1/2 page - </li> -</ul> + </dd> +</dl> <p> Your standard buttons (<k name="Up"/>/<k name="Down"/>/<k name="PgUp"/>/<k name="PgDn"/>) will @@ -149,31 +153,36 @@ scrolling. </p> -<ul> - <li><k name="C-o"/>/<k name="C-i"/> – +<dl> + <dt><k name="C-o"/>/<k name="C-i"/></dt> + <dd> move Back/Forward in the current window/tab's history, respectively - </li> -</ul> + </dd> +</dl> <p> Move between tabs using these keystrokes which may also be familiar to tabbing Vimmers. </p> -<ul> - <li><k>gt</k>/<k name="C-n"/> – +<dl> + <dt><k>gt</k>/<k name="C-n"/></dt> + <dd> go to the next tab - </li> - <li><k>gT</k>/<k name="C-p"/> – + </dd> + <dt><k>gT</k>/<k name="C-p"/></dt> + <dd> go to the previous tab - </li> - <li><k>g0</k>/<k>g$</k> – + </dd> + <dt><k>g0</k>/<k>g$</k></dt> + <dd> go to the first/last tab - </li> - <li><k>d</k> – + </dd> + <dt><k>d</k></dt> + <dd> close the active tab (delete the buffer) - </li> -</ul> + </dd> +</dl> <p> To open a web page in a new tab, use the <ex>:tabopen <a>url</a></ex>. To open a URL in @@ -186,8 +195,8 @@ <ex>:open my.webmail.com<key name="CR"/></ex> <k>o</k>my.webmail.com<key name="CR"/> -<ex>:tabopen vimperator.org<key name="CR"/></ex> -<k>t</k>vimperator.org<key name="CR"/> +<ex>:tabopen google.com<key name="CR"/></ex> +<k>t</k>google.com<key name="CR"/> </code> <h2 tag="hints-tutorial">Some hints about surfing…</h2> @@ -225,7 +234,7 @@ </p> <p> - To test it, try this link: <link target="http://vimperator.org/">&dactyl.appname; Homepage</link>. + To test it, try this link: <link target="&dactyl.apphome;">&dactyl.appname; Homepage</link>. Activate QuickHint mode with <k>f</k> or <k>F</k> to highlight all currently visible links. Then start typing the text of the link. The link should be uniquely identified soon, and &dactyl.appname; will open it. Once you're done, @@ -250,7 +259,7 @@ <code><k name="Esc"/></code> -<h2 tag="pentadactylrc">Saving for posterity - pentadactylrc</h2> +<h2 tag="pentadactylrc">Saving for posterity—pentadactylrc</h2> <p> Once you get &dactyl.appname; set up with your desired options, maps, and commands, @@ -275,17 +284,24 @@ &dactyl.appname; supports all of Vim's classic methods of exiting. </p> -<ul> - <li><ex>:xall</ex> – command to quit and save the current browsing - session for next time; the default. - </li> - <li><ex>:qall</ex> – command to quit <em>without</em> saving the session - </li> - <li><k>ZZ</k> – Normal mode mapping equivalent to <ex>:xall</ex> - </li> - <li><k>ZQ</k> – Normal mode mapping equivalent to <ex>:qall</ex> - </li> -</ul> +<dl> + <dt><ex>:xall</ex></dt> + <dd> + command to quit and save the current browsing session for next time; the default. + </dd> + <dt><ex>:qall</ex></dt> + <dd> + command to quit <em>without</em> saving the session + </dd> + <dt><k>ZZ</k></dt> + <dd> + Normal mode mapping equivalent to <ex>:xall</ex> + </dd> + <dt><k>ZQ</k></dt> + <dd> + Normal mode mapping equivalent to <ex>:qall</ex> + </dd> +</dl> <h2 tag="whither-&dactyl.host;">Where did &dactyl.host; go?</h2> @@ -296,24 +312,28 @@ make the best use of them. </p> -<ul> - <li><ex>:dialog</ex> – +<dl> + <dt><ex>:dialog</ex></dt> + <dd> To access some of &dactyl.host;'s many dialog windows, you can use the <ex>:dialog</ex> command. See <ex>:help :dialog</ex>. - </li> - <li><ex>:bmarks</ex> – + </dd> + <dt><ex>:bmarks</ex></dt> + <dd> &dactyl.appname; provides a new interface to bookmarks, but they're still your standard &dactyl.host; bookmarks under the hood. <ex>:bmark</ex> will add a new bookmark, while <ex>:bmarks</ex> will list the bookmarks currently defined. - </li> - <li><ex>:history</ex> – + </dd> + <dt><ex>:history</ex></dt> + <dd> It's exactly what it sounds like. This command will display a colorized, scrollable and clickable list of the locations in &dactyl.appname;'s history. - </li> - <li><ex>:emenu</ex> – + </dd> + <dt><ex>:emenu</ex></dt> + <dd> Access the &dactyl.host; menus through the &dactyl.appname; command line. - </li> -</ul> + </dd> +</dl> <p> Feel free to explore at this point. If you use the <ex>:tabopen</ex> command, diff --git a/xulmus/content/config.js b/xulmus/content/config.js index 1a19a3a4..6eee5298 100644 --- a/xulmus/content/config.js +++ b/xulmus/content/config.js @@ -44,98 +44,74 @@ const Config = Module("config", ConfigBase, { styleableChrome: ["chrome://gonzo/content/xul/mainplayer.xul"], - autocommands: [["BookmarkAdd", "Triggered after a page is bookmarked"], - ["ColorScheme", "Triggered after a color scheme has been loaded"], - ["DOMLoad", "Triggered when a page's DOM content has fully loaded"], - ["DownloadPost", "Triggered when a download has completed"], - ["Fullscreen", "Triggered when the browser's fullscreen state changes"], - ["LocationChange", "Triggered when changing tabs or when navigation to a new location"], - ["PageLoadPre", "Triggered after a page load is initiated"], - ["PageLoad", "Triggered when a page gets (re)loaded/opened"], - ["ShellCmdPost", "Triggered after executing a shell command with :!cmd"], - ["TrackChangePre", "Triggered before a playing track is changed"], - ["TrackChange", "Triggered after a playing track has changed"], - ["ViewChangePre", "Triggered before a sequencer view is changed"], - ["ViewChange", "Triggered after a sequencer view is changed"], - ["StreamStart", "Triggered after a stream has started"], - ["StreamPause", "Triggered after a stream has paused"], - ["StreamEnd", "Triggered after a stream has ended"], - ["StreamStop", "Triggered after a stream has stopped"], - ["XulmusEnter", "Triggered after Songbird starts"], - ["XulmusLeavePre", "Triggered before exiting Songbird, just before destroying each module"], - ["XulmusLeave", "Triggered before exiting Songbird"]], + autocommands: { + BookmarkAdd: "Triggered after a page is bookmarked", + ColorScheme: "Triggered after a color scheme has been loaded", + DOMLoad: "Triggered when a page's DOM content has fully loaded", + DownloadPost: "Triggered when a download has completed", + Fullscreen: "Triggered when the browser's fullscreen state changes", + LocationChange: "Triggered when changing tabs or when navigation to a new location", + PageLoadPre: "Triggered after a page load is initiated", + PageLoad: "Triggered when a page gets (re)loaded/opened", + ShellCmdPost: "Triggered after executing a shell command with :!cmd", + TrackChangePre: "Triggered before a playing track is changed", + TrackChange: "Triggered after a playing track has changed", + ViewChangePre: "Triggered before a sequencer view is changed", + ViewChange: "Triggered after a sequencer view is changed", + StreamStart: "Triggered after a stream has started", + StreamPause: "Triggered after a stream has paused", + StreamEnd: "Triggered after a stream has ended", + StreamStop: "Triggered after a stream has stopped", + Enter: "Triggered after Songbird starts", + LeavePre: "Triggered before exiting Songbird, just before destroying each module", + Leave: "Triggered before exiting Songbird", + }, // TODO: remove those which don't make sense, can't be provided. - dialogs: [ - ["about", "About Songbird", + dialogs: { + about: ["About Songbird", function () { window.openDialog("chrome://songbird/content/xul/about.xul", "_blank", "chrome,dialog,modal,centerscreen"); }], - /* - ["addbookmark", "Add bookmark for the current page", - function () { PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksRootId); }], - */ - ["addons", "Manage Add-ons", + addons: ["Manage Add-ons", function () { SBOpenPreferences("paneAddons"); }], - /* - ["bookmarks", "List your bookmarks", - function () { window.openDialog("chrome://browser/content/bookmarks/bookmarksPanel.xul", "Bookmarks", "dialog,centerscreen,width=600,height=600"); }], - */ - ["checkupdates", "Check for updates", + checkupdates: ["Check for updates", function () { window.checkForUpdates(); }], - ["cleardata", "Clear private data", + cleardata: ["Clear private data", function () { Sanitizer.showUI(); }], - ["cookies", "List your cookies", + cookies: ["List your cookies", function () { window.toOpenWindowByType("Browser:Cookies", "chrome://browser/content/preferences/cookies.xul", "chrome,dialog=no,resizable"); }], - ["console", "JavaScript console", + console: ["JavaScript console", function () { window.toJavaScriptConsole(); }], - /* - ["customizetoolbar", "Customize the Toolbar", - function () { window.BrowserCustomizeToolbar(); }], - */ - ["dominspector", "DOM Inspector", + dominspector: ["DOM Inspector", function () { try { window.inspectDOMDocument(content.document); } catch (e) { dactyl.echoerr("DOM Inspector extension not installed"); } }], - ["downloads", "Manage Downloads", + downloads: ["Manage Downloads", function () { window.toOpenWindowByType("Download:Manager", "chrome://mozapps/content/downloads/downloads.xul", "chrome,dialog=no,resizable"); }], - /* - ["history", "List your history", - function () { window.openDialog("chrome://browser/content/history/history-panel.xul", "History", "dialog,centerscreen,width=600,height=600"); }], - ["import", "Import Preferences, Bookmarks, History, etc. from other browsers", - function () { window.BrowserImport(); }], - */ - ["jumpto", "Jump to a media item", + jumpto: ["Jump to a media item", function () { onJumpToFileKey(); }], - ["newsmartplaylist", "Open the file selector dialog", + newsmartplaylist: ["Open the file selector dialog", function () { SBNewSmartPlaylist(); }], - ["openfile", "Open the file selector dialog", + openfile: ["Open the file selector dialog", function () { SBFileOpen(); }], - /* - ["pageinfo", "Show information about the current page", - function () { window.BrowserPageInfo(); }], - */ - ["pagesource", "View page source", + pagesource: ["View page source", function () { window.BrowserViewSourceOfDocument(content.document); }], - ["places", "Places Organizer: Manage your bookmarks and history", + places: ["Places Organizer: Manage your bookmarks and history", function () { PlacesCommandHook.showPlacesOrganizer(ORGANIZER_ROOT_BOOKMARKS); }], - ["preferences", "Show Songbird preferences dialog", + preferences: ["Show Songbird preferences dialog", function () { window.openPreferences(); }], - /* - ["printpreview", "Preview the page before printing", - function () { PrintUtils.printPreview(onEnterPrintPreview, onExitPrintPreview); }], - */ - ["printsetup", "Setup the page size and orientation before printing", + printsetup: ["Setup the page size and orientation before printing", function () { PrintUtils.showPageSetup(); }], - ["print", "Show print dialog", + print: ["Show print dialog", function () { PrintUtils.print(); }], - ["saveframe", "Save frame to disk", + saveframe: ["Save frame to disk", function () { window.saveFrameDocument(); }], - ["savepage", "Save page to disk", + savepage: ["Save page to disk", function () { window.saveDocument(window.content.document); }], - ["searchengines", "Manage installed search engines", + searchengines: ["Manage installed search engines", function () { window.openDialog("chrome://browser/content/search/engineManager.xul", "_blank", "chrome,dialog,modal,centerscreen"); }], - ["selectionsource", "View selection source", + selectionsource: ["View selection source", function () { buffer.viewSelectionSource(); }], - ["subscribe", "Add a new subscription", + subscribe: ["Add a new subscription", function () { SBSubscribe(); }] - ], + }, focusChange: function () { // Switch to -- PLAYER -- mode for Songbird Media Player. @@ -153,14 +129,27 @@ const Config = Module("config", ConfigBase, { modes: [["PLAYER", { char: "p" }]], + removeTab: function (tab) { + if (config.tabbrowser.mTabs.length > 1) + config.tabbrowser.removeTab(tab); + else { + if (buffer.URL != "about:blank" || window.getWebNavigation().sessionHistory.count > 0) { + dactyl.open("about:blank", dactyl.NEW_BACKGROUND_TAB); + config.tabbrowser.removeTab(tab); + } + else + dactyl.beep(); + } + }, + scripts: [ - "browser.js", - "bookmarks.js", - "history.js", - "quickmarks.js", - "tabs.js", - "player.js", - "library.js" + "browser", + "bookmarks", + "history", + "quickmarks", + "tabs", + "player", + "library" ], // FIXME: tab arg and media tab exception? |