1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-20 12:17:59 +01:00
Files
pentadactyl-pm/common/content/tabs.js
Kris Maglione 61002641a3 Context-specific completer options. Option refactoring.
New review
Owner: dougkearns
Hopefully the changeset hash will actually be linked this time. If
not, it's the tip of the testing branch, presumably r4161.
I've been meaning to do this for a while. It allows case matching,
sorting, and auto completion options to be assigned on a per context
basis, with fine grained control. It also adds builtin regex support
to options since it's used fairly extensively. There are definitely
other options that would benefit from the regexlist type, if I can
dig them up. The interface (and perhaps the doc wording) is the only
place I'm really ambivalent.

--HG--
branch : testing
2009-11-20 15:37:39 -05:00

1123 lines
41 KiB
JavaScript

// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
/** @scope modules */
// TODO: many methods do not work with Thunderbird correctly yet
/**
* @instance tabs
*/
const Tabs = Module("tabs", {
requires: ["config"],
init: function () {
this._alternates = [config.tabbrowser.mCurrentTab, null];
// used for the "gb" and "gB" mappings to remember the last :buffer[!] command
this._lastBufferSwitchArgs = "";
this._lastBufferSwitchSpecial = true;
// hide tabs initially to prevent flickering when 'stal' would hide them
// on startup
if (config.hasTabbrowser)
config.tabbrowser.mTabContainer.collapsed = true; // FIXME: see 'stal' comment
},
_updateTabCount: function () {
statusline.updateTabCount(true);
},
_onTabSelect: function () {
// TODO: is all of that necessary?
// I vote no. --Kris
modes.reset();
statusline.updateTabCount(true);
this.updateSelectionHistory();
if (options["focuscontent"])
setTimeout(function () { liberator.focusContent(true); }, 10); // just make sure, that no widget has focus
},
/**
* @property {Object} The previously accessed tab or null if no tab
* other than the current one has been accessed.
*/
get alternate() this._alternates[1],
/**
* @property {Iterator(Object)} A genenerator that returns all browsers
* in the current window.
*/
get browsers() {
let browsers = config.tabbrowser.browsers;
for (let i = 0; i < browsers.length; i++)
yield [i, browsers[i]];
},
/**
* @property {boolean} Whether the tab numbering XBL binding has been
* applied.
*/
get tabsBound() Boolean(styles.get(true, "tab-binding")),
set tabsBound(val) {
let fragment = liberator.has("MacUnix") ? "tab-mac" : "tab";
if (!val)
styles.removeSheet(true, "tab-binding");
else if (!this.tabsBound)
styles.addSheet(true, "tab-binding", "chrome://browser/content/browser.xul",
".tabbrowser-tab { -moz-binding: url(chrome://liberator/content/bindings.xml#" + fragment + ") !important; }" +
// FIXME: better solution for themes?
".tabbrowser-tab[busy] > .tab-icon > .tab-icon-image { list-style-image: url('chrome://global/skin/icons/loading_16.png') !important; }");
},
/**
* @property {number} The number of tabs in the current window.
*/
get count() config.tabbrowser.mTabs.length,
/**
* @property {Object} The local options store for the current tab.
*/
get options() {
let store = this.localStore;
if (!("options" in store))
store.options = {};
return store.options;
},
/**
* Returns the local state store for the tab at the specified
* <b>tabIndex</b>. If <b>tabIndex</b> is not specified then the
* current tab is used.
*
* @param {number} tabIndex
* @returns {Object}
*/
// FIXME: why not a tab arg? Why this and the property?
// : To the latter question, because this works for any tab, the
// 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) {
let tab = this.getTab(tabIndex);
if (!tab.liberatorStore)
tab.liberatorStore = {};
return tab.liberatorStore;
},
/**
* @property {Object} The local state store for the currently selected
* tab.
*/
get localStore() this.getLocalStore(),
/**
* @property {Object[]} The array of closed tabs for the current
* session.
*/
get closedTabs() services.get("json").decode(services.get("sessionStore").getClosedTabData(window)),
/**
* Returns the index of <b>tab</b> or the index of the currently
* selected tab if <b>tab</b> is not specified. This is a 0-based
* index.
*
* @param {Object} tab A tab from the current tab list.
* @returns {number}
*/
index: function (tab) {
if (tab)
return Array.indexOf(config.tabbrowser.mTabs, tab);
else
return config.tabbrowser.mTabContainer.selectedIndex;
},
// TODO: implement filter
/**
* Returns an array of all tabs in the tab list.
*
* @returns {Object[]}
*/
// FIXME: why not return the tab element?
// : unused? Remove me.
get: function () {
let buffers = [];
for (let [i, browser] in this.browsers) {
let title = browser.contentTitle || "(Untitled)";
let uri = browser.currentURI.spec;
let number = i + 1;
buffers.push([number, title, uri]);
}
return buffers;
},
/**
* Returns the index of the tab containing <b>content</b>.
*
* @param {Object} content Either a content window or a content
* document.
*/
// FIXME: Only called once...necessary?
getContentIndex: function (content) {
for (let [i, browser] in this.browsers) {
if (browser.contentWindow == content || browser.contentDocument == content)
return i;
}
return -1;
},
/**
* Returns the tab at the specified <b>index</b> or the currently
* selected tab if <b>index</b> is not specified. This is a 0-based
* index.
*
* @param {number} index The index of the tab required.
* @returns {Object}
*/
getTab: function (index) {
if (index != undefined)
return config.tabbrowser.mTabs[index];
else
return config.tabbrowser.mCurrentTab;
},
/**
* Lists all tabs matching <b>filter</b>.
*
* @param {string} filter A filter matching a substring of the tab's
* document title or URL.
*/
list: function (filter) {
completion.listCompleter("buffer", filter);
},
/**
* Moves a tab to a new position in the tab list.
*
* @param {Object} tab The tab to move.
* @param {string} spec See {@link Tabs.indexFromSpec}.
* @param {boolean} wrap Whether an out of bounds <b>spec</b> 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);
config.tabbrowser.moveTabTo(tab, index);
},
/**
* Removes the specified <b>tab</b> from the tab list.
*
* @param {Object} tab
* @param {number} count
* @param {boolean} focusLeftTab Focus the tab to the left of the removed tab.
* @param {number} quitOnLastTab Whether to quit if the tab being
* deleted is the only tab in the tab list:
* 1 - quit without saving session
* 2 - quit and save session
*/
// FIXME: what is quitOnLastTab {1,2} all about then, eh? --djk
remove: function (tab, count, focusLeftTab, quitOnLastTab) {
let removeOrBlankTab = {
Firefox: function (tab) {
if (config.tabbrowser.mTabs.length > 1)
config.tabbrowser.removeTab(tab);
else {
if (buffer.URL != "about:blank" ||
window.getWebNavigation().sessionHistory.count > 0) {
liberator.open("about:blank", liberator.NEW_BACKGROUND_TAB);
config.tabbrowser.removeTab(tab);
}
else
liberator.beep();
}
},
Thunderbird: function (tab) {
if (config.tabbrowser.mTabs.length > 1)
config.tabbrowser.removeTab(tab);
else
liberator.beep();
},
Songbird: function (tab) {
if (config.tabbrowser.mTabs.length > 1)
config.tabbrowser.removeTab(tab);
else {
if (buffer.URL != "about:blank" || window.getWebNavigation().sessionHistory.count > 0) {
liberator.open("about:blank", liberator.NEW_BACKGROUND_TAB);
config.tabbrowser.removeTab(tab);
}
else
liberator.beep();
}
}
}[config.hostApplication] || function () {};
if (typeof count != "number" || count < 1)
count = 1;
if (quitOnLastTab >= 1 && config.tabbrowser.mTabs.length <= count) {
if (liberator.windows.length > 1)
window.close();
else
liberator.quit(quitOnLastTab == 2);
return;
}
let index = this.index(tab);
if (focusLeftTab) {
let lastRemovedTab = 0;
for (let i = index; i > index - count && i >= 0; i--) {
removeOrBlankTab(this.getTab(i));
lastRemovedTab = i > 0 ? i : 1;
}
config.tabbrowser.mTabContainer.selectedIndex = lastRemovedTab - 1;
}
else {
let i = index + count - 1;
if (i >= this.count)
i = this.count - 1;
for (; i >= index; i--)
removeOrBlankTab(this.getTab(i));
config.tabbrowser.mTabContainer.selectedIndex = index;
}
},
/**
* Removes all tabs from the tab list except the specified <b>tab</b>.
*
* @param {Object} tab The tab to keep.
*/
keepOnly: function (tab) {
config.tabbrowser.removeAllTabsBut(tab);
},
/**
* Selects the tab at the position specified by <b>spec</b>.
*
* @param {string} spec See {@link Tabs.indexFromSpec}
* @param {boolean} wrap Whether an out of bounds <b>spec</b> causes
* the selection position to wrap around the start/end of the tab
* list.
*/
select: function (spec, wrap) {
let index = Tabs.indexFromSpec(spec, wrap);
// FIXME:
if (index == -1)
liberator.beep();
else
config.tabbrowser.mTabContainer.selectedIndex = index;
},
/**
* Reloads the specified tab.
*
* @param {Object} tab The tab to reload.
* @param {boolean} bypassCache Whether to bypass the cache when
* reloading.
*/
reload: function (tab, bypassCache) {
if (bypassCache) {
const flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
config.tabbrowser.getBrowserForTab(tab).reloadWithFlags(flags);
}
else
config.tabbrowser.reloadTab(tab);
},
/**
* Reloads all tabs.
*
* @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?
}
}
}
else
config.tabbrowser.reloadAllTabs();
},
/**
* Stops loading the specified tab.
*
* @param {Object} tab The tab to stop loading.
*/
stop: function (tab) {
if (config.stop)
config.stop(tab);
else
tab.linkedBrowser.stop();
},
/**
* Stops loading all tabs.
*/
stopAll: function () {
for (let [, browser] in this.browsers)
browser.stop();
},
/**
* Selects the tab containing the specified <b>buffer</b>.
*
* @param {string} buffer A string which matches the URL or title of a
* buffer, if it is null, the last used string is used again.
* @param {boolean} allowNonUnique Whether to select the first of
* multiple matches.
* @param {number} count If there are multiple matches select the
* count'th match.
* @param {boolean} reverse Whether to search the buffer list in
* reverse order.
*
*/
// FIXME: help!
switchTo: function (buffer, allowNonUnique, count, reverse) {
if (buffer == "")
return;
if (buffer != null) {
// store this command, so it can be repeated with "B"
this._lastBufferSwitchArgs = buffer;
this._lastBufferSwitchSpecial = allowNonUnique;
}
else {
buffer = this._lastBufferSwitchArgs;
if (allowNonUnique === undefined || allowNonUnique == null) // XXX
allowNonUnique = this._lastBufferSwitchSpecial;
}
if (buffer == "#") {
tabs.selectAlternateTab();
return;
}
if (!count || count < 1)
count = 1;
if (typeof reverse != "boolean")
reverse = false;
let matches = buffer.match(/^(\d+):?/);
if (matches) {
tabs.select(parseInt(matches[1], 10) - 1, false); // make it zero-based
return;
}
matches = [];
let lowerBuffer = buffer.toLowerCase();
let first = tabs.index() + (reverse ? 0 : 1);
let nbrowsers = config.tabbrowser.browsers.length;
for (let [i, ] in tabs.browsers) {
let index = (i + first) % nbrowsers;
let url = config.tabbrowser.getBrowserAtIndex(index).contentDocument.location.href;
let title = config.tabbrowser.getBrowserAtIndex(index).contentDocument.title.toLowerCase();
if (url == buffer) {
tabs.select(index, false);
return;
}
if (url.indexOf(buffer) >= 0 || title.indexOf(lowerBuffer) >= 0)
matches.push(index);
}
if (matches.length == 0)
liberator.echoerr("E94: No matching buffer for " + buffer);
else if (matches.length > 1 && !allowNonUnique)
liberator.echoerr("E93: More than one match for " + buffer);
else {
if (reverse) {
index = matches.length - count;
while (index < 0)
index += matches.length;
}
else
index = (count - 1) % matches.length;
tabs.select(matches[index], false);
}
},
/**
* Clones the specified <b>tab</b> and append it to the tab list.
*
* @param {Object} tab The tab to clone.
* @param {boolean} activate Whether to select the newly cloned tab.
*/
cloneTab: function (tab, activate) {
let newTab = config.tabbrowser.addTab();
Tabs.copyTab(newTab, tab);
if (activate)
config.tabbrowser.mTabContainer.selectedItem = newTab;
return newTab;
},
/**
* Detaches the specified <b>tab</b> and open it in a new window. If no
* tab is specified the currently selected tab is detached.
*
* @param {Object} tab The tab to detach.
*/
detachTab: function (tab) {
if (!tab)
tab = config.tabbrowser.mTabContainer.selectedItem;
services.get("windowWatcher")
.openWindow(window, window.getBrowserURL(), null, "chrome,dialog=no,all", tab);
},
/**
* Selects the alternate tab.
*/
selectAlternateTab: function () {
liberator.assert(tabs.alternate != null && tabs.getTab() != tabs.alternate,
"E23: No alternate page");
// NOTE: this currently relies on v.tabs.index() returning the
// currently selected tab index when passed null
let index = tabs.index(tabs.alternate);
// TODO: since a tab close is more like a bdelete for us we
// should probably reopen the closed tab when a 'deleted'
// alternate is selected
liberator.assert(index >= 0, "E86: Buffer does not exist"); // TODO: This should read "Buffer N does not exist"
tabs.select(index);
},
// NOTE: when restarting a session FF selects the first tab and then the
// tab that was selected when the session was created. As a result the
// alternate after a restart is often incorrectly tab 1 when there
// shouldn't be one yet.
/**
* Sets the current and alternate tabs, updating the tab selection
* history.
*
* @param {Array(Object)} tabs The current and alternate tab.
* @see tabs#alternate
*/
updateSelectionHistory: function (tabs) {
this._alternates = tabs || [this.getTab(), this._alternates[0]];
}
}, {
copyTab: function (to, from) {
if (!from)
from = config.tabbrowser.mTabContainer.selectedItem;
let tabState = services.get("sessionStore").getTabState(from);
services.get("sessionStore").setTabState(to, tabState);
},
/**
* @param spec can either be:
* - an absolute integer
* - "" for the current tab
* - "+1" for the next tab
* - "-3" for the tab, which is 3 positions left of the current
* - "$" for the last tab
*/
indexFromSpec: function (spec, wrap) {
let position = config.tabbrowser.mTabContainer.selectedIndex;
let length = config.tabbrowser.mTabs.length;
let last = length - 1;
if (spec === undefined || spec === "")
return position;
if (typeof spec === "number")
position = spec;
else if (spec === "$")
position = last;
else if (/^[+-]\d+$/.test(spec))
position += parseInt(spec, 10);
else if (/^\d+$/.test(spec))
position = parseInt(spec, 10);
else
return -1;
if (position > last)
position = wrap ? position % length : last;
else if (position < 0)
position = wrap ? (position % length) + length : 0;
return position;
}
}, {
commands: function () {
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.literalArg;
if (arg) {
let removed = 0;
let matches = arg.match(/^(\d+):?/);
if (matches) {
tabs.remove(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))) {
tabs.remove(tabs.getTab(i));
removed++;
}
}
}
if (removed > 0)
liberator.echomsg(removed + " fewer tab(s)", 9);
else
liberator.echoerr("E94: No matching tab for " + arg);
}
else // just remove the current tab
tabs.remove(tabs.getTab(), Math.max(count, 1), special, 0);
}, {
argCount: "?",
bang: true,
count: true,
completer: function (context) completion.buffer(context),
literal: 0
});
commands.add(["keepa[lt]"],
"Execute a command without changing the current alternate buffer",
function (args) {
let alternate = tabs.alternate;
try {
liberator.execute(args[0], null, true);
}
finally {
tabs.updateSelectionHistory([tabs.getTab(), alternate]);
}
}, {
argCount: "+",
completer: function (context) completion.ex(context),
literal: 0
});
// TODO: this should open in a new tab positioned directly after the current one, not at the end
commands.add(["tab"],
"Execute a command and tell it to output in a new tab",
function (args) {
liberator.forceNewTab = true;
liberator.execute(args.string, null, true);
liberator.forceNewTab = false;
}, {
argCount: "+",
completer: function (context) completion.ex(context),
literal: 0
});
commands.add(["tabd[o]", "bufd[o]"],
"Execute a command in each tab",
function (args) {
for (let i = 0; i < tabs.count; i++) {
tabs.select(i);
liberator.execute(args.string, null, true);
}
}, {
argCount: "1",
completer: function (context) completion.ex(context),
literal: 0
});
commands.add(["tabl[ast]", "bl[ast]"],
"Switch to the last tab",
function () tabs.select("$", false),
{ argCount: "0" });
// TODO: "Zero count" if 0 specified as arg
commands.add(["tabp[revious]", "tp[revious]", "tabN[ext]", "tN[ext]", "bp[revious]", "bN[ext]"],
"Switch to the previous tab or go [count] tabs back",
function (args) {
let count = args.count;
let arg = args[0];
// count is ignored if an arg is specified, as per Vim
if (arg) {
if (/^\d+$/.test(arg))
tabs.select("-" + arg, true);
else
liberator.echoerr("E488: Trailing characters");
}
else if (count > 0)
tabs.select("-" + count, true);
else
tabs.select("-1", true);
}, {
argCount: "?",
count: true
});
// TODO: "Zero count" if 0 specified as arg
commands.add(["tabn[ext]", "tn[ext]", "bn[ext]"],
"Switch to the next or [count]th tab",
function (args) {
let count = args.count;
let arg = args[0];
if (arg || count > 0) {
let index;
// count is ignored if an arg is specified, as per Vim
if (arg) {
liberator.assert(/^\d+$/.test(arg), "E488: Trailing characters");
index = arg - 1;
}
else
index = count - 1;
if (index < tabs.count)
tabs.select(index, true);
else
liberator.beep();
}
else
tabs.select("+1", true);
}, {
argCount: "?",
count: true
});
commands.add(["tabr[ewind]", "tabfir[st]", "br[ewind]", "bf[irst]"],
"Switch to the first tab",
function () { tabs.select(0, false); },
{ argCount: "0" });
if (config.hasTabbrowser) {
// TODO: "Zero count" if 0 specified as arg, multiple args and count ranges?
commands.add(["b[uffer]"],
"Switch to a buffer",
function (args) {
let special = args.bang;
let count = args.count;
let arg = args.literalArg;
// if a numeric arg is specified any count is ignored; if a
// count and non-numeric arg are both specified then E488
if (arg && count > 0) {
liberator.assert(/^\d+$/.test(arg), "E488: Trailing characters");
tabs.switchTo(arg, special);
}
else if (count > 0)
tabs.switchTo(count.toString(), special);
else
tabs.switchTo(arg, special);
}, {
argCount: "?",
bang: true,
count: true,
completer: function (context) completion.buffer(context),
literal: 0
});
commands.add(["buffers", "files", "ls", "tabs"],
"Show a list of all buffers",
function (args) { tabs.list(args.literalArg); }, {
argCount: "?",
literal: 0
});
commands.add(["quita[ll]", "qa[ll]"],
"Quit " + config.name,
function (args) { liberator.quit(false, args.bang); }, {
argCount: "0",
bang: true
});
commands.add(["reloada[ll]"],
"Reload all tab pages",
function (args) { tabs.reloadAll(args.bang); }, {
argCount: "0",
bang: true
});
commands.add(["stopa[ll]"],
"Stop loading all tab pages",
function () { tabs.stopAll(); },
{ argCount: "0" });
// TODO: add count support
commands.add(["tabm[ove]"],
"Move the current tab after tab N",
function (args) {
let arg = args[0];
// FIXME: tabmove! N should probably produce an error
liberator.assert(!arg || /^([+-]?\d+)$/.test(arg),
"E488: Trailing characters");
// if not specified, move to after the last tab
tabs.move(config.tabbrowser.mCurrentTab, arg || "$", args.bang);
}, {
argCount: "?",
bang: true
});
commands.add(["tabo[nly]"],
"Close all other tabs",
function () { tabs.keepOnly(config.tabbrowser.mCurrentTab); },
{ argCount: "0" });
commands.add(["tabopen", "t[open]", "tabnew"],
"Open one or more URLs in a new tab",
function (args) {
let special = args.bang;
args = args.string;
let where = special ? liberator.NEW_TAB : liberator.NEW_BACKGROUND_TAB;
if (args)
liberator.open(args, { from: "tabopen", where: where });
else
liberator.open("about:blank", { from: "tabopen", where: where });
}, {
bang: true,
completer: function (context) completion.url(context),
literal: 0,
privateData: true
});
commands.add(["tabde[tach]"],
"Detach current tab to its own window",
function () {
liberator.assert(tabs.count > 1, "Can't detach the last tab");
tabs.detachTab(null);
},
{ argCount: "0" });
commands.add(["tabdu[plicate]"],
"Duplicate current tab",
function (args) {
let tab = tabs.getTab();
let activate = args.bang ? true : false;
if (options.get("activate").has("tabopen", "all"))
activate = !activate;
for (let i in util.range(0, Math.max(1, args.count)))
tabs.cloneTab(tab, activate);
}, {
argCount: "0",
bang: true,
count: true
});
// TODO: match window by title too?
// : accept the full :tabmove arg spec for the tab index arg?
// : better name or merge with :tabmove?
commands.add(["taba[ttach]"],
"Attach the current tab to another window",
function (args) {
liberator.assert(args.length <= 2 && !args.some(function (i) !/^\d+$/.test(i)),
"E488: Trailing characters");
let [winIndex, tabIndex] = args.map(parseInt);
let win = liberator.windows[winIndex - 1];
liberator.assert(win, "Window " + winIndex + " does not exist");
liberator.assert(win != window, "Can't reattach to the same window");
let browser = win.getBrowser();
let dummy = 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;
browser.moveTabTo(dummy, util.Math.constrain(tabIndex || last, 0, last));
browser.selectedTab = dummy; // required
browser.swapBrowsersAndCloseOther(dummy, config.tabbrowser.mCurrentTab);
}, {
argCount: "+",
completer: function (context, args) {
if (args.completeArg == 0) {
context.filters.push(function ({ item: win }) win != window);
completion.window(context);
}
}
});
}
if (liberator.has("tabs_undo")) {
commands.add(["u[ndo]"],
"Undo closing of a tab",
function (args) {
if (args.length)
args = args[0];
else
args = args.count || 0;
let m = /^(\d+)(:|$)/.exec(args || '1');
if (m)
window.undoCloseTab(Number(m[1]) - 1);
else if (args) {
for (let [i, item] in Iterator(tabs.closedTabs))
if (item.state.entries[item.state.index - 1].url == args) {
window.undoCloseTab(i);
return;
}
liberator.echoerr("Exxx: No matching closed tab");
}
}, {
argCount: "?",
completer: function (context) {
context.anchored = false;
context.compare = CompletionContext.Sort.unsorted;
context.filters = [CompletionContext.Filter.textDescription];
context.keys = { text: function ([i, { state: s }]) (i + 1) + ": " + s.entries[s.index - 1].url, description: "[1].title", icon: "[1].image" };
context.completions = Iterator(tabs.closedTabs);
},
count: true,
literal: 0
});
commands.add(["undoa[ll]"],
"Undo closing of all closed tabs",
function (args) {
for (let i in Iterator(tabs.closedTabs))
window.undoCloseTab(0);
},
{ argCount: "0" });
}
if (liberator.has("session")) {
commands.add(["wqa[ll]", "wq", "xa[ll]"],
"Save the session and quit",
function () { liberator.quit(true); },
{ argCount: "0" });
}
},
completion: function () {
completion.addUrlCompleter("t",
"Open tabs",
completion.buffer);
},
events: function () {
let tabContainer = config.tabbrowser.mTabContainer;
["TabMove", "TabOpen", "TabClose"].forEach(function (event) {
events.addSessionListener(tabContainer, event, this.closure._updateTabCount, false);
}, this);
events.addSessionListener(tabContainer, "TabSelect", this.closure._onTabSelect, false);
},
mappings: function () {
mappings.add([modes.NORMAL], ["g0", "g^"],
"Go to the first tab",
function (count) { tabs.select(0); });
mappings.add([modes.NORMAL], ["g$"],
"Go to the last tab",
function (count) { tabs.select("$"); });
mappings.add([modes.NORMAL], ["gt"],
"Go to the next tab",
function (count) {
if (count != null)
tabs.select(count - 1, false);
else
tabs.select("+1", true);
},
{ count: true });
mappings.add([modes.NORMAL], ["<C-n>", "<C-Tab>", "<C-PageDown>"],
"Go to the next tab",
function (count) { tabs.select("+" + (count || 1), true); },
{ count: true });
mappings.add([modes.NORMAL], ["gT", "<C-p>", "<C-S-Tab>", "<C-PageUp>"],
"Go to previous tab",
function (count) { tabs.select("-" + (count || 1), true); },
{ count: true });
if (config.hasTabbrowser) {
mappings.add([modes.NORMAL], ["b"],
"Open a prompt to switch buffers",
function (count) {
if (count != null)
tabs.switchTo(String(count));
else
commandline.open(":", "buffer! ", modes.EX);
},
{ count: true });
mappings.add([modes.NORMAL], ["B"],
"Show buffer list",
function () { tabs.list(false); });
mappings.add([modes.NORMAL], ["d"],
"Delete current buffer",
function (count) { tabs.remove(tabs.getTab(), count, false, 0); },
{ count: true });
mappings.add([modes.NORMAL], ["D"],
"Delete current buffer, focus tab to the left",
function (count) { tabs.remove(tabs.getTab(), count, true, 0); },
{ count: true });
mappings.add([modes.NORMAL], ["gb"],
"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",
function (count) { tabs.switchTo(null, null, count, true); },
{ count: true });
// TODO: feature dependencies - implies "session"?
if (liberator.has("tabs_undo")) {
mappings.add([modes.NORMAL], ["u"],
"Undo closing of a tab",
function (count) { commands.get("undo").execute("", false, count); },
{ count: true });
}
mappings.add([modes.NORMAL], ["<C-^>", "<C-6>"],
"Select the alternate tab or the [count]th tab",
function (count) {
if (count < 1)
tabs.selectAlternateTab();
else
tabs.switchTo(count.toString(), false);
},
{ count: true });
}
},
options: function () {
options.add(["showtabline", "stal"],
"Control when to show the tab bar of opened web pages",
"number", config.defaults["showtabline"],
{
setter: function (value) {
// FIXME: we manipulate mTabContainer underneath mStrip so we
// don't have to fight against the host app's attempts to keep
// it open - hack! Adding a filter watch to mStrip is probably
// the cleanest solution.
let tabStrip = config.tabbrowser.mTabContainer;
if (value == 0)
tabStrip.collapsed = true;
else {
// FIXME: Why are we preferring our own created preference
// here? --djk
let pref = "browser.tabStrip.autoHide";
if (options.getPref(pref) == null) // Try for FF 3.0 & 3.1
pref = "browser.tabs.autoHide";
options.safeSetPref(pref, value == 1);
tabStrip.collapsed = false;
}
return value;
},
completer: function (context) [
["0", "Never show tab bar"],
["1", "Show tab bar only if more than one tab is open"],
["2", "Always show tab bar"]
]
});
if (config.hasTabbrowser) {
options.add(["activate", "act"],
"Define when tabs are automatically activated",
"stringlist", "addons,downloads,extoptions,help,homepage,quickmark,tabopen,paste",
{
completer: function (context) [
["addons", ":addo[ns] command"],
["downloads", ":downl[oads] command"],
["extoptions", ":exto[ptions] command"],
["help", ":h[elp] command"],
["homepage", "gH mapping"],
["quickmark", "go and gn mappings"],
["tabopen", ":tabopen[!] command"],
["paste", "P and gP mappings"]
]
});
options.add(["newtab"],
"Define which commands should output in a new tab by default",
"stringlist", "",
{
completer: function (context) [
["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"]
]
});
// TODO: Is this really applicable to Xulmus?
options.add(["popups", "pps"],
"Where to show requested popup windows",
"stringlist", "tab",
{
setter: function (values) {
let [open, restriction] = [1, 0];
for (let [, opt] in Iterator(values)) {
if (opt == "tab")
open = 3;
else if (opt == "window")
open = 2;
else if (opt == "resized")
restriction = 2;
}
options.safeSetPref("browser.link.open_newwindow", open, "See 'popups' option.");
options.safeSetPref("browser.link.open_newwindow.restriction", restriction, "See 'popups' option.");
return values;
},
completer: function (context) [
["tab", "Open popups in a new tab"],
["window", "Open popups in a new window"],
["resized", "Open resized popups in a new window"]
]
});
}
}
});
// vim: set fdm=marker sw=4 ts=4 et: