teramako
MPL 1.1/GPL 2.0/LGPL 2.1
For adding Panorama supports.
This plugin makes the default feature to not switch to the other group suddenly.
And add some mappings and commands for Parnorama.
Use at your OWN RISK.
This pluin overwrite many mappings, some commands, and some completions.
New Mappings and Commands
-
g@
countg@
Switch to AppTab.
If the current tab is already AppTab, switch to the next AppTab.
-
]]>
count<C-S-n>
Switch to next group.
Caution: cannot switch to empty group.
-
]]>
count<C-S-p>
Switch to previous group.
Main Command
-
:panorama :tabview :tabcandy
:panorama SubCommand
:tabview SubCommand
:tabcandy SubCommand
See the following SubCommands.
SubCommands
-
mkgroup mkg
mkgroup! GroupName
Create new tab group named GroupName. And then, switch to the group.
If specified !, move the current tab to the group.
-
stashtogroup stash
stashtogroup! GroupName
Stash the current tab to GroupName.
Caution: connnot stash AppTab (pinned tab)
-
switchgroup swg g
switchgroup GroupName
swg GroupName
g GroupName
countswitchgroup
countswg
countg
Switch group to GroupName
-
rmgroup rmg
rmggroup! GroupName
remove group. The current group is used if ommited GroupName
-
pulltab pull
pulltab buffer
pull a tab from the other group
-
pintab pin
pintab
pin the current tab, if already pinned, unpin.
-
title
title title GroupName
update GroupName's title to title.
if omitted GroupName, update the current group.
>;
/**
* @method selectVisible {{{
* 現在表示されているタブでの絶対/相対位置によるタブ選択を行う
* (tabs.select() だと全タブが対象となる)
* @param {String} spec
* @param {Boolean} wrap
*/
function selectVisible (spec, wrap) {
if (spec === void(0) || spec === "")
return;
let tabs = gBrowser.visibleTabs;
let index;
if (typeof spec === "number" || /^\d+$/.test(spec)) {
index = parseInt(spec, 10);
} else if (spec === "$") {
index = tabs.length - 1;
} else if (/^[+-]\d+$/.test(spec)) {
index = tabs.indexOf(gBrowser.mCurrentTab) + parseInt(spec, 10);
} else {
return;
}
let length = tabs.length;
if (index > length - 1)
index = wrap ? index % length : length - 1;
else if (index < 0)
index = wrap ? index % length + length : 0;
gBrowser.mTabContainer.selectedItem = tabs[index];
} // }}}
/**
* @method switchTo {{{
* tabs.switchTo 相当の関数
* @param {String} buffer
* @param {Boolean} allowNonUnique
* @param {Number} count
* @param {Boolean} reverse
*/
function switchTo (buffer, allowNonUnique, count, reverse) {
if (buffer == "")
return null;
if (buffer != null) {
tabs._lastBufferSwitchArgs = buffer;
tabs._lastBufferSwitchSpecial = allowNonUnique;
} else {
buffer = this._lastBufferSwitchArgs;
if (allowNonUnique === void(0) || allowNonUnique === null)
allowNonUnique = tabs._lastBufferSwitchSpecial;
}
if (buffer == "#") {
tabs.selectAlternateTab();
return;
}
let tab = searchTab(buffer);
if (tab) {
tabs.select(tab._tPos, false);
return;
}
if (!count || count < 1)
count = 1;
reverse = !!reverse;
let m = [];
let lowerBuffer = buffer.toLowerCase();
let first = tabs.index() + (reverse ? 0 : 1);
let length = config.tabbrowser.browsers.length;
for (let [i, ] in tabs.browsers) {
let index = (i + first) % length;
let browser = config.tabbrowser.browsers[index];
let url, title;
if ("__SS_restoreState" in browser) {
let entry = browser.__SS_data.entries.slice(-1)[0];
url = entry.url;
title = entry.title || url;
} else {
url = browser.contentDocument.location.href;
title = browser.contentDocument.title;
}
title = title.toLowerCase();
if (url == buffer) {
tabs.select(index, false);
return;
}
if (url.indexOf(buffer) >= 0 || title.indexOf(lowerBuffer) >= 0)
m.push(index);
}
if (m.length == 0)
liberator.echoerr("E94: No matching buffer for " + buffer);
else if (m.length > 1 && !allowNonUnique)
liberator.echoerr("E93: More than one match for " + buffer);
else {
if (reverse) {
index = m.length - count;
while (index < 0)
index + m.length;
} else {
index = (count - 1) % m.length;
}
tabs.select(m[index], false);
}
} // }}}
/**
* @method searchTab {{{
* @param {String} buffer
* - "{Number}:"
* - "{GroupName} {Number}:"
* @return {Element|null}
*/
function searchTab (buffer) {
if (buffer == "#") {
if (tabs.alternate != null && tabs.getTab() != tabs.alternate)
return tabs.alternate;
return null;
}
let m = buffer.match(/^(\d+):?/);
if (m)
return tabs.getTab(parseInt(m[1], 10) -1);
m = buffer.match(/^(.+?)\s+(\d+):?/);
if (m) {
let [, groupName, tabNum] = m;
tabNum = parseInt(tabNum, 10);
let group = getGroupByName(groupName)[0];
if (!group)
return null;
let tabItem = group.getChild(tabNum -1);
if (!tabItem)
return null;
return tabItem.tab;
}
return null;
} // }}}
let TV = window.TabView;
/**
* @type {Window} TabView._window {{{
*/
this.__defineGetter__("tabView", function() {
if (TV && TV._window && TV._window.GroupItems) {
delete this.tabView;
this.tabView = TV._window;
return TV._window;
} else {
let wating = true;
TV._initFrame(function(){ wating = false; })
while (wating)
liberator.threadYield(false, true);
return this.tabView;
}
}); // }}}
/**
* @type {Array} Array of AppTabs
*/
this.__defineGetter__("appTabs", function() gBrowser.visibleTabs.filter(function(t) t.pinned));
/**
* @method createGroup {{{
* @param {String} name GroupName
* @return {GroupItem}
*/
function createGroup (name) {
let pageBounds = tabView.Items.getPageBounds();
pageBounds.inset(20, 20);
let box = new tabView.Rect(pageBounds);
box.width = 50;
box.height = 50;
let group = new tabView.GroupItem([], { bounds: box, title: name });
if (name && group.$title.hasClass("defaultName"))
group.$title.removeClass("defaultName");
return group;
} // }}}
/**
* @param {String|Number} name GroupName or GroupId
* @return {GroupItem[]}
*/
function getGroupByName (name) {
if (typeof name === "number")
return tabView.GroupItems.groupItems.filter(function(g) g.id == name);
return tabView.GroupItems.groupItems.filter(function(g) g.getTitle() == name);
}
/**
* @param {Element} tab
* @param {GroupItem|Number} group GroupItem object or group id
*/
function tabMoveToGroup (tab, group) {
let id = (typeof group == "object") ? group.id : group;
tabView.GroupItems.moveTabToGroupItem(tab, id);
}
/**
* @method switchToGroup {{{
* @param {String|Number} spec
* @param {Boolean} wrap
*/
function switchToGroup (spec, wrap) {
const GI = tabView.GroupItems
let current = GI.getActiveGroupItem() || GI.getActiveOrphanTab();
let groupsAndOrphans = GI.groupItems.concat(GI.getOrphanedTabs());
let offset = 1, relative = false, index;
if (typeof spec === "number")
index = parseInt(spec, 10);
else if (/^[+-]\d+$/.test(spec)) {
let buf = parseInt(spec, 10);
index = groupsAndOrphans.indexOf(current) + buf;
offset = buf >= 0 ? 1 : -1;
relative = true;
} else if (spec != "") {
if (/^\d+$/.test(spec))
spec = parseInt(spec, 10);
let targetGroup = getGroupByName(spec)[0];
if (targetGroup)
index = groupsAndOrphans.indexOf(targetGroup);
else {
liberator.echoerr("No such group: " + spec);
return;
}
} else {
return;
}
let length = groupsAndOrphans.length;
let apps = appTabs;
function groupSwitch (index, wrap) {
if (index > length - 1)
index = wrap ? index % length : length - 1;
else if (index < 0)
index = wrap ? index % length + length : 0;
let target = groupsAndOrphans[index],
group = null;
if (target instanceof tabView.GroupItem) {
group = target;
target = target.getActiveTab() || target.getChild(0);
}
if (target) {
gBrowser.mTabContainer.selectedItem = target.tab;
} else if (group && apps.length != 0) {
GI.setActiveGroupItem(group);
tabView.UI.goToTab(tabs.getTab(0));
} else if (relative) {
groupSwitch(index + offset, true);
} else {
liberator.echoerr("Cannot switch to " + spec);
return;
}
}
groupSwitch(index, wrap);
} // }}}
/**
* removeTab {{{
* @param {Element} tab
* @param {Number} count
* @param {Boolean} focusLeftTab
* @param {Number} quitOnLastTab
* @see tabs.remove
*/
function removeTab (tab, count, focusLeftTab, quitOnLastTab) {
const gb = gBrowser;
function remove (tab) {
if (vTabs.length > 1) {
gb.removeTab(tab);
} else if (buffer.URL != "about:blank" || gb.webNavigation.sessionHistory.count > 0) {
gb.loadURI("about:blank");
} else {
liberator.beep();
}
}
let vTabs = gb.visibleTabs;
if (typeof count != "number" || count < 1)
count = 1;
if (quitOnLastTab >= 1 && gb.tabs.length <= count) {
if (liberator.windows.length > 1)
window.close();
else
liberator.quit(quitOnLastTab == 2);
return;
}
// delegate selecting a tab to Firefox after the tab removed
if (count === 1 && !focusLeftTab && tab.owner) {
remove(tab);
return;
}
let index = vTabs.indexOf(tab);
liberator.assert(index >= 0, "No such tab(s) in the current tabs");
let start, end, selIndex;
if (focusLeftTab) {
end = index;
start = Math.max(0, index - count + 1);
selIndex = Math.max(0, start - 1);
} else {
start = index;
end = Math.min(index + count, vTabs.length) - 1;
selIndex = end + 1;
if (selIndex >= vTabs.length)
selIndex = start > 0 ? start - 1 : 0;
}
gb.mTabContainer.selectedItem = vTabs[selIndex];
for (let i = end; i >= start; i--) {
remove(vTabs[i]);
}
} // }}}
/**
* setGroupTitile {{{
* @param {String} title
* @param {GroupItem} group
*/
function setGroupTitle (title, group) {
let activeGroup = tabView.GroupItems.getActiveGroupItem();
if (!group)
group = activeGroup;
liberator.assert(group, "Missing group");
group.setTitle(title);
if (title && group.$title.hasClass("defaultName"))
group.$title.removeClass("defaultName");
if (group === activeGroup)
gBrowser.updateTitlebar();
} // }}}
// ============================================================================
// Mappings {{{
// ============================================================================
/**
* {count}g@ select {count} of AppTab
* g@ select AppTab,
* if already selected, select the next AppTab
*/
mappings.add([modes.NORMAL], ["g@"],
"Go to AppTab",
function (count) {
let apps = appTabs;
let i = 0;
if (count != null)
i = count - 1;
else {
let currentTab = tabs.getTab();
if (currentTab.pinned)
i = apps.indexOf(currentTab) + 1;
i %= apps.length;
}
if (apps[i])
selectVisible(i);
}, { count: true });
/**
* Switch to next group
*/
mappings.add([modes.NORMAL], ["