X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fcontent%2Ftabs.js;h=fb155db8d36539cf5a309521d74ba06cfdb49c6e;hb=8b6fcae7eaa413bc62d645d2d0c99835c47265e6;hp=c6210807247f555928f78738202d1896178639cb;hpb=70740024f9c028c1fd63e1a1850ab062ff956054;p=dactyl.git diff --git a/common/content/tabs.js b/common/content/tabs.js index c621080..fb155db 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -1,6 +1,6 @@ // Copyright (c) 2006-2008 by Martin Stubenschrott // Copyright (c) 2007-2011 by Doug Kearns -// Copyright (c) 2008-2011 by Kris Maglione +// Copyright (c) 2008-2012 Kris Maglione // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. @@ -23,7 +23,7 @@ var Tabs = Module("tabs", { // hide tabs initially to prevent flickering when 'stal' would hide them // on startup - if (config.hasTabbrowser) + if (config.has("tabbrowser")) config.tabStrip.collapsed = true; this.tabStyle = styles.system.add("tab-strip-hiding", config.styleableChrome, @@ -32,12 +32,12 @@ var Tabs = Module("tabs", { false, true); dactyl.commands["tabs.select"] = function (event) { - tabs.select(event.originalTarget.getAttribute("identifier")); + tabs.switchTo(event.originalTarget.getAttribute("identifier")); }; - this.tabBinding = styles.system.add("tab-binding", "chrome://browser/content/browser.xul", String.replace(<>, /tab-./g, function (m) util.OS.isMacOSX ? "tab-mac" : m), + */).replace(/tab-./g, function (m) config.OS.isMacOSX ? "tab-mac" : m), false, true); this.timeout(function () { @@ -57,7 +57,7 @@ var Tabs = Module("tabs", { } }, - _alternates: Class.memoize(function () [config.tabbrowser.mCurrentTab, null]), + _alternates: Class.Memoize(function () [config.tabbrowser.mCurrentTab, null]), cleanup: function cleanup() { for (let [i, tab] in Iterator(this.allTabs)) { @@ -65,6 +65,9 @@ var Tabs = Module("tabs", { for (let elem in values(["dactyl-tab-icon-number", "dactyl-tab-number"].map(node))) if (elem) elem.parentNode.parentNode.removeChild(elem.parentNode); + + delete tab.dactylOrdinal; + tab.removeAttribute("dactylOrdinal"); } }, @@ -75,18 +78,25 @@ var Tabs = Module("tabs", { if (!node("dactyl-tab-number")) { let img = node("tab-icon-image"); if (img) { - let nodes = {}; - let dom = util.xmlToDom(.*, document, nodes); - img.parentNode.appendChild(dom); - tab.__defineGetter__("dactylOrdinal", function () Number(nodes.icon.value)); - tab.__defineSetter__("dactylOrdinal", function (i) nodes.icon.value = nodes.label.textContent = i); + let dom = DOM([ + ["xul:hbox", { highlight: "tab-number" }, + ["xul:label", { key: "icon", align: "center", highlight: "TabIconNumber", + class: "dactyl-tab-icon-number" }]], + ["xul:hbox", { highlight: "tab-number" }, + ["html:div", { key: "label", highlight: "TabNumber", + class: "dactyl-tab-number" }]]], + document).appendTo(img.parentNode); + + update(tab, { + get dactylOrdinal() Number(dom.nodes.icon.value), + set dactylOrdinal(i) { + dom.nodes.icon.value = dom.nodes.label.textContent = i; + this.setAttribute("dactylOrdinal", i); + } + }); } } } - tab.setAttribute("dactylOrdinal", i + 1); tab.dactylOrdinal = i + 1; } statusline.updateTabCount(true); @@ -127,12 +137,7 @@ var Tabs = Module("tabs", { /** * @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 options() this.localStore.options, get visibleTabs() config.tabbrowser.visibleTabs || this.allTabs.filter(function (tab) !tab.hidden), @@ -151,8 +156,8 @@ var Tabs = Module("tabs", { getLocalStore: function getLocalStore(tabIndex) { let tab = this.getTab(tabIndex); if (!tab.dactylStore) - tab.dactylStore = {}; - return tab.dactylStore; + tab.dactylStore = Object.create(this.localStorePrototype); + return tab.dactylStore.instance = tab.dactylStore; }, /** @@ -161,6 +166,11 @@ var Tabs = Module("tabs", { */ get localStore() this.getLocalStore(), + localStorePrototype: memoize({ + instance: {}, + get options() ({}) + }), + /** * @property {[Object]} The array of closed tabs for the current * session. @@ -219,16 +229,21 @@ var Tabs = Module("tabs", { * * @returns {Window} */ - getGroups: function getGroups() { - if ("_groups" in this) + getGroups: function getGroups(func) { + let iframe = document.getElementById("tab-view"); + this._groups = iframe ? iframe.contentWindow : null; + + if ("_groups" in this && !func) return this._groups; - if (window.TabView && TabView._initFrame) - TabView._initFrame(); + if (func) + func = bind(function (func) { func(this._groups) }, this, func); + + if (window.TabView && window.TabView._initFrame) + window.TabView._initFrame(func); - let iframe = document.getElementById("tab-view"); this._groups = iframe ? iframe.contentWindow : null; - if (this._groups) + if (this._groups && !func) util.waitFor(function () this._groups.TabItems, this); return this._groups; }, @@ -320,6 +335,60 @@ var Tabs = Module("tabs", { completion.listCompleter("buffer", filter); }, + + /** + * Return an iterator of tabs matching the given filter. If no + * *filter* or *count* is provided, returns the currently selected + * tab. If *filter* is a number or begins with a number followed + * by a colon, the tab of that ordinal is returned. Otherwise, + * tabs matching the filter as below are returned. + * + * @param {string} filter The filter. If *regexp*, this is a + * regular expression against which the tab's URL or title + * must match. Otherwise, it is a site filter. + * @optional + * @param {number|null} count If non-null, return only the + * *count*th matching tab. + * @optional + * @param {boolean} regexp Whether to interpret *filter* as a + * regular expression. + * @param {boolean} all If true, match against all tabs. If + * false, match only tabs in the current tab group. + */ + match: function match(filter, count, regexp, all) { + if (!filter && count == null) + yield tabs.getTab(); + else if (!filter) + yield dactyl.assert(tabs.getTab(count - 1)); + else { + let matches = /^(\d+)(?:$|:)/.exec(filter); + if (matches) + yield dactyl.assert(count == null && + tabs.getTab(parseInt(matches[1], 10) - 1, !all)); + else { + if (regexp) + regexp = util.regexp(filter, "i"); + else + var matcher = Styles.matchFilter(filter); + + for (let tab in values(tabs[all ? "allTabs" : "visibleTabs"])) { + let browser = tab.linkedBrowser; + let uri = browser.currentURI; + let title; + if (uri.spec == "about:blank") + title = "(Untitled)"; + else + title = browser.contentTitle; + + if (matcher && matcher(uri) + || regexp && (regexp.test(title) || regexp.test(uri.spec))) + if (count == null || --count == 0) + yield tab; + } + } + } + }, + /** * Moves a tab to a new position in the tab list. * @@ -531,75 +600,49 @@ var Tabs = Module("tabs", { services.sessionStore.setTabState(to, tabState); } }, { - load: function () { + load: function initLoad() { tabs.updateTabCount(); }, - commands: function () { - commands.add(["bd[elete]", "bw[ipeout]", "bun[load]", "tabc[lose]"], - "Delete current buffer", - function (args) { - let removed = 0; - for (let tab in matchTabs(args, args.bang, true)) { - config.removeTab(tab); - removed++; - } - - if (args[0]) - if (removed > 0) - dactyl.echomsg(_("buffer.fewerTab" + (removed == 1 ? "" : "s"), removed), 9); - else - dactyl.echoerr(_("buffer.noMatching", arg)); - }, { - argCount: "?", - bang: true, - count: true, - completer: function (context) completion.buffer(context), - literal: 0, - privateData: true - }); - - function matchTabs(args, substr, all) { - let filter = args[0]; - - if (!filter && args.count == null) - yield tabs.getTab(); - else if (!filter) - yield dactyl.assert(tabs.getTab(args.count - 1)); - else { - let matches = /^(\d+)(?:$|:)/.exec(filter); - if (matches) - yield dactyl.assert(args.count == null && - tabs.getTab(parseInt(matches[1], 10) - 1, !all)); - else { - let str = filter.toLowerCase(); - for (let tab in values(tabs[all ? "allTabs" : "visibleTabs"])) { - let host, title; - let browser = tab.linkedBrowser; - let uri = browser.currentURI.spec; - if (browser.currentURI.schemeIs("about")) { - host = ""; - title = "(Untitled)"; - } - else { - host = browser.currentURI.host; - title = browser.contentTitle; - } - - [host, title, uri] = [host, title, uri].map(String.toLowerCase); - - if (host.indexOf(str) >= 0 || uri == str || - (substr && (title.indexOf(str) >= 0 || uri.indexOf(str) >= 0))) - if (args.count == null || --args.count == 0) - yield tab; - } - } + commands: function initCommands() { + [ + { + name: ["bd[elete]"], + description: "Delete matching buffers", + visible: false + }, + { + name: ["tabc[lose]"], + description: "Delete matching tabs", + visible: true } - } + ].forEach(function (params) { + commands.add(params.name, params.description, + function (args) { + let removed = 0; + for (let tab in tabs.match(args[0], args.count, args.bang, !params.visible)) { + config.removeTab(tab); + removed++; + } + + if (args[0]) + if (removed > 0) + dactyl.echomsg(_("buffer.fewerTab" + (removed == 1 ? "" : "s"), removed), 9); + else + dactyl.echoerr(_("buffer.noMatching", args[0])); + }, { + argCount: "?", + bang: true, + count: true, + completer: function (context) completion.buffer(context), + literal: 0, + privateData: true + }); + }); commands.add(["pin[tab]"], "Pin tab as an application tab", function (args) { - for (let tab in matchTabs(args)) + for (let tab in tabs.match(args[0], args.count)) config.browser[!args.bang || !tab.pinned ? "pinTab" : "unpinTab"](tab); }, { @@ -616,7 +659,7 @@ var Tabs = Module("tabs", { commands.add(["unpin[tab]"], "Unpin tab as an application tab", function (args) { - for (let tab in matchTabs(args)) + for (let tab in tabs.match(args[0], args.count)) config.browser.unpinTab(tab); }, { @@ -647,8 +690,22 @@ var Tabs = Module("tabs", { commands.add(["tab"], "Execute a command and tell it to output in a new tab", function (args) { - dactyl.withSavedValues(["forceNewTab"], function () { - this.forceNewTab = true; + dactyl.withSavedValues(["forceTarget"], function () { + this.forceTarget = dactyl.NEW_TAB; + dactyl.execute(args[0], null, true); + }); + }, { + argCount: "1", + completer: function (context) completion.ex(context), + literal: 0, + subCommand: 0 + }); + + commands.add(["background", "bg"], + "Execute a command opening any new tabs in the background", + function (args) { + dactyl.withSavedValues(["forceBackground"], function () { + this.forceBackground = true; dactyl.execute(args[0], null, true); }); }, { @@ -735,7 +792,7 @@ var Tabs = Module("tabs", { function () { tabs.select(0, false); }, { argCount: "0" }); - if (config.hasTabbrowser) { + if (config.has("tabbrowser")) { commands.add(["b[uffer]"], "Switch to a buffer", function (args) { tabs.switchTo(args[0], args.bang, args.count); }, { @@ -778,10 +835,10 @@ var Tabs = Module("tabs", { let arg = args[0]; if (tabs.indexFromSpec(arg) == -1) { - let tabs = [tab for (tab in matchTabs(args, true))]; - dactyl.assert(tabs.length, _("error.invalidArgument", arg)); - dactyl.assert(tabs.length == 1, _("buffer.multipleMatching", arg)); - arg = tabs[0]; + let list = [tab for (tab in tabs.match(args[0], args.count, true))]; + dactyl.assert(list.length, _("error.invalidArgument", arg)); + dactyl.assert(list.length == 1, _("buffer.multipleMatching", arg)); + arg = list[0]; } tabs.move(tabs.getTab(), arg, args.bang); }, { @@ -799,7 +856,8 @@ var Tabs = Module("tabs", { commands.add(["tabopen", "t[open]", "tabnew"], "Open one or more URLs in a new tab", function (args) { - dactyl.open(args[0] || "about:blank", { from: "tabopen", where: dactyl.NEW_TAB, background: args.bang }); + dactyl.open(args[0] || "about:blank", + { from: "tabopen", where: dactyl.NEW_TAB, background: args.bang }); }, { bang: true, completer: function (context) completion.url(context), @@ -840,50 +898,71 @@ var Tabs = Module("tabs", { _("error.trailingCharacters")); let [winIndex, tabIndex] = args.map(function (arg) parseInt(arg)); + if (args["-group"]) { + util.assert(args.length == 1); + window.TabView.moveTabTo(tabs.getTab(), winIndex); + return; + } + let win = dactyl.windows[winIndex - 1]; + let sourceTab = tabs.getTab(); dactyl.assert(win, _("window.noIndex", winIndex)); dactyl.assert(win != window, _("window.cantAttachSame")); - let browser = win.getBrowser(); + let modules = win.dactyl.modules; + let { browser } = modules.config; if (args[1]) { - let tabList = browser.visibleTabs || browser.mTabs; + let tabList = modules.tabs.visibleTabs; let target = dactyl.assert(tabList[tabIndex]); - tabIndex = Array.indexOf(browser.mTabs, target) - 1; + tabIndex = Array.indexOf(tabs.allTabs, target) - 1; } - let dummy = browser.addTab("about:blank"); + let newTab = browser.addTab("about:blank"); browser.stop(); // XXX: the implementation of DnD in tabbrowser.xml suggests // that we may not be guaranteed of having a docshell here // without this reference? browser.docShell; - let last = browser.mTabs.length - 1; + let last = modules.tabs.allTabs.length - 1; if (args[1]) - browser.moveTabTo(dummy, tabIndex); - browser.selectedTab = dummy; // required - browser.swapBrowsersAndCloseOther(dummy, config.tabbrowser.mCurrentTab); + browser.moveTabTo(newTab, tabIndex); + browser.selectedTab = newTab; // required + browser.swapBrowsersAndCloseOther(newTab, sourceTab); }, { argCount: "+", literal: 1, completer: function (context, args) { switch (args.completeArg) { case 0: - context.filters.push(function ({ item }) item != window); - completion.window(context); + if (args["-group"]) + completion.tabGroup(context); + else { + context.filters.push(function ({ item }) item != window); + completion.window(context); + } break; case 1: - let win = dactyl.windows[Number(args[0]) - 1]; - if (!win || !win.dactyl) - context.message = _("Error", _("window.noIndex", winIndex)); - else - win.dactyl.modules.commands.get("tabmove").completer(context); + if (!args["-group"]) { + let win = dactyl.windows[Number(args[0]) - 1]; + if (!win || !win.dactyl) + context.message = _("Error", _("window.noIndex", winIndex)); + else + win.dactyl.modules.commands.get("tabmove").completer(context); + } break; } - } + }, + options: [ + { + names: ["-group", "-g"], + description: "Attach to a group rather than a window", + type: CommandOption.NOARG + } + ] }); } @@ -940,7 +1019,89 @@ var Tabs = Module("tabs", { { argCount: "0" }); } }, - events: function () { + completion: function initCompletion() { + + completion.buffer = function buffer(context, visible) { + let { tabs } = modules; + + let filter = context.filter.toLowerCase(); + + let defItem = { parent: { getTitle: function () "" } }; + + let tabGroups = {}; + tabs.getGroups(); + tabs[visible ? "visibleTabs" : "allTabs"].forEach(function (tab, i) { + let group = (tab.tabItem || tab._tabViewTabItem || 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]); + }); + + context.pushProcessor(0, function (item, text, next) [ + ["span", { highlight: "Indicator", style: "display: inline-block;" }, + item.indicator], + next.call(this, item, text) + ]); + context.process[1] = function (item, text) template.bookmarkDescription(item, template.highlightFilter(text, this.filter)); + + context.anchored = false; + context.keys = { + text: "text", + description: "url", + indicator: function (item) item.tab === tabs.getTab() ? "%" : + item.tab === tabs.alternate ? "#" : " ", + icon: "icon", + id: "id", + command: function () "tabs.select" + }; + context.compare = CompletionContext.Sort.number; + context.filters[0] = CompletionContext.Filter.textDescription; + + for (let [id, vals] in Iterator(tabGroups)) + context.fork(id, 0, this, function (context, [name, browsers]) { + context.title = [name || "Buffers"]; + context.generate = function () + Array.map(browsers, function ([i, browser]) { + let indicator = " "; + if (i == tabs.index()) + indicator = "%"; + else if (i == tabs.index(tabs.alternate)) + indicator = "#"; + + let tab = tabs.getTab(i, visible); + let url = browser.contentDocument.location.href; + i = i + 1; + + return { + text: [i + ": " + (tab.label || /*L*/"(Untitled)"), i + ": " + url], + tab: tab, + id: i, + url: url, + icon: tab.image || BookmarkCache.DEFAULT_FAVICON + }; + }); + }, vals); + }; + + completion.tabGroup = function tabGroup(context) { + context.title = ["Tab Groups"]; + context.keys = { + text: "id", + description: function (group) group.getTitle() || + group.getChildren().map(function (t) t.tab.label).join(", ") + }; + context.generate = function () { + context.incomplete = true; + tabs.getGroups(function ({ GroupItems }) { + context.incomplete = false; + context.completions = GroupItems.groupItems; + }); + }; + }; + }, + events: function initEvents() { let tabContainer = config.tabbrowser.mTabContainer; function callback() { tabs.timeout(function () { this.updateTabCount(); }); @@ -949,7 +1110,18 @@ var Tabs = Module("tabs", { events.listen(tabContainer, event, callback, false); events.listen(tabContainer, "TabSelect", tabs.closure._onTabSelect, false); }, - mappings: function () { + mappings: function initMappings() { + + mappings.add([modes.COMMAND], ["", ""], + "Execute the next mapping in a new tab", + function ({ count }) { + dactyl.forceTarget = dactyl.NEW_TAB; + mappings.afterCommands((count || 1) + 1, function () { + dactyl.forceTarget = null; + }); + }, + { count: true }); + mappings.add([modes.NORMAL], ["g0", "g^"], "Go to the first tab", function () { tabs.select(0); }); @@ -978,7 +1150,7 @@ var Tabs = Module("tabs", { function ({ count }) { tabs.select("-" + (count || 1), true); }, { count: true }); - if (config.hasTabbrowser) { + if (config.has("tabbrowser")) { mappings.add([modes.NORMAL], ["b"], "Open a prompt to switch buffers", function ({ count }) { @@ -1004,12 +1176,12 @@ var Tabs = Module("tabs", { { count: true }); mappings.add([modes.NORMAL], ["gb"], - "Repeat last :buffer[!] command", + "Repeat last :buffer command", function ({ count }) { tabs.switchTo(null, null, count, false); }, { count: true }); mappings.add([modes.NORMAL], ["gB"], - "Repeat last :buffer[!] command in reverse direction", + "Repeat last :buffer command in reverse direction", function ({ count }) { tabs.switchTo(null, null, count, true); }, { count: true }); @@ -1032,17 +1204,17 @@ var Tabs = Module("tabs", { { count: true }); } }, - options: function () { + options: function initOptions() { options.add(["showtabline", "stal"], "Define when the tab bar is visible", - "string", config.defaults["showtabline"], + "string", true, { setter: function (value) { if (value === "never") tabs.tabStyle.enabled = true; else { prefs.safeSet("browser.tabs.autoHide", value === "multitab", - _("option.showtabline.safeSet")); + _("option.safeSet", "showtabline")); tabs.tabStyle.enabled = false; } @@ -1063,7 +1235,7 @@ var Tabs = Module("tabs", { } }); - if (config.hasTabbrowser) { + if (config.has("tabbrowser")) { let activateGroups = [ ["all", "Activate everything"], ["addons", ":addo[ns] command"], @@ -1090,7 +1262,7 @@ var Tabs = Module("tabs", { if (group[2]) prefs.safeSet("browser.tabs." + group[2], !(valueSet["all"] ^ valueSet[group[0]]), - _("option.activate.safeSet")); + _("option.safeSet", "activate")); return newValues; } }); @@ -1125,9 +1297,9 @@ var Tabs = Module("tabs", { } prefs.safeSet("browser.link.open_newwindow", open, - _("option.popups.safeSet")); + _("option.safeSet", "popups")); prefs.safeSet("browser.link.open_newwindow.restriction", restriction, - _("option.popups.safeSet")); + _("option.safeSet", "popups")); return values; }, values: {