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 // hide tabs initially to prevent flickering when 'stal' would hide them
24 if (config.hasTabbrowser)
25 config.tabStrip.collapsed = true;
27 this.tabStyle = styles.system.add("tab-strip-hiding", config.styleableChrome,
28 (config.tabStrip.id ? "#" + config.tabStrip.id : ".tabbrowser-strip") +
29 "{ visibility: collapse; }",
32 dactyl.commands["tabs.select"] = function (event) {
33 tabs.select(event.originalTarget.getAttribute("identifier"));
36 this.tabBinding = styles.system.add("tab-binding", "chrome://browser/content/browser.xul", String.replace(<><![CDATA[
37 xul|tab { -moz-binding: url(chrome://dactyl/content/bindings.xml#tab) !important; }
38 ]]></>, /tab-./g, function (m) util.OS.isMacOSX ? "tab-mac" : m),
41 this.timeout(function () {
42 for (let { linkedBrowser: { contentDocument } } in values(this.allTabs))
43 if (contentDocument.readyState === "complete")
44 dactyl.initDocument(contentDocument);
48 _alternates: Class.memoize(function () [config.tabbrowser.mCurrentTab, null]),
50 cleanup: function cleanup() {
51 for (let [i, tab] in Iterator(this.allTabs)) {
52 let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_);
53 for (let elem in values(["dactyl-tab-icon-number", "dactyl-tab-number"].map(node)))
55 elem.parentNode.parentNode.removeChild(elem.parentNode);
59 updateTabCount: function () {
60 for (let [i, tab] in Iterator(this.visibleTabs)) {
61 if (dactyl.has("Gecko2")) {
62 let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_);
63 if (!node("dactyl-tab-number")) {
64 let img = node("tab-icon-image");
67 let dom = util.xmlToDom(<xul xmlns:xul={XUL} xmlns:html={XHTML}
68 ><xul:hbox highlight="tab-number"><xul:label key="icon" align="center" highlight="TabIconNumber" class="dactyl-tab-icon-number"/></xul:hbox
69 ><xul:hbox highlight="tab-number"><html:div key="label" highlight="TabNumber" class="dactyl-tab-number"/></xul:hbox
70 ></xul>.*, document, nodes);
71 img.parentNode.appendChild(dom);
72 tab.__defineGetter__("dactylOrdinal", function () Number(nodes.icon.value));
73 tab.__defineSetter__("dactylOrdinal", function (i) nodes.icon.value = nodes.label.textContent = i);
77 tab.setAttribute("dactylOrdinal", i + 1);
78 tab.dactylOrdinal = i + 1;
80 statusline.updateTabCount(true);
83 _onTabSelect: function () {
84 // TODO: is all of that necessary?
87 statusline.updateTabCount(true);
88 this.updateSelectionHistory();
91 get allTabs() Array.slice(config.tabbrowser.tabContainer.childNodes),
94 * @property {Object} The previously accessed tab or null if no tab
95 * other than the current one has been accessed.
97 get alternate() this.allTabs.indexOf(this._alternates[1]) > -1 ? this._alternates[1] : null,
100 * @property {Iterator(Object)} A genenerator that returns all browsers
101 * in the current window.
104 let browsers = config.tabbrowser.browsers;
105 for (let i = 0; i < browsers.length; i++)
106 yield [i, browsers[i]];
110 * @property {number} The number of tabs in the current window.
112 get count() config.tabbrowser.mTabs.length,
115 * @property {Object} The local options store for the current tab.
118 let store = this.localStore;
119 if (!("options" in store))
121 return store.options;
124 get visibleTabs() config.tabbrowser.visibleTabs || this.allTabs.filter(function (tab) !tab.hidden),
127 * Returns the local state store for the tab at the specified *tabIndex*.
128 * If *tabIndex* is not specified then the current tab is used.
130 * @param {number} tabIndex
133 // FIXME: why not a tab arg? Why this and the property?
134 // : To the latter question, because this works for any tab, the
135 // property doesn't. And the property is so oft-used that it's
136 // convenient. To the former question, because I think this is mainly
137 // useful for autocommands, and they get index arguments. --Kris
138 getLocalStore: function (tabIndex) {
139 let tab = this.getTab(tabIndex);
140 if (!tab.dactylStore)
141 tab.dactylStore = {};
142 return tab.dactylStore;
146 * @property {Object} The local state store for the currently selected
149 get localStore() this.getLocalStore(),
152 * @property {Object[]} The array of closed tabs for the current
155 get closedTabs() services.json.decode(services.sessionStore.getClosedTabData(window)),
158 * Clones the specified *tab* and append it to the tab list.
160 * @param {Object} tab The tab to clone.
161 * @param {boolean} activate Whether to select the newly cloned tab.
163 cloneTab: function (tab, activate) {
164 let newTab = config.tabbrowser.addTab();
165 Tabs.copyTab(newTab, tab);
168 config.tabbrowser.mTabContainer.selectedItem = newTab;
174 * Detaches the specified *tab* and open it in a new window. If no tab is
175 * specified the currently selected tab is detached.
177 * @param {Object} tab The tab to detach.
179 detachTab: function (tab) {
181 tab = config.tabbrowser.mTabContainer.selectedItem;
183 services.windowWatcher
184 .openWindow(window, window.getBrowserURL(), null, "chrome,dialog=no,all", tab);
188 * Returns the index of the tab containing *content*.
190 * @param {Object} content Either a content window or a content
193 // FIXME: Only called once...necessary?
194 getContentIndex: function (content) {
195 for (let [i, browser] in this.browsers) {
196 if (browser.contentWindow == content || browser.contentDocument == content)
203 * If TabView exists, returns the Panorama window. If the Panorama
204 * is has not yet initialized, this function will not return until
209 getGroups: function () {
210 if ("_groups" in this)
213 if (window.TabView && TabView._initFrame)
214 TabView._initFrame();
216 let iframe = document.getElementById("tab-view");
217 this._groups = iframe ? iframe.contentWindow : null;
219 util.waitFor(function () this._groups.TabItems, this);
224 * Returns the tab at the specified *index* or the currently selected tab
225 * if *index* is not specified. This is a 0-based index.
227 * @param {number|Node} index The index of the tab required or the tab itself
230 getTab: function (index) {
231 if (index instanceof Node)
234 return config.tabbrowser.mTabs[index];
235 return config.tabbrowser.mCurrentTab;
239 * Returns the index of *tab* or the index of the currently selected tab if
240 * *tab* is not specified. This is a 0-based index.
242 * @param {<xul:tab/>} tab A tab from the current tab list.
243 * @param {boolean} visible Whether to consider only visible tabs.
246 index: function (tab, visible) {
247 let tabs = this[visible ? "visibleTabs" : "allTabs"];
248 return tabs.indexOf(tab || config.tabbrowser.mCurrentTab);
252 * @param spec can either be:
253 * - an absolute integer
254 * - "" for the current tab
255 * - "+1" for the next tab
256 * - "-3" for the tab, which is 3 positions left of the current
257 * - "$" for the last tab
259 indexFromSpec: function (spec, wrap) {
260 if (spec instanceof Node)
261 return this.allTabs.indexOf(spec);
263 let tabs = this.visibleTabs;
264 let position = this.index(null, true);
266 if (spec == null || spec === "")
269 if (typeof spec === "number")
271 else if (spec === "$")
272 position = tabs.length - 1;
273 else if (/^[+-]\d+$/.test(spec))
274 position += parseInt(spec, 10);
275 else if (/^\d+$/.test(spec))
276 position = parseInt(spec, 10);
280 if (position >= tabs.length)
281 position = wrap ? position % tabs.length : tabs.length - 1;
282 else if (position < 0)
283 position = wrap ? (position % tabs.length) + tabs.length : 0;
285 return this.allTabs.indexOf(tabs[position]);
289 * Removes all tabs from the tab list except the specified *tab*.
291 * @param {Object} tab The tab to keep.
293 keepOnly: function (tab) {
294 config.tabbrowser.removeAllTabsBut(tab);
298 * Lists all tabs matching *filter*.
300 * @param {string} filter A filter matching a substring of the tab's
301 * document title or URL.
303 list: function (filter) {
304 completion.listCompleter("buffer", filter);
308 * Moves a tab to a new position in the tab list.
310 * @param {Object} tab The tab to move.
311 * @param {string} spec See {@link Tabs.indexFromSpec}.
312 * @param {boolean} wrap Whether an out of bounds *spec* causes the
313 * destination position to wrap around the start/end of the tab list.
315 move: function (tab, spec, wrap) {
316 let index = tabs.indexFromSpec(spec, wrap);
317 config.tabbrowser.moveTabTo(tab, index);
321 * Removes the specified *tab* from the tab list.
323 * @param {Object} tab The tab to remove.
324 * @param {number} count How many tabs to remove.
325 * @param {boolean} focusLeftTab Focus the tab to the left of the removed tab.
327 remove: function (tab, count, focusLeftTab) {
329 let res = this.count > count;
331 let tabs = this.visibleTabs;
332 if (tabs.indexOf(tab) < 0)
334 let index = tabs.indexOf(tab);
336 let next = index + (focusLeftTab ? -count : count);
338 next = index + (focusLeftTab ? 1 : -1);
340 this._alternates[0] = tabs[next];
341 config.tabbrowser.mTabContainer.selectedItem = tabs[next];
345 tabs.slice(Math.max(0, index + 1 - count), index + 1).forEach(config.closure.removeTab);
347 tabs.slice(index, index + count).forEach(config.closure.removeTab);
352 * Reloads the specified tab.
354 * @param {Object} tab The tab to reload.
355 * @param {boolean} bypassCache Whether to bypass the cache when
358 reload: function (tab, bypassCache) {
361 const flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
362 config.tabbrowser.getBrowserForTab(tab).reloadWithFlags(flags);
365 config.tabbrowser.reloadTab(tab);
367 catch (e if !(e instanceof Error)) {}
373 * @param {boolean} bypassCache Whether to bypass the cache when
376 reloadAll: function (bypassCache) {
378 for (let i = 0; i < config.tabbrowser.mTabs.length; i++) {
380 this.reload(config.tabbrowser.mTabs[i], bypassCache);
383 // FIXME: can we do anything useful here without stopping the
384 // other tabs from reloading?
389 config.tabbrowser.reloadAllTabs();
393 * Selects the tab at the position specified by *spec*.
395 * @param {string} spec See {@link Tabs.indexFromSpec}
396 * @param {boolean} wrap Whether an out of bounds *spec* causes the
397 * selection position to wrap around the start/end of the tab list.
399 select: function (spec, wrap) {
400 let index = tabs.indexFromSpec(spec, wrap);
404 config.tabbrowser.mTabContainer.selectedIndex = index;
408 * Selects the alternate tab.
410 selectAlternateTab: function () {
411 dactyl.assert(tabs.alternate != null && tabs.getTab() != tabs.alternate,
412 _("buffer.noAlternate"));
413 tabs.select(tabs.alternate);
417 * Stops loading the specified tab.
419 * @param {Object} tab The tab to stop loading.
421 stop: function (tab) {
425 tab.linkedBrowser.stop();
429 * Stops loading all tabs.
431 stopAll: function () {
432 for (let [, browser] in this.browsers)
437 * Selects the tab containing the specified *buffer*.
439 * @param {string} buffer A string which matches the URL or title of a
440 * buffer, if it is null, the last used string is used again.
441 * @param {boolean} allowNonUnique Whether to select the first of
443 * @param {number} count If there are multiple matches select the
445 * @param {boolean} reverse Whether to search the buffer list in
450 switchTo: function (buffer, allowNonUnique, count, reverse) {
451 if (buffer != null) {
452 // store this command, so it can be repeated with "B"
453 this._lastBufferSwitchArgs = buffer;
454 this._lastBufferSwitchSpecial = allowNonUnique;
457 buffer = this._lastBufferSwitchArgs;
458 if (allowNonUnique == null) // XXX
459 allowNonUnique = this._lastBufferSwitchSpecial;
463 return tabs.selectAlternateTab();
465 reverse = Boolean(reverse);
466 count = Math.max(1, count || 1) * (1 + -2 * reverse);
468 let matches = buffer.match(/^(\d+):?/);
470 return tabs.select(this.allTabs[parseInt(matches[1], 10) - 1], false);
472 matches = array.nth(tabs.allTabs, function (t) (t.linkedBrowser.lastURI || {}).spec === buffer, 0);
474 return tabs.select(matches, false);
476 matches = completion.runCompleter("buffer", buffer).map(function (obj) obj.tab);
478 if (matches.length == 0)
479 dactyl.echoerr(_("buffer.noMatching", buffer));
480 else if (matches.length > 1 && !allowNonUnique)
481 dactyl.echoerr(_("buffer.multipleMatching", buffer));
483 let start = matches.indexOf(tabs.getTab());
484 if (start == -1 && reverse)
487 let index = (start + count) % matches.length;
489 index = matches.length + index;
490 tabs.select(matches[index], false);
494 // NOTE: when restarting a session FF selects the first tab and then the
495 // tab that was selected when the session was created. As a result the
496 // alternate after a restart is often incorrectly tab 1 when there
497 // shouldn't be one yet.
499 * Sets the current and alternate tabs, updating the tab selection
502 * @param {Array(Object)} tabs The current and alternate tab.
503 * @see tabs#alternate
505 updateSelectionHistory: function (tabs) {
507 if (this.getTab() == this._alternates[0]
508 || this.alternate && this.allTabs.indexOf(this._alternates[0]) == -1
509 || this.alternate && config.tabbrowser._removingTabs && config.tabbrowser._removingTabs.indexOf(this._alternates[0]) >= 0)
510 tabs = [this.getTab(), this.alternate];
512 this._alternates = tabs || [this.getTab(), this._alternates[0]];
515 copyTab: function (to, from) {
517 from = config.tabbrowser.mTabContainer.selectedItem;
519 let tabState = services.sessionStore.getTabState(from);
520 services.sessionStore.setTabState(to, tabState);
524 tabs.updateTabCount();
526 commands: function () {
527 commands.add(["bd[elete]", "bw[ipeout]", "bun[load]", "tabc[lose]"],
528 "Delete current buffer",
530 let special = args.bang;
531 let count = args.count;
532 let arg = args[0] || "";
536 let matches = arg.match(/^(\d+):?/);
539 config.removeTab(tabs.getTab(parseInt(matches[1], 10) - 1));
543 let str = arg.toLowerCase();
544 let browsers = config.tabbrowser.browsers;
546 for (let i = browsers.length - 1; i >= 0; i--) {
547 let host, title, uri = browsers[i].currentURI.spec;
548 if (browsers[i].currentURI.schemeIs("about")) {
550 title = "(Untitled)";
553 host = browsers[i].currentURI.host;
554 title = browsers[i].contentTitle;
557 [host, title, uri] = [host, title, uri].map(String.toLowerCase);
559 if (host.indexOf(str) >= 0 || uri == str ||
560 (special && (title.indexOf(str) >= 0 || uri.indexOf(str) >= 0))) {
561 config.removeTab(tabs.getTab(i));
568 dactyl.echomsg(_("buffer.fewer", removed, removed == 1 ? "" : "s"), 9);
570 dactyl.echoerr(_("buffer.noMatching", arg));
572 else // just remove the current tab
573 tabs.remove(tabs.getTab(), Math.max(count, 1), special);
578 completer: function (context) completion.buffer(context),
583 commands.add(["keepa[lt]"],
584 "Execute a command without changing the current alternate buffer",
586 let alternate = tabs.alternate;
589 commands.execute(args[0] || "", null, true);
592 tabs.updateSelectionHistory([tabs.getTab(), alternate]);
596 completer: function (context) completion.ex(context),
601 commands.add(["tab"],
602 "Execute a command and tell it to output in a new tab",
604 dactyl.withSavedValues(["forceNewTab"], function () {
605 this.forceNewTab = true;
606 commands.execute(args[0] || "", null, true);
610 completer: function (context) completion.ex(context),
615 commands.add(["tabd[o]", "bufd[o]"],
616 "Execute a command in each tab",
618 for (let tab in values(tabs.visibleTabs)) {
620 dactyl.execute(args[0], null, true);
624 completer: function (context) completion.ex(context),
629 commands.add(["tabl[ast]", "bl[ast]"],
630 "Switch to the last tab",
631 function () tabs.select("$", false),
634 // TODO: "Zero count" if 0 specified as arg
635 commands.add(["tabp[revious]", "tp[revious]", "tabN[ext]", "tN[ext]", "bp[revious]", "bN[ext]"],
636 "Switch to the previous tab or go [count] tabs back",
638 let count = args.count;
641 // count is ignored if an arg is specified, as per Vim
643 if (/^\d+$/.test(arg))
644 tabs.select("-" + arg, true);
646 dactyl.echoerr(_("error.trailing"));
649 tabs.select("-" + count, true);
651 tabs.select("-1", true);
657 // TODO: "Zero count" if 0 specified as arg
658 commands.add(["tabn[ext]", "tn[ext]", "bn[ext]"],
659 "Switch to the next or [count]th tab",
661 let count = args.count;
664 if (arg || count > 0) {
667 // count is ignored if an arg is specified, as per Vim
669 dactyl.assert(/^\d+$/.test(arg), _("error.trailing"));
675 if (index < tabs.count)
676 tabs.select(index, true);
681 tabs.select("+1", true);
687 commands.add(["tabr[ewind]", "tabfir[st]", "br[ewind]", "bf[irst]"],
688 "Switch to the first tab",
689 function () { tabs.select(0, false); },
692 if (config.hasTabbrowser) {
693 commands.add(["b[uffer]"],
694 "Switch to a buffer",
695 function (args) { tabs.switchTo(args[0], args.bang, args.count); }, {
699 completer: function (context) completion.buffer(context),
704 commands.add(["buffers", "files", "ls", "tabs"],
705 "Show a list of all buffers",
706 function (args) { tabs.list(args[0] || ""); }, {
711 commands.add(["quita[ll]", "qa[ll]"],
712 "Quit " + config.appName,
713 function (args) { dactyl.quit(false, args.bang); }, {
718 commands.add(["reloada[ll]"],
719 "Reload all tab pages",
720 function (args) { tabs.reloadAll(args.bang); }, {
725 commands.add(["stopa[ll]"],
726 "Stop loading all tab pages",
727 function () { tabs.stopAll(); },
730 // TODO: add count support
731 commands.add(["tabm[ove]"],
732 "Move the current tab after tab N",
736 // FIXME: tabmove! N should probably produce an error
737 dactyl.assert(!arg || /^([+-]?\d+)$/.test(arg),
738 _("error.trailing"));
740 // if not specified, move to after the last tab
741 tabs.move(config.tabbrowser.mCurrentTab, arg || "$", args.bang);
747 commands.add(["tabo[nly]"],
748 "Close all other tabs",
749 function () { tabs.keepOnly(tabs.getTab()); },
752 commands.add(["tabopen", "t[open]", "tabnew"],
753 "Open one or more URLs in a new tab",
755 dactyl.open(args[0] || "about:blank", { from: "tabopen", where: dactyl.NEW_TAB, background: args.bang });
758 completer: function (context) completion.url(context),
759 domains: function (args) commands.get("open").domains(args),
764 commands.add(["tabde[tach]"],
765 "Detach current tab to its own window",
766 function () { tabs.detachTab(null); },
769 commands.add(["tabdu[plicate]"],
770 "Duplicate current tab",
772 let tab = tabs.getTab();
774 let activate = args.bang ? true : false;
775 if (options.get("activate").has("tabopen"))
776 activate = !activate;
778 for (let i in util.range(0, Math.max(1, args.count)))
779 tabs.cloneTab(tab, activate);
786 // TODO: match window by title too?
787 // : accept the full :tabmove arg spec for the tab index arg?
788 // : better name or merge with :tabmove?
789 commands.add(["taba[ttach]"],
790 "Attach the current tab to another window",
792 dactyl.assert(args.length <= 2 && !args.some(function (i) !/^\d+$/.test(i)),
793 _("error.trailing"));
795 let [winIndex, tabIndex] = args.map(parseInt);
796 let win = dactyl.windows[winIndex - 1];
798 dactyl.assert(win, _("window.noIndex", winIndex));
799 dactyl.assert(win != window, _("window.cantAttachSame"));
801 let browser = win.getBrowser();
802 let dummy = browser.addTab("about:blank");
804 // XXX: the implementation of DnD in tabbrowser.xml suggests
805 // that we may not be guaranteed of having a docshell here
806 // without this reference?
809 let last = browser.mTabs.length - 1;
811 browser.moveTabTo(dummy, Math.constrain(tabIndex || last, 0, last));
812 browser.selectedTab = dummy; // required
813 browser.swapBrowsersAndCloseOther(dummy, config.tabbrowser.mCurrentTab);
816 completer: function (context, args) {
817 if (args.completeArg == 0) {
818 context.filters.push(function ({ item }) item != window);
819 completion.window(context);
825 if (dactyl.has("tabs_undo")) {
826 commands.add(["u[ndo]"],
827 "Undo closing of a tab",
832 args = args.count || 0;
834 let m = /^(\d+)(:|$)/.exec(args || '1');
836 window.undoCloseTab(Number(m[1]) - 1);
838 for (let [i, item] in Iterator(tabs.closedTabs))
839 if (item.state.entries[item.state.index - 1].url == args) {
840 window.undoCloseTab(i);
844 dactyl.echoerr(_("buffer.noClosed"));
848 completer: function (context) {
849 context.anchored = false;
850 context.compare = CompletionContext.Sort.unsorted;
851 context.filters = [CompletionContext.Filter.textDescription];
852 context.keys = { text: function ([i, { state: s }]) (i + 1) + ": " + s.entries[s.index - 1].url, description: "[1].title", icon: "[1].image" };
853 context.completions = Iterator(tabs.closedTabs);
860 commands.add(["undoa[ll]"],
861 "Undo closing of all closed tabs",
863 for (let i in Iterator(tabs.closedTabs))
864 window.undoCloseTab(0);
871 if (dactyl.has("session")) {
872 commands.add(["wqa[ll]", "wq", "xa[ll]"],
873 "Save the session and quit",
874 function () { dactyl.quit(true); },
878 events: function () {
879 let tabContainer = config.tabbrowser.mTabContainer;
880 function callback() {
881 tabs.timeout(function () { this.updateTabCount(); });
883 for (let event in values(["TabMove", "TabOpen", "TabClose"]))
884 events.listen(tabContainer, event, callback, false);
885 events.listen(tabContainer, "TabSelect", tabs.closure._onTabSelect, false);
887 mappings: function () {
888 mappings.add([modes.NORMAL], ["g0", "g^"],
889 "Go to the first tab",
890 function () { tabs.select(0); });
892 mappings.add([modes.NORMAL], ["g$"],
893 "Go to the last tab",
894 function () { tabs.select("$"); });
896 mappings.add([modes.NORMAL], ["gt"],
897 "Go to the next tab",
898 function ({ count }) {
900 tabs.select(count - 1, false);
902 tabs.select("+1", true);
906 mappings.add([modes.NORMAL], ["<C-n>", "<C-Tab>", "<C-PageDown>"],
907 "Go to the next tab",
908 function ({ count }) { tabs.select("+" + (count || 1), true); },
911 mappings.add([modes.NORMAL], ["gT", "<C-p>", "<C-S-Tab>", "<C-PageUp>"],
912 "Go to previous tab",
913 function ({ count }) { tabs.select("-" + (count || 1), true); },
916 if (config.hasTabbrowser) {
917 mappings.add([modes.NORMAL], ["b"],
918 "Open a prompt to switch buffers",
919 function ({ count }) {
921 tabs.switchTo(String(count));
923 CommandExMode().open("buffer! ");
927 mappings.add([modes.NORMAL], ["B"],
929 function () { tabs.list(false); });
931 mappings.add([modes.NORMAL], ["d"],
932 "Delete current buffer",
933 function ({ count }) { tabs.remove(tabs.getTab(), count, false); },
936 mappings.add([modes.NORMAL], ["D"],
937 "Delete current buffer, focus tab to the left",
938 function ({ count }) { tabs.remove(tabs.getTab(), count, true); },
941 mappings.add([modes.NORMAL], ["gb"],
942 "Repeat last :buffer[!] command",
943 function ({ count }) { tabs.switchTo(null, null, count, false); },
946 mappings.add([modes.NORMAL], ["gB"],
947 "Repeat last :buffer[!] command in reverse direction",
948 function ({ count }) { tabs.switchTo(null, null, count, true); },
951 // TODO: feature dependencies - implies "session"?
952 if (dactyl.has("tabs_undo")) {
953 mappings.add([modes.NORMAL], ["u"],
954 "Undo closing of a tab",
955 function ({ count }) { ex.undo({ "#": count }); },
959 mappings.add([modes.NORMAL], ["<C-^>", "<C-6>"],
960 "Select the alternate tab or the [count]th tab",
961 function ({ count }) {
963 tabs.switchTo(String(count), false);
965 tabs.selectAlternateTab();
970 options: function () {
971 options.add(["showtabline", "stal"],
972 "Define when the tab bar is visible",
973 "string", config.defaults["showtabline"],
975 setter: function (value) {
976 if (value === "never")
977 tabs.tabStyle.enabled = true;
979 prefs.safeSet("browser.tabs.autoHide", value === "multitab",
980 "See 'showtabline' option.");
981 tabs.tabStyle.enabled = false;
983 if (value !== "multitab" || !dactyl.has("Gecko2"))
984 config.tabStrip.collapsed = false;
985 if (config.tabbrowser.tabContainer._positionPinnedTabs)
986 config.tabbrowser.tabContainer._positionPinnedTabs();
990 "never": "Never show the tab bar",
991 "multitab": "Show the tab bar when there are multiple tabs",
992 "always": "Always show the tab bar"
996 if (config.hasTabbrowser) {
997 let activateGroups = [
998 ["all", "Activate everything"],
999 ["addons", ":addo[ns] command"],
1000 ["bookmarks", "Tabs loaded from bookmarks", "loadBookmarksInBackground"],
1001 ["diverted", "Links with targets set to new tabs", "loadDivertedInBackground"],
1002 ["downloads", ":downl[oads] command"],
1003 ["extoptions", ":exto[ptions] command"],
1004 ["help", ":h[elp] command"],
1005 ["homepage", "gH mapping"],
1006 ["links", "Middle- or Control-clicked links", "loadInBackground"],
1007 ["quickmark", "go and gn mappings"],
1008 ["tabopen", ":tabopen[!] command"],
1009 ["paste", "P and gP mappings"]
1011 options.add(["activate", "act"],
1012 "Define when newly created tabs are automatically activated",
1013 "stringlist", [g[0] for (g in values(activateGroups.slice(1))) if (!g[2] || !prefs.get("browser.tabs." + g[2]))].join(","),
1015 values: activateGroups,
1016 has: Option.has.toggleAll,
1017 setter: function (newValues) {
1018 let valueSet = set(newValues);
1019 for (let group in values(activateGroups))
1021 prefs.safeSet("browser.tabs." + group[2],
1022 !(valueSet["all"] ^ valueSet[group[0]]),
1023 "See the 'activate' option");
1028 options.add(["newtab"],
1029 "Define which commands should output in a new tab by default",
1033 "all": "All commands",
1034 "addons": ":addo[ns] command",
1035 "downloads": ":downl[oads] command",
1036 "extoptions": ":exto[ptions] command",
1037 "help": ":h[elp] command",
1038 "javascript": ":javascript! or :js! command",
1039 "prefs": ":pref[erences]! or :prefs! command"
1041 has: Option.has.toggleAll
1044 // TODO: Is this really applicable to Melodactyl?
1045 options.add(["popups", "pps"],
1046 "Where to show requested popup windows",
1047 "stringlist", "tab",
1049 setter: function (values) {
1050 let open = 1, restriction = 0;
1051 for (let [, opt] in Iterator(values)) {
1054 else if (opt == "window")
1056 else if (opt == "resized")
1060 prefs.safeSet("browser.link.open_newwindow", open,
1061 "See 'popups' option.");
1062 prefs.safeSet("browser.link.open_newwindow.restriction", restriction,
1063 "See 'popups' option.");
1067 "tab": "Open popups in a new tab",
1068 "window": "Open popups in a new window",
1069 "resized": "Open resized popups in a new window"
1076 // vim: set fdm=marker sw=4 ts=4 et: