this._lastBufferSwitchArgs = "";
this._lastBufferSwitchSpecial = true;
+ this.xulTabs = document.getElementById("tabbrowser-tabs");
+
// hide tabs initially to prevent flickering when 'stal' would hide them
// on startup
if (config.hasTabbrowser)
for (let { linkedBrowser: { contentDocument } } in values(this.allTabs))
if (contentDocument.readyState === "complete")
dactyl.initDocument(contentDocument);
- });
+ }, 1000);
+
+ if (window.TabsInTitlebar)
+ window.TabsInTitlebar.allowedBy("dactyl", false);
+ },
+
+ signals: {
+ enter: function enter() {
+ if (window.TabsInTitlebar)
+ window.TabsInTitlebar.allowedBy("dactyl", true);
+ }
},
_alternates: Class.memoize(function () [config.tabbrowser.mCurrentTab, null]),
}
},
- updateTabCount: function () {
+ updateTabCount: function updateTabCount() {
for (let [i, tab] in Iterator(this.visibleTabs)) {
if (dactyl.has("Gecko2")) {
let node = function node(class_) document.getAnonymousElementByAttribute(tab, "class", class_);
statusline.updateTabCount(true);
},
- _onTabSelect: function () {
+ _onTabSelect: function _onTabSelect() {
// TODO: is all of that necessary?
// I vote no. --Kris
modes.reset();
get browsers() {
let browsers = config.tabbrowser.browsers;
for (let i = 0; i < browsers.length; i++)
- yield [i, browsers[i]];
+ if (browsers[i] !== undefined) // Bug in Google's Page Speed add-on.
+ yield [i, browsers[i]];
},
/**
// property doesn't. And the property is so oft-used that it's
// convenient. To the former question, because I think this is mainly
// useful for autocommands, and they get index arguments. --Kris
- getLocalStore: function (tabIndex) {
+ getLocalStore: function getLocalStore(tabIndex) {
let tab = this.getTab(tabIndex);
if (!tab.dactylStore)
tab.dactylStore = {};
get localStore() this.getLocalStore(),
/**
- * @property {Object[]} The array of closed tabs for the current
+ * @property {[Object]} The array of closed tabs for the current
* session.
*/
- get closedTabs() services.json.decode(services.sessionStore.getClosedTabData(window)),
+ get closedTabs() JSON.parse(services.sessionStore.getClosedTabData(window)),
/**
* Clones the specified *tab* and append it to the tab list.
* @param {Object} tab The tab to clone.
* @param {boolean} activate Whether to select the newly cloned tab.
*/
- cloneTab: function (tab, activate) {
- let newTab = config.tabbrowser.addTab();
+ cloneTab: function cloneTab(tab, activate) {
+ let newTab = config.tabbrowser.addTab("about:blank", { ownerTab: tab });
Tabs.copyTab(newTab, tab);
if (activate)
*
* @param {Object} tab The tab to detach.
*/
- detachTab: function (tab) {
+ detachTab: function detachTab(tab) {
if (!tab)
tab = config.tabbrowser.mTabContainer.selectedItem;
* document.
*/
// FIXME: Only called once...necessary?
- getContentIndex: function (content) {
+ getContentIndex: function getContentIndex(content) {
for (let [i, browser] in this.browsers) {
if (browser.contentWindow == content || browser.contentDocument == content)
return i;
*
* @returns {Window}
*/
- getGroups: function () {
+ getGroups: function getGroups() {
if ("_groups" in this)
return this._groups;
* if *index* is not specified. This is a 0-based index.
*
* @param {number|Node} index The index of the tab required or the tab itself
+ * @param {boolean} visible If true, consider only visible tabs rather than
+ * all tabs.
* @returns {Object}
*/
- getTab: function (index) {
+ getTab: function getTab(index, visible) {
if (index instanceof Node)
return index;
if (index != null)
- return config.tabbrowser.mTabs[index];
+ return this[visible ? "visibleTabs" : "allTabs"][index];
return config.tabbrowser.mCurrentTab;
},
* @param {boolean} visible Whether to consider only visible tabs.
* @returns {number}
*/
- index: function (tab, visible) {
+ index: function index(tab, visible) {
let tabs = this[visible ? "visibleTabs" : "allTabs"];
return tabs.indexOf(tab || config.tabbrowser.mCurrentTab);
},
* - "-3" for the tab, which is 3 positions left of the current
* - "$" for the last tab
*/
- indexFromSpec: function (spec, wrap) {
+ indexFromSpec: function indexFromSpec(spec, wrap, offset) {
if (spec instanceof Node)
return this.allTabs.indexOf(spec);
let tabs = this.visibleTabs;
let position = this.index(null, true);
- if (spec == null || spec === "")
+ if (spec == null)
+ return -1;
+
+ if (spec === "")
return position;
- if (typeof spec === "number")
- position = spec;
+ if (/^\d+$/.test(spec))
+ position = parseInt(spec, 10) + (offset || 0);
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;
*
* @param {Object} tab The tab to keep.
*/
- keepOnly: function (tab) {
+ keepOnly: function keepOnly(tab) {
config.tabbrowser.removeAllTabsBut(tab);
},
* @param {string} filter A filter matching a substring of the tab's
* document title or URL.
*/
- list: function (filter) {
+ list: function list(filter) {
completion.listCompleter("buffer", filter);
},
* @param {boolean} wrap Whether an out of bounds *spec* causes the
* destination position to wrap around the start/end of the tab list.
*/
- move: function (tab, spec, wrap) {
- let index = tabs.indexFromSpec(spec, wrap);
+ move: function move(tab, spec, wrap) {
+ let index = tabs.indexFromSpec(spec, wrap, -1);
config.tabbrowser.moveTabTo(tab, index);
},
* @param {number} count How many tabs to remove.
* @param {boolean} focusLeftTab Focus the tab to the left of the removed tab.
*/
- remove: function (tab, count, focusLeftTab) {
+ remove: function remove(tab, count, focusLeftTab) {
count = count || 1;
let res = this.count > count;
* @param {boolean} bypassCache Whether to bypass the cache when
* reloading.
*/
- reload: function (tab, bypassCache) {
+ reload: function reload(tab, bypassCache) {
try {
if (bypassCache) {
const flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
* @param {boolean} bypassCache Whether to bypass the cache when
* reloading.
*/
- reloadAll: function (bypassCache) {
- if (bypassCache) {
- for (let i = 0; i < config.tabbrowser.mTabs.length; i++) {
- try {
- this.reload(config.tabbrowser.mTabs[i], bypassCache);
- }
- catch (e) {
- // FIXME: can we do anything useful here without stopping the
- // other tabs from reloading?
- }
+ reloadAll: function reloadAll(bypassCache) {
+ this.visibleTabs.forEach(function (tab) {
+ try {
+ tabs.reload(tab, bypassCache);
}
- }
- else
- config.tabbrowser.reloadAllTabs();
+ catch (e) {
+ dactyl.reportError(e, true);
+ }
+ });
},
/**
* @param {boolean} wrap Whether an out of bounds *spec* causes the
* selection position to wrap around the start/end of the tab list.
*/
- select: function (spec, wrap) {
+ select: function select(spec, wrap) {
let index = tabs.indexFromSpec(spec, wrap);
if (index == -1)
dactyl.beep();
/**
* Selects the alternate tab.
*/
- selectAlternateTab: function () {
+ selectAlternateTab: function selectAlternateTab() {
dactyl.assert(tabs.alternate != null && tabs.getTab() != tabs.alternate,
_("buffer.noAlternate"));
tabs.select(tabs.alternate);
*
* @param {Object} tab The tab to stop loading.
*/
- stop: function (tab) {
+ stop: function stop(tab) {
if (config.stop)
config.stop(tab);
else
/**
* Stops loading all tabs.
*/
- stopAll: function () {
+ stopAll: function stopAll() {
for (let [, browser] in this.browsers)
browser.stop();
},
*
*/
// FIXME: help!
- switchTo: function (buffer, allowNonUnique, count, reverse) {
+ switchTo: function switchTo(buffer, allowNonUnique, count, reverse) {
if (buffer != null) {
// store this command, so it can be repeated with "B"
this._lastBufferSwitchArgs = buffer;
* @param {Array(Object)} tabs The current and alternate tab.
* @see tabs#alternate
*/
- updateSelectionHistory: function (tabs) {
+ updateSelectionHistory: function updateSelectionHistory(tabs) {
if (!tabs) {
if (this.getTab() == this._alternates[0]
|| this.alternate && this.allTabs.indexOf(this._alternates[0]) == -1
commands.add(["bd[elete]", "bw[ipeout]", "bun[load]", "tabc[lose]"],
"Delete current buffer",
function (args) {
- let special = args.bang;
- let count = args.count;
- let arg = args[0] || "";
-
- if (arg) {
- let removed = 0;
- let matches = arg.match(/^(\d+):?/);
-
- if (matches) {
- config.removeTab(tabs.getTab(parseInt(matches[1], 10) - 1));
- removed = 1;
- }
- else {
- let str = arg.toLowerCase();
- let browsers = config.tabbrowser.browsers;
-
- for (let i = browsers.length - 1; i >= 0; i--) {
- let host, title, uri = browsers[i].currentURI.spec;
- if (browsers[i].currentURI.schemeIs("about")) {
- host = "";
- title = "(Untitled)";
- }
- else {
- host = browsers[i].currentURI.host;
- title = browsers[i].contentTitle;
- }
-
- [host, title, uri] = [host, title, uri].map(String.toLowerCase);
-
- if (host.indexOf(str) >= 0 || uri == str ||
- (special && (title.indexOf(str) >= 0 || uri.indexOf(str) >= 0))) {
- config.removeTab(tabs.getTab(i));
- removed++;
- }
- }
- }
+ 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.fewer", removed, removed == 1 ? "" : "s"), 9);
+ dactyl.echomsg(_("buffer.fewerTab" + (removed == 1 ? "" : "s"), removed), 9);
else
dactyl.echoerr(_("buffer.noMatching", arg));
- }
- else // just remove the current tab
- tabs.remove(tabs.getTab(), Math.max(count, 1), special);
}, {
argCount: "?",
bang: true,
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.add(["pin[tab]"],
+ "Pin tab as an application tab",
+ function (args) {
+ for (let tab in matchTabs(args))
+ config.browser[!args.bang || !tab.pinned ? "pinTab" : "unpinTab"](tab);
+ },
+ {
+ argCount: "?",
+ bang: true,
+ count: true,
+ completer: function (context, args) {
+ if (!args.bang)
+ context.filters.push(function ({ item }) !item.tab.pinned);
+ completion.buffer(context);
+ }
+ });
+
+ commands.add(["unpin[tab]"],
+ "Unpin tab as an application tab",
+ function (args) {
+ for (let tab in matchTabs(args))
+ config.browser.unpinTab(tab);
+ },
+ {
+ argCount: "?",
+ count: true,
+ completer: function (context, args) {
+ context.filters.push(function ({ item }) item.tab.pinned);
+ completion.buffer(context);
+ }
+ });
+
commands.add(["keepa[lt]"],
"Execute a command without changing the current alternate buffer",
function (args) {
- let alternate = tabs.alternate;
-
try {
- commands.execute(args[0] || "", null, true);
+ dactyl.execute(args[0], null, true);
}
finally {
- tabs.updateSelectionHistory([tabs.getTab(), alternate]);
+ tabs.updateSelectionHistory([tabs.getTab(), tabs.alternate]);
}
}, {
- argCount: "+",
+ argCount: "1",
completer: function (context) completion.ex(context),
literal: 0,
subCommand: 0
function (args) {
dactyl.withSavedValues(["forceNewTab"], function () {
this.forceNewTab = true;
- commands.execute(args[0] || "", null, true);
+ dactyl.execute(args[0], null, true);
});
}, {
- argCount: "+",
+ argCount: "1",
completer: function (context) completion.ex(context),
literal: 0,
subCommand: 0
if (/^\d+$/.test(arg))
tabs.select("-" + arg, true);
else
- dactyl.echoerr(_("error.trailing"));
+ dactyl.echoerr(_("error.trailingCharacters"));
}
else if (count > 0)
tabs.select("-" + count, true);
// count is ignored if an arg is specified, as per Vim
if (arg) {
- dactyl.assert(/^\d+$/.test(arg), _("error.trailing"));
+ dactyl.assert(/^\d+$/.test(arg), _("error.trailingCharacters"));
index = arg - 1;
}
else
});
commands.add(["quita[ll]", "qa[ll]"],
- "Quit " + config.appName,
- function (args) { dactyl.quit(false, args.bang); }, {
- argCount: "0",
- bang: true
- });
+ "Quit this " + config.appName + " window",
+ function (args) { window.close(); },
+ { argCount: "0" });
commands.add(["reloada[ll]"],
"Reload all tab pages",
function () { tabs.stopAll(); },
{ argCount: "0" });
- // TODO: add count support
+ // TODO: add count and bang multimatch support - unify with :buffer nonsense
commands.add(["tabm[ove]"],
- "Move the current tab after tab N",
+ "Move the current tab to the position of tab N",
function (args) {
let arg = args[0];
- // FIXME: tabmove! N should probably produce an error
- dactyl.assert(!arg || /^([+-]?\d+)$/.test(arg),
- _("error.trailing"));
-
- // if not specified, move to after the last tab
- tabs.move(config.tabbrowser.mCurrentTab, arg || "$", args.bang);
+ 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];
+ }
+ tabs.move(tabs.getTab(), arg, args.bang);
}, {
- argCount: "?",
- bang: true
+ argCount: "1",
+ bang: true,
+ completer: function (context, args) completion.buffer(context, true),
+ literal: 0
});
commands.add(["tabo[nly]"],
commands.add(["taba[ttach]"],
"Attach the current tab to another window",
function (args) {
- dactyl.assert(args.length <= 2 && !args.some(function (i) !/^\d+$/.test(i)),
- _("error.trailing"));
+ dactyl.assert(args.length <= 2 && !args.some(function (i) !/^\d+(?:$|:)/.test(i)),
+ _("error.trailingCharacters"));
- let [winIndex, tabIndex] = args.map(parseInt);
+ let [winIndex, tabIndex] = args.map(function (arg) parseInt(arg));
let win = dactyl.windows[winIndex - 1];
dactyl.assert(win, _("window.noIndex", winIndex));
dactyl.assert(win != window, _("window.cantAttachSame"));
let browser = win.getBrowser();
+
+ if (args[1]) {
+ let tabList = browser.visibleTabs || browser.mTabs;
+ let target = dactyl.assert(tabList[tabIndex]);
+ tabIndex = Array.indexOf(browser.mTabs, target) - 1;
+ }
+
let dummy = browser.addTab("about:blank");
browser.stop();
// XXX: the implementation of DnD in tabbrowser.xml suggests
let last = browser.mTabs.length - 1;
- browser.moveTabTo(dummy, Math.constrain(tabIndex || last, 0, last));
+ if (args[1])
+ browser.moveTabTo(dummy, tabIndex);
browser.selectedTab = dummy; // required
browser.swapBrowsersAndCloseOther(dummy, config.tabbrowser.mCurrentTab);
}, {
argCount: "+",
+ literal: 1,
completer: function (context, args) {
- if (args.completeArg == 0) {
+ switch (args.completeArg) {
+ case 0:
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);
+ break;
}
}
});
tabs.tabStyle.enabled = true;
else {
prefs.safeSet("browser.tabs.autoHide", value === "multitab",
- "See 'showtabline' option.");
+ _("option.showtabline.safeSet"));
tabs.tabStyle.enabled = false;
}
+
if (value !== "multitab" || !dactyl.has("Gecko2"))
- config.tabStrip.collapsed = false;
+ if (tabs.xulTabs)
+ tabs.xulTabs.visible = value !== "never";
+ else
+ config.tabStrip.collapsed = false;
+
if (config.tabbrowser.tabContainer._positionPinnedTabs)
config.tabbrowser.tabContainer._positionPinnedTabs();
return value;
values: activateGroups,
has: Option.has.toggleAll,
setter: function (newValues) {
- let valueSet = set(newValues);
+ let valueSet = Set(newValues);
for (let group in values(activateGroups))
if (group[2])
prefs.safeSet("browser.tabs." + group[2],
!(valueSet["all"] ^ valueSet[group[0]]),
- "See the 'activate' option");
+ _("option.activate.safeSet"));
return newValues;
}
});
{
values: {
"all": "All commands",
- "addons": ":addo[ns] command",
- "downloads": ":downl[oads] command",
"extoptions": ":exto[ptions] command",
"help": ":h[elp] command",
- "javascript": ":javascript! or :js! command",
"prefs": ":pref[erences]! or :prefs! command"
},
has: Option.has.toggleAll
}
prefs.safeSet("browser.link.open_newwindow", open,
- "See 'popups' option.");
+ _("option.popups.safeSet"));
prefs.safeSet("browser.link.open_newwindow.restriction", restriction,
- "See 'popups' option.");
+ _("option.popups.safeSet"));
return values;
},
values: {