1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2011 by Kris Maglione <maglione.k at Gmail>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
11 // TODO: many methods do not work with Thunderbird correctly yet
16 var Tabs = Module("tabs", {
18 // used for the "gb" and "gB" mappings to remember the last :buffer[!] command
19 this._lastBufferSwitchArgs = "";
20 this._lastBufferSwitchSpecial = true;
22 this.xulTabs = document.getElementById("tabbrowser-tabs");
24 // hide tabs initially to prevent flickering when 'stal' would hide them
26 if (config.hasTabbrowser)
27 config.tabStrip.collapsed = true;
29 this.tabStyle = styles.system.add("tab-strip-hiding", config.styleableChrome,
30 (config.tabStrip.id ? "#" + config.tabStrip.id : ".tabbrowser-strip") +
31 "{ visibility: collapse; }",
34 dactyl.commands["tabs.select"] = function (event) {
35 tabs.select(event.originalTarget.getAttribute("identifier"));
38 this.tabBinding = styles.system.add("tab-binding", "chrome://browser/content/browser.xul", String.replace(<><![CDATA[
39 xul|tab { -moz-binding: url(chrome://dactyl/content/bindings.xml#tab) !important; }
40 ]]></>, /tab-./g, function (m) util.OS.isMacOSX ? "tab-mac" : m),
43 this.timeout(function () {
44 for (let { linkedBrowser: { contentDocument } } in values(this.allTabs))
45 if (contentDocument.readyState === "complete")
46 dactyl.initDocument(contentDocument);
49 if (window.TabsInTitlebar)
50 window.TabsInTitlebar.allowedBy("dactyl", false);
54 enter: function enter() {
55 if (window.TabsInTitlebar)
56 window.TabsInTitlebar.allowedBy("dactyl", true);
60 _alternates: Class.memoize(function () [config.tabbrowser.mCurrentTab, null]),
62 cleanup: function cleanup() {
63 for (let [i, tab] in Iterator(this.allTabs)) {
64 let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_);
65 for (let elem in values(["dactyl-tab-icon-number", "dactyl-tab-number"].map(node)))
67 elem.parentNode.parentNode.removeChild(elem.parentNode);
71 updateTabCount: function updateTabCount() {
72 for (let [i, tab] in Iterator(this.visibleTabs)) {
73 if (dactyl.has("Gecko2")) {
74 let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_);
75 if (!node("dactyl-tab-number")) {
76 let img = node("tab-icon-image");
79 let dom = util.xmlToDom(<xul xmlns:xul={XUL} xmlns:html={XHTML}
80 ><xul:hbox highlight="tab-number"><xul:label key="icon" align="center" highlight="TabIconNumber" class="dactyl-tab-icon-number"/></xul:hbox
81 ><xul:hbox highlight="tab-number"><html:div key="label" highlight="TabNumber" class="dactyl-tab-number"/></xul:hbox
82 ></xul>.*, document, nodes);
83 img.parentNode.appendChild(dom);
84 tab.__defineGetter__("dactylOrdinal", function () Number(nodes.icon.value));
85 tab.__defineSetter__("dactylOrdinal", function (i) nodes.icon.value = nodes.label.textContent = i);
89 tab.setAttribute("dactylOrdinal", i + 1);
90 tab.dactylOrdinal = i + 1;
92 statusline.updateTabCount(true);
95 _onTabSelect: function _onTabSelect() {
96 // TODO: is all of that necessary?
99 statusline.updateTabCount(true);
100 this.updateSelectionHistory();
103 get allTabs() Array.slice(config.tabbrowser.tabContainer.childNodes),
106 * @property {Object} The previously accessed tab or null if no tab
107 * other than the current one has been accessed.
109 get alternate() this.allTabs.indexOf(this._alternates[1]) > -1 ? this._alternates[1] : null,
112 * @property {Iterator(Object)} A genenerator that returns all browsers
113 * in the current window.
116 let browsers = config.tabbrowser.browsers;
117 for (let i = 0; i < browsers.length; i++)
118 if (browsers[i] !== undefined) // Bug in Google's Page Speed add-on.
119 yield [i, browsers[i]];
123 * @property {number} The number of tabs in the current window.
125 get count() config.tabbrowser.mTabs.length,
128 * @property {Object} The local options store for the current tab.
131 let store = this.localStore;
132 if (!("options" in store))
134 return store.options;
137 get visibleTabs() config.tabbrowser.visibleTabs || this.allTabs.filter(function (tab) !tab.hidden),
140 * Returns the local state store for the tab at the specified *tabIndex*.
141 * If *tabIndex* is not specified then the current tab is used.
143 * @param {number} tabIndex
146 // FIXME: why not a tab arg? Why this and the property?
147 // : To the latter question, because this works for any tab, the
148 // property doesn't. And the property is so oft-used that it's
149 // convenient. To the former question, because I think this is mainly
150 // useful for autocommands, and they get index arguments. --Kris
151 getLocalStore: function getLocalStore(tabIndex) {
152 let tab = this.getTab(tabIndex);
153 if (!tab.dactylStore)
154 tab.dactylStore = {};
155 return tab.dactylStore;
159 * @property {Object} The local state store for the currently selected
162 get localStore() this.getLocalStore(),
165 * @property {[Object]} The array of closed tabs for the current
168 get closedTabs() JSON.parse(services.sessionStore.getClosedTabData(window)),
171 * Clones the specified *tab* and append it to the tab list.
173 * @param {Object} tab The tab to clone.
174 * @param {boolean} activate Whether to select the newly cloned tab.
176 cloneTab: function cloneTab(tab, activate) {
177 let newTab = config.tabbrowser.addTab("about:blank", { ownerTab: tab });
178 Tabs.copyTab(newTab, tab);
181 config.tabbrowser.mTabContainer.selectedItem = newTab;
187 * Detaches the specified *tab* and open it in a new window. If no tab is
188 * specified the currently selected tab is detached.
190 * @param {Object} tab The tab to detach.
192 detachTab: function detachTab(tab) {
194 tab = config.tabbrowser.mTabContainer.selectedItem;
196 services.windowWatcher
197 .openWindow(window, window.getBrowserURL(), null, "chrome,dialog=no,all", tab);
201 * Returns the index of the tab containing *content*.
203 * @param {Object} content Either a content window or a content
206 // FIXME: Only called once...necessary?
207 getContentIndex: function getContentIndex(content) {
208 for (let [i, browser] in this.browsers) {
209 if (browser.contentWindow == content || browser.contentDocument == content)
216 * If TabView exists, returns the Panorama window. If the Panorama
217 * is has not yet initialized, this function will not return until
222 getGroups: function getGroups() {
223 if ("_groups" in this)
226 if (window.TabView && TabView._initFrame)
227 TabView._initFrame();
229 let iframe = document.getElementById("tab-view");
230 this._groups = iframe ? iframe.contentWindow : null;
232 util.waitFor(function () this._groups.TabItems, this);
237 * Returns the tab at the specified *index* or the currently selected tab
238 * if *index* is not specified. This is a 0-based index.
240 * @param {number|Node} index The index of the tab required or the tab itself
241 * @param {boolean} visible If true, consider only visible tabs rather than
245 getTab: function getTab(index, visible) {
246 if (index instanceof Node)
249 return this[visible ? "visibleTabs" : "allTabs"][index];
250 return config.tabbrowser.mCurrentTab;
254 * Returns the index of *tab* or the index of the currently selected tab if
255 * *tab* is not specified. This is a 0-based index.
257 * @param {<xul:tab/>} tab A tab from the current tab list.
258 * @param {boolean} visible Whether to consider only visible tabs.
261 index: function index(tab, visible) {
262 let tabs = this[visible ? "visibleTabs" : "allTabs"];
263 return tabs.indexOf(tab || config.tabbrowser.mCurrentTab);
267 * @param spec can either be:
268 * - an absolute integer
269 * - "" for the current tab
270 * - "+1" for the next tab
271 * - "-3" for the tab, which is 3 positions left of the current
272 * - "$" for the last tab
274 indexFromSpec: function indexFromSpec(spec, wrap, offset) {
275 if (spec instanceof Node)
276 return this.allTabs.indexOf(spec);
278 let tabs = this.visibleTabs;
279 let position = this.index(null, true);
287 if (/^\d+$/.test(spec))
288 position = parseInt(spec, 10) + (offset || 0);
289 else if (spec === "$")
290 position = tabs.length - 1;
291 else if (/^[+-]\d+$/.test(spec))
292 position += parseInt(spec, 10);
296 if (position >= tabs.length)
297 position = wrap ? position % tabs.length : tabs.length - 1;
298 else if (position < 0)
299 position = wrap ? (position % tabs.length) + tabs.length : 0;
301 return this.allTabs.indexOf(tabs[position]);
305 * Removes all tabs from the tab list except the specified *tab*.
307 * @param {Object} tab The tab to keep.
309 keepOnly: function keepOnly(tab) {
310 config.tabbrowser.removeAllTabsBut(tab);
314 * Lists all tabs matching *filter*.
316 * @param {string} filter A filter matching a substring of the tab's
317 * document title or URL.
319 list: function list(filter) {
320 completion.listCompleter("buffer", filter);
324 * Moves a tab to a new position in the tab list.
326 * @param {Object} tab The tab to move.
327 * @param {string} spec See {@link Tabs.indexFromSpec}.
328 * @param {boolean} wrap Whether an out of bounds *spec* causes the
329 * destination position to wrap around the start/end of the tab list.
331 move: function move(tab, spec, wrap) {
332 let index = tabs.indexFromSpec(spec, wrap, -1);
333 config.tabbrowser.moveTabTo(tab, index);
337 * Removes the specified *tab* from the tab list.
339 * @param {Object} tab The tab to remove.
340 * @param {number} count How many tabs to remove.
341 * @param {boolean} focusLeftTab Focus the tab to the left of the removed tab.
343 remove: function remove(tab, count, focusLeftTab) {
345 let res = this.count > count;
347 let tabs = this.visibleTabs;
348 if (tabs.indexOf(tab) < 0)
350 let index = tabs.indexOf(tab);
352 let next = index + (focusLeftTab ? -count : count);
354 next = index + (focusLeftTab ? 1 : -1);
356 this._alternates[0] = tabs[next];
357 config.tabbrowser.mTabContainer.selectedItem = tabs[next];
361 tabs.slice(Math.max(0, index + 1 - count), index + 1).forEach(config.closure.removeTab);
363 tabs.slice(index, index + count).forEach(config.closure.removeTab);
368 * Reloads the specified tab.
370 * @param {Object} tab The tab to reload.
371 * @param {boolean} bypassCache Whether to bypass the cache when
374 reload: function reload(tab, bypassCache) {
377 const flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
378 config.tabbrowser.getBrowserForTab(tab).reloadWithFlags(flags);
381 config.tabbrowser.reloadTab(tab);
383 catch (e if !(e instanceof Error)) {}
389 * @param {boolean} bypassCache Whether to bypass the cache when
392 reloadAll: function reloadAll(bypassCache) {
393 this.visibleTabs.forEach(function (tab) {
395 tabs.reload(tab, bypassCache);
398 dactyl.reportError(e, true);
404 * Selects the tab at the position specified by *spec*.
406 * @param {string} spec See {@link Tabs.indexFromSpec}
407 * @param {boolean} wrap Whether an out of bounds *spec* causes the
408 * selection position to wrap around the start/end of the tab list.
410 select: function select(spec, wrap) {
411 let index = tabs.indexFromSpec(spec, wrap);
415 config.tabbrowser.mTabContainer.selectedIndex = index;
419 * Selects the alternate tab.
421 selectAlternateTab: function selectAlternateTab() {
422 dactyl.assert(tabs.alternate != null && tabs.getTab() != tabs.alternate,
423 _("buffer.noAlternate"));
424 tabs.select(tabs.alternate);
428 * Stops loading the specified tab.
430 * @param {Object} tab The tab to stop loading.
432 stop: function stop(tab) {
436 tab.linkedBrowser.stop();
440 * Stops loading all tabs.
442 stopAll: function stopAll() {
443 for (let [, browser] in this.browsers)
448 * Selects the tab containing the specified *buffer*.
450 * @param {string} buffer A string which matches the URL or title of a
451 * buffer, if it is null, the last used string is used again.
452 * @param {boolean} allowNonUnique Whether to select the first of
454 * @param {number} count If there are multiple matches select the
456 * @param {boolean} reverse Whether to search the buffer list in
461 switchTo: function switchTo(buffer, allowNonUnique, count, reverse) {
462 if (buffer != null) {
463 // store this command, so it can be repeated with "B"
464 this._lastBufferSwitchArgs = buffer;
465 this._lastBufferSwitchSpecial = allowNonUnique;
468 buffer = this._lastBufferSwitchArgs;
469 if (allowNonUnique == null) // XXX
470 allowNonUnique = this._lastBufferSwitchSpecial;
474 return tabs.selectAlternateTab();
476 reverse = Boolean(reverse);
477 count = Math.max(1, count || 1) * (1 + -2 * reverse);
479 let matches = buffer.match(/^(\d+):?/);
481 return tabs.select(this.allTabs[parseInt(matches[1], 10) - 1], false);
483 matches = array.nth(tabs.allTabs, function (t) (t.linkedBrowser.lastURI || {}).spec === buffer, 0);
485 return tabs.select(matches, false);
487 matches = completion.runCompleter("buffer", buffer).map(function (obj) obj.tab);
489 if (matches.length == 0)
490 dactyl.echoerr(_("buffer.noMatching", buffer));
491 else if (matches.length > 1 && !allowNonUnique)
492 dactyl.echoerr(_("buffer.multipleMatching", buffer));
494 let start = matches.indexOf(tabs.getTab());
495 if (start == -1 && reverse)
498 let index = (start + count) % matches.length;
500 index = matches.length + index;
501 tabs.select(matches[index], false);
505 // NOTE: when restarting a session FF selects the first tab and then the
506 // tab that was selected when the session was created. As a result the
507 // alternate after a restart is often incorrectly tab 1 when there
508 // shouldn't be one yet.
510 * Sets the current and alternate tabs, updating the tab selection
513 * @param {Array(Object)} tabs The current and alternate tab.
514 * @see tabs#alternate
516 updateSelectionHistory: function updateSelectionHistory(tabs) {
518 if (this.getTab() == this._alternates[0]
519 || this.alternate && this.allTabs.indexOf(this._alternates[0]) == -1
520 || this.alternate && config.tabbrowser._removingTabs && config.tabbrowser._removingTabs.indexOf(this._alternates[0]) >= 0)
521 tabs = [this.getTab(), this.alternate];
523 this._alternates = tabs || [this.getTab(), this._alternates[0]];
526 copyTab: function (to, from) {
528 from = config.tabbrowser.mTabContainer.selectedItem;
530 let tabState = services.sessionStore.getTabState(from);
531 services.sessionStore.setTabState(to, tabState);
535 tabs.updateTabCount();
537 commands: function () {
538 commands.add(["bd[elete]", "bw[ipeout]", "bun[load]", "tabc[lose]"],
539 "Delete current buffer",
542 for (let tab in matchTabs(args, args.bang, true)) {
543 config.removeTab(tab);
549 dactyl.echomsg(_("buffer.fewerTab" + (removed == 1 ? "" : "s"), removed), 9);
551 dactyl.echoerr(_("buffer.noMatching", arg));
556 completer: function (context) completion.buffer(context),
561 function matchTabs(args, substr, all) {
562 let filter = args[0];
564 if (!filter && args.count == null)
567 yield dactyl.assert(tabs.getTab(args.count - 1));
569 let matches = /^(\d+)(?:$|:)/.exec(filter);
571 yield dactyl.assert(args.count == null &&
572 tabs.getTab(parseInt(matches[1], 10) - 1, !all));
574 let str = filter.toLowerCase();
575 for (let tab in values(tabs[all ? "allTabs" : "visibleTabs"])) {
577 let browser = tab.linkedBrowser;
578 let uri = browser.currentURI.spec;
579 if (browser.currentURI.schemeIs("about")) {
581 title = "(Untitled)";
584 host = browser.currentURI.host;
585 title = browser.contentTitle;
588 [host, title, uri] = [host, title, uri].map(String.toLowerCase);
590 if (host.indexOf(str) >= 0 || uri == str ||
591 (substr && (title.indexOf(str) >= 0 || uri.indexOf(str) >= 0)))
592 if (args.count == null || --args.count == 0)
599 commands.add(["pin[tab]"],
600 "Pin tab as an application tab",
602 for (let tab in matchTabs(args))
603 config.browser[!args.bang || !tab.pinned ? "pinTab" : "unpinTab"](tab);
609 completer: function (context, args) {
611 context.filters.push(function ({ item }) !item.tab.pinned);
612 completion.buffer(context);
616 commands.add(["unpin[tab]"],
617 "Unpin tab as an application tab",
619 for (let tab in matchTabs(args))
620 config.browser.unpinTab(tab);
625 completer: function (context, args) {
626 context.filters.push(function ({ item }) item.tab.pinned);
627 completion.buffer(context);
631 commands.add(["keepa[lt]"],
632 "Execute a command without changing the current alternate buffer",
635 dactyl.execute(args[0], null, true);
638 tabs.updateSelectionHistory([tabs.getTab(), tabs.alternate]);
642 completer: function (context) completion.ex(context),
647 commands.add(["tab"],
648 "Execute a command and tell it to output in a new tab",
650 dactyl.withSavedValues(["forceNewTab"], function () {
651 this.forceNewTab = true;
652 dactyl.execute(args[0], null, true);
656 completer: function (context) completion.ex(context),
661 commands.add(["tabd[o]", "bufd[o]"],
662 "Execute a command in each tab",
664 for (let tab in values(tabs.visibleTabs)) {
666 dactyl.execute(args[0], null, true);
670 completer: function (context) completion.ex(context),
675 commands.add(["tabl[ast]", "bl[ast]"],
676 "Switch to the last tab",
677 function () tabs.select("$", false),
680 // TODO: "Zero count" if 0 specified as arg
681 commands.add(["tabp[revious]", "tp[revious]", "tabN[ext]", "tN[ext]", "bp[revious]", "bN[ext]"],
682 "Switch to the previous tab or go [count] tabs back",
684 let count = args.count;
687 // count is ignored if an arg is specified, as per Vim
689 if (/^\d+$/.test(arg))
690 tabs.select("-" + arg, true);
692 dactyl.echoerr(_("error.trailingCharacters"));
695 tabs.select("-" + count, true);
697 tabs.select("-1", true);
703 // TODO: "Zero count" if 0 specified as arg
704 commands.add(["tabn[ext]", "tn[ext]", "bn[ext]"],
705 "Switch to the next or [count]th tab",
707 let count = args.count;
710 if (arg || count > 0) {
713 // count is ignored if an arg is specified, as per Vim
715 dactyl.assert(/^\d+$/.test(arg), _("error.trailingCharacters"));
721 if (index < tabs.count)
722 tabs.select(index, true);
727 tabs.select("+1", true);
733 commands.add(["tabr[ewind]", "tabfir[st]", "br[ewind]", "bf[irst]"],
734 "Switch to the first tab",
735 function () { tabs.select(0, false); },
738 if (config.hasTabbrowser) {
739 commands.add(["b[uffer]"],
740 "Switch to a buffer",
741 function (args) { tabs.switchTo(args[0], args.bang, args.count); }, {
745 completer: function (context) completion.buffer(context),
750 commands.add(["buffers", "files", "ls", "tabs"],
751 "Show a list of all buffers",
752 function (args) { tabs.list(args[0] || ""); }, {
757 commands.add(["quita[ll]", "qa[ll]"],
758 "Quit this " + config.appName + " window",
759 function (args) { window.close(); },
762 commands.add(["reloada[ll]"],
763 "Reload all tab pages",
764 function (args) { tabs.reloadAll(args.bang); }, {
769 commands.add(["stopa[ll]"],
770 "Stop loading all tab pages",
771 function () { tabs.stopAll(); },
774 // TODO: add count and bang multimatch support - unify with :buffer nonsense
775 commands.add(["tabm[ove]"],
776 "Move the current tab to the position of tab N",
780 if (tabs.indexFromSpec(arg) == -1) {
781 let tabs = [tab for (tab in matchTabs(args, true))];
782 dactyl.assert(tabs.length, _("error.invalidArgument", arg));
783 dactyl.assert(tabs.length == 1, _("buffer.multipleMatching", arg));
786 tabs.move(tabs.getTab(), arg, args.bang);
790 completer: function (context, args) completion.buffer(context, true),
794 commands.add(["tabo[nly]"],
795 "Close all other tabs",
796 function () { tabs.keepOnly(tabs.getTab()); },
799 commands.add(["tabopen", "t[open]", "tabnew"],
800 "Open one or more URLs in a new tab",
802 dactyl.open(args[0] || "about:blank", { from: "tabopen", where: dactyl.NEW_TAB, background: args.bang });
805 completer: function (context) completion.url(context),
806 domains: function (args) commands.get("open").domains(args),
811 commands.add(["tabde[tach]"],
812 "Detach current tab to its own window",
813 function () { tabs.detachTab(null); },
816 commands.add(["tabdu[plicate]"],
817 "Duplicate current tab",
819 let tab = tabs.getTab();
821 let activate = args.bang ? true : false;
822 if (options.get("activate").has("tabopen"))
823 activate = !activate;
825 for (let i in util.range(0, Math.max(1, args.count)))
826 tabs.cloneTab(tab, activate);
833 // TODO: match window by title too?
834 // : accept the full :tabmove arg spec for the tab index arg?
835 // : better name or merge with :tabmove?
836 commands.add(["taba[ttach]"],
837 "Attach the current tab to another window",
839 dactyl.assert(args.length <= 2 && !args.some(function (i) !/^\d+(?:$|:)/.test(i)),
840 _("error.trailingCharacters"));
842 let [winIndex, tabIndex] = args.map(function (arg) parseInt(arg));
843 let win = dactyl.windows[winIndex - 1];
845 dactyl.assert(win, _("window.noIndex", winIndex));
846 dactyl.assert(win != window, _("window.cantAttachSame"));
848 let browser = win.getBrowser();
851 let tabList = browser.visibleTabs || browser.mTabs;
852 let target = dactyl.assert(tabList[tabIndex]);
853 tabIndex = Array.indexOf(browser.mTabs, target) - 1;
856 let dummy = browser.addTab("about:blank");
858 // XXX: the implementation of DnD in tabbrowser.xml suggests
859 // that we may not be guaranteed of having a docshell here
860 // without this reference?
863 let last = browser.mTabs.length - 1;
866 browser.moveTabTo(dummy, tabIndex);
867 browser.selectedTab = dummy; // required
868 browser.swapBrowsersAndCloseOther(dummy, config.tabbrowser.mCurrentTab);
872 completer: function (context, args) {
873 switch (args.completeArg) {
875 context.filters.push(function ({ item }) item != window);
876 completion.window(context);
879 let win = dactyl.windows[Number(args[0]) - 1];
880 if (!win || !win.dactyl)
881 context.message = _("Error", _("window.noIndex", winIndex));
883 win.dactyl.modules.commands.get("tabmove").completer(context);
890 if (dactyl.has("tabs_undo")) {
891 commands.add(["u[ndo]"],
892 "Undo closing of a tab",
897 args = args.count || 0;
899 let m = /^(\d+)(:|$)/.exec(args || '1');
901 window.undoCloseTab(Number(m[1]) - 1);
903 for (let [i, item] in Iterator(tabs.closedTabs))
904 if (item.state.entries[item.state.index - 1].url == args) {
905 window.undoCloseTab(i);
909 dactyl.echoerr(_("buffer.noClosed"));
913 completer: function (context) {
914 context.anchored = false;
915 context.compare = CompletionContext.Sort.unsorted;
916 context.filters = [CompletionContext.Filter.textDescription];
917 context.keys = { text: function ([i, { state: s }]) (i + 1) + ": " + s.entries[s.index - 1].url, description: "[1].title", icon: "[1].image" };
918 context.completions = Iterator(tabs.closedTabs);
925 commands.add(["undoa[ll]"],
926 "Undo closing of all closed tabs",
928 for (let i in Iterator(tabs.closedTabs))
929 window.undoCloseTab(0);
936 if (dactyl.has("session")) {
937 commands.add(["wqa[ll]", "wq", "xa[ll]"],
938 "Save the session and quit",
939 function () { dactyl.quit(true); },
943 events: function () {
944 let tabContainer = config.tabbrowser.mTabContainer;
945 function callback() {
946 tabs.timeout(function () { this.updateTabCount(); });
948 for (let event in values(["TabMove", "TabOpen", "TabClose"]))
949 events.listen(tabContainer, event, callback, false);
950 events.listen(tabContainer, "TabSelect", tabs.closure._onTabSelect, false);
952 mappings: function () {
953 mappings.add([modes.NORMAL], ["g0", "g^"],
954 "Go to the first tab",
955 function () { tabs.select(0); });
957 mappings.add([modes.NORMAL], ["g$"],
958 "Go to the last tab",
959 function () { tabs.select("$"); });
961 mappings.add([modes.NORMAL], ["gt"],
962 "Go to the next tab",
963 function ({ count }) {
965 tabs.select(count - 1, false);
967 tabs.select("+1", true);
971 mappings.add([modes.NORMAL], ["<C-n>", "<C-Tab>", "<C-PageDown>"],
972 "Go to the next tab",
973 function ({ count }) { tabs.select("+" + (count || 1), true); },
976 mappings.add([modes.NORMAL], ["gT", "<C-p>", "<C-S-Tab>", "<C-PageUp>"],
977 "Go to previous tab",
978 function ({ count }) { tabs.select("-" + (count || 1), true); },
981 if (config.hasTabbrowser) {
982 mappings.add([modes.NORMAL], ["b"],
983 "Open a prompt to switch buffers",
984 function ({ count }) {
986 tabs.switchTo(String(count));
988 CommandExMode().open("buffer! ");
992 mappings.add([modes.NORMAL], ["B"],
994 function () { tabs.list(false); });
996 mappings.add([modes.NORMAL], ["d"],
997 "Delete current buffer",
998 function ({ count }) { tabs.remove(tabs.getTab(), count, false); },
1001 mappings.add([modes.NORMAL], ["D"],
1002 "Delete current buffer, focus tab to the left",
1003 function ({ count }) { tabs.remove(tabs.getTab(), count, true); },
1006 mappings.add([modes.NORMAL], ["gb"],
1007 "Repeat last :buffer[!] command",
1008 function ({ count }) { tabs.switchTo(null, null, count, false); },
1011 mappings.add([modes.NORMAL], ["gB"],
1012 "Repeat last :buffer[!] command in reverse direction",
1013 function ({ count }) { tabs.switchTo(null, null, count, true); },
1016 // TODO: feature dependencies - implies "session"?
1017 if (dactyl.has("tabs_undo")) {
1018 mappings.add([modes.NORMAL], ["u"],
1019 "Undo closing of a tab",
1020 function ({ count }) { ex.undo({ "#": count }); },
1024 mappings.add([modes.NORMAL], ["<C-^>", "<C-6>"],
1025 "Select the alternate tab or the [count]th tab",
1026 function ({ count }) {
1028 tabs.switchTo(String(count), false);
1030 tabs.selectAlternateTab();
1035 options: function () {
1036 options.add(["showtabline", "stal"],
1037 "Define when the tab bar is visible",
1038 "string", config.defaults["showtabline"],
1040 setter: function (value) {
1041 if (value === "never")
1042 tabs.tabStyle.enabled = true;
1044 prefs.safeSet("browser.tabs.autoHide", value === "multitab",
1045 _("option.showtabline.safeSet"));
1046 tabs.tabStyle.enabled = false;
1049 if (value !== "multitab" || !dactyl.has("Gecko2"))
1051 tabs.xulTabs.visible = value !== "never";
1053 config.tabStrip.collapsed = false;
1055 if (config.tabbrowser.tabContainer._positionPinnedTabs)
1056 config.tabbrowser.tabContainer._positionPinnedTabs();
1060 "never": "Never show the tab bar",
1061 "multitab": "Show the tab bar when there are multiple tabs",
1062 "always": "Always show the tab bar"
1066 if (config.hasTabbrowser) {
1067 let activateGroups = [
1068 ["all", "Activate everything"],
1069 ["addons", ":addo[ns] command"],
1070 ["bookmarks", "Tabs loaded from bookmarks", "loadBookmarksInBackground"],
1071 ["diverted", "Links with targets set to new tabs", "loadDivertedInBackground"],
1072 ["downloads", ":downl[oads] command"],
1073 ["extoptions", ":exto[ptions] command"],
1074 ["help", ":h[elp] command"],
1075 ["homepage", "gH mapping"],
1076 ["links", "Middle- or Control-clicked links", "loadInBackground"],
1077 ["quickmark", "go and gn mappings"],
1078 ["tabopen", ":tabopen[!] command"],
1079 ["paste", "P and gP mappings"]
1081 options.add(["activate", "act"],
1082 "Define when newly created tabs are automatically activated",
1083 "stringlist", [g[0] for (g in values(activateGroups.slice(1))) if (!g[2] || !prefs.get("browser.tabs." + g[2]))].join(","),
1085 values: activateGroups,
1086 has: Option.has.toggleAll,
1087 setter: function (newValues) {
1088 let valueSet = Set(newValues);
1089 for (let group in values(activateGroups))
1091 prefs.safeSet("browser.tabs." + group[2],
1092 !(valueSet["all"] ^ valueSet[group[0]]),
1093 _("option.activate.safeSet"));
1098 options.add(["newtab"],
1099 "Define which commands should output in a new tab by default",
1103 "all": "All commands",
1104 "extoptions": ":exto[ptions] command",
1105 "help": ":h[elp] command",
1106 "prefs": ":pref[erences]! or :prefs! command"
1108 has: Option.has.toggleAll
1111 // TODO: Is this really applicable to Melodactyl?
1112 options.add(["popups", "pps"],
1113 "Where to show requested popup windows",
1114 "stringlist", "tab",
1116 setter: function (values) {
1117 let open = 1, restriction = 0;
1118 for (let [, opt] in Iterator(values)) {
1121 else if (opt == "window")
1123 else if (opt == "resized")
1127 prefs.safeSet("browser.link.open_newwindow", open,
1128 _("option.popups.safeSet"));
1129 prefs.safeSet("browser.link.open_newwindow.restriction", restriction,
1130 _("option.popups.safeSet"));
1134 "tab": "Open popups in a new tab",
1135 "window": "Open popups in a new window",
1136 "resized": "Open resized popups in a new window"
1143 // vim: set fdm=marker sw=4 ts=4 et: