aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--panorama.js629
1 files changed, 629 insertions, 0 deletions
diff --git a/panorama.js b/panorama.js
new file mode 100644
index 0000000..3f1c715
--- /dev/null
+++ b/panorama.js
@@ -0,0 +1,629 @@
+/**
+ * Use at your OWN RISK.
+ */
+let INFO = <>
+<plugin name="panorama" version="0.1"
+ href="https://github.com/vimpr/vimperator-plugins/blob/master/panorama.js"
+ summary="Add supports for Panorama"
+ lang="en-US"
+ xmlns="http://vimperator.org/namespaces/liberator">
+ <author email="teramako@gmail.com">teramako</author>
+ <license>MPL 1.1/GPL 2.0/LGPL 2.1</license>
+ <project name="Vimperator" minVersion="3.0pre"/>
+ <p>
+ 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.
+ </p>
+ <p>
+ Use at your OWN RISK.
+ This pluin overwrite many mappings, some commands, and some completions.
+ </p>
+ <h3 tag="panorama-new">New Mappings and Commands</h3>
+ <item>
+ <tags>g@</tags>
+ <spec><oa>count</oa>g@</spec>
+ <description>
+ <p>Switch to AppTab.</p>
+ <p>If the current tab is already AppTab, switch to the next AppTab.</p>
+ </description>
+ </item>
+ <item>
+ <tags><![CDATA[<C-S-n>]]></tags>
+ <spec><oa>count</oa>&lt;C-S-n></spec>
+ <description>
+ <p>Switch to next group.</p>
+ <p>Caution: cannot switch to empty group.</p>
+ </description>
+ </item>
+ <item>
+ <tags><![CDATA[<C-S-p>]]></tags>
+ <spec><oa>count</oa>&lt;C-S-p></spec>
+ <description>
+ <p>Switch to previous group.</p>
+ </description>
+ </item>
+ <item>
+ <tags>:mkgroup :mkg</tags>
+ <spec>:mkg<oa>roup</oa><oa>!</oa> <oa>GroupName</oa></spec>
+ <description>
+ <p>Create new tab group named <a>GroupName</a>.</p>
+ <p>If specified <a>!</a>, move the current tab to the group.</p>
+ </description>
+ </item>
+ <item>
+ <tags>:stash :stashtogroup</tags>
+ <spec>:stash<oa>togroup</oa><oa>!</oa> <a>GroupName</a></spec>
+ <description>
+ <p>Stash the current tab to <a>GroupName</a>.</p>
+ <p>Caution: connnot stash AppTab (pinned tab)</p>
+ </description>
+ </item>
+ <item>
+ <tags>:switchgroup :swg</tags>
+ <spec>:switchgroup <a>GroupName</a></spec>
+ <spec>:swg <a>GroupName</a></spec>
+ <spec>:<oa>count</oa>switchgroup</spec>
+ <spec>:<oa>count</oa>swg</spec>
+ <description>
+ <p>Switch group to <a>GroupName</a></p>
+ </description>
+ </item>
+</plugin>
+</>;
+
+/**
+ * @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;
+
+ 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[0];
+ url = entry.url;
+ title = entry.title;
+ } 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);
+} // }}}
+
+// ============================================================================
+// 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;
+ 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], ["<C-S-n>"],
+ "switch to next group",
+ function (count) { switchToGroup("+" + (count || 1), true); },
+ { count: true });
+
+/**
+ * Switch to previous group
+ */
+mappings.add([modes.NORMAL], ["<C-S-p>"],
+ "switch to previous group",
+ function (count) { switchToGroup("-" + (count || 1), true); },
+ { count: true });
+
+// overwrite 'g0", 'g^'
+mappings.getDefault(modes.NORMAL, "g0").action = function (count) { selectVisible(0); };
+// overwrite 'g$'
+mappings.getDefault(modes.NORMAL, "g$").action = function (count) { selectVisible("$"); };
+// overwrite 'gt'
+mappings.getDefault(modes.NORMAL, "gt").action = function (count) {
+ if (count != null)
+ selectVisible(count - 1, false);
+ else
+ selectVisible("+1", true);
+};
+// overwrite 'C-n', 'C-Tab', 'C-PageDown'
+mappings.getDefault(modes.NORMAL, "<C-n>").action = function (count) {
+ selectVisible("+" + (count || 1), true);
+};
+// overwrite 'gT'
+mappings.getDefault(modes.NORMAL, "gT").action = function (count) {
+ selectVisible("-" + (count || 1), true);
+}
+// overwrite 'b'
+mappings.getDefault(modes.NORMAL, "b").action = function (count) {
+ if (count != null)
+ selectVisible(count - 1);
+ else
+ commandline.open("", "buffer! ", modes.EX);
+}
+
+// }}}
+
+// ============================================================================
+// Command {{{
+// ============================================================================
+/**
+ * overwrite :ls :buffers
+ */
+let (cmd = commands.get("buffers")) {
+ cmd.action = function (args) {
+ completion.listCompleter("buffer", args.literalArg, null, args.bang);
+ };
+ cmd.bang = true;
+}
+
+/**
+ * overwrite :buffer
+ */
+let (cmd = commands.get("buffer")) {
+ cmd.action = function (args) {
+ let arg = args.literalArg;
+ if (arg && args.count > 0)
+ switchTo(arg, args.bang);
+ else if (args.count > 0)
+ switchTo(args.count.toString(), args.bang);
+ else
+ switchTo(arg, args.bang);
+ };
+ cmd.completer = function (context) completion.buffer(context, true);
+}
+
+commands.addUserCommand(["mkg[roup]"], "create Group",
+ function (args) {
+ let groupName = args.literalArg;
+ let group = createGroup(groupName);
+ if (args.bang) {
+ let currentTab = tabs.getTab();
+ if (!currentTab.pinned)
+ TV.moveTabTo(currentTab, group.id);
+ }
+ }, {
+ argCount: "1",
+ bang: true,
+ literal: 0,
+ }, true);
+
+commands.addUserCommand(["switchgruop", "swg"], "Switch Group",
+ function (args) {
+ if (args.count > 0) {
+ switchToGroup("+" + args.count, true);
+ } else {
+ switchToGroup(args.literalArg);
+ }
+ }, {
+ argCount: "?",
+ count: true,
+ literal: 0,
+ completer: function (context) completion.tabgroup(context, true),
+ }, true);
+
+commands.addUserCommand(["stash[togroup]"], "Stash the current tab to other group",
+ function (args) {
+ let currentTab = tabs.getTab();
+ if (currentTab.pinned) {
+ liberator.echoerr("Connot stash an AppTab");
+ return;
+ }
+ let groupName = args.literalArg;
+ let group = getGroupByName(groupName)[0];
+ if (!group) {
+ if (args.bang) {
+ group = createGroup(groupName);
+ } else {
+ liberator.echoerr("No such group: " + groupName.quote() + ". if want create, add \"!\"");
+ return;
+ }
+ }
+ TV.moveTabTo(currentTab, group.id);
+ } ,{
+ argCount: "1",
+ bang: true,
+ literal: 0,
+ completer: function (context) completion.tabgroup(context, true),
+ }, true);
+
+// }}}
+
+// ============================================================================
+// Completion {{{
+// ============================================================================
+completion.tabgroup = function TabGroupCompleter (context, excludeActiveGroup) {
+ const GI = tabView.GroupItems;
+ let groupItems = GI.groupItems;
+ if (excludeActiveGroup) {
+ let activeGroup = GI.getActiveGroupItem();
+ groupItems = groupItems.filter(function(group) group.id != activeGroup.id);
+ }
+ context.title = ["TabGroup"];
+ context.completions = groupItems.map(function(group) {
+ let title = group.getTitle();
+ let desc = [
+ "Title:", title || "(Untitled)",
+ "TabNum:", group.getChildren().length,
+ ].join(" ");
+ if (!title)
+ title = group.id;
+ return [title, desc];
+ });
+};
+
+/**
+ * overwite completion.buffer
+ */
+(function(TV, gBrowser) {
+ const UNTITLE_LABEL = "(Untitled)";
+
+ function getIndicator (tab) {
+ if (tab == gBrowser.mCurrentTab)
+ return "%";
+ else if (tab == tabs.alternate)
+ return "#";
+ return " ";
+ }
+ function getURLFromTab (tab) {
+ if ("__SS_restoreState" in tab.linkedBrowser && "__SS_data" in tab.linkedBrowser)
+ return tab.linkedBrowser.__SS_data.entries[0].url;
+ return tab.linkedBrowser.contentDocument.location.href;
+ }
+ function generateVisibleTabs () {
+ for (let [i, tab] in Iterator(gBrowser.visibleTabs)) {
+ let indicator = getIndicator(tab),
+ label = tab.label || UNTITLE_LABEL,
+ url = getURLFromTab(tab),
+ index = (tab._tPos + 1) + ": ";
+ let item = {
+ text: [ index + label, index + url],
+ url: template.highlightURL(url),
+ indicator: indicator + (tab.pinned ? "@" : ""),
+ icon: tab.image || DEFAULT_FAVICON
+ };
+ if (!tab.pinned && tab.tabItem && tab.tabItem.parent) {
+ let groupName = tab.tabItem.parent.getTitle();
+ if (groupName) {
+ let prefix = groupName + " " + (i + 1) + ": ";
+ item.text.push(prefix + label);
+ item.text.push(prefix + url);
+ }
+ }
+ yield item;
+ }
+ }
+ function generateGroupList (group, groupName) {
+ for (let [i, tabItem] in Iterator(group.getChildren())) {
+ let indicator = getIndicator(tabItem.tab);
+ let index = (tabItem.tab._tPos + 1) + ": ",
+ label = tabItem.tab.label || UNTITLE_LABEL,
+ url = getURLFromTab(tabItem.tab);
+ let item = {
+ text: [index + label, index + url],
+ url: template.highlightURL(url),
+ indicator: indicator,
+ icon: tabItem.tab.image || DEFAULT_FAVICON
+ };
+ if (groupName) {
+ index = groupName + " " + (i + 1) + ": ";
+ item.text.push(index + label);
+ item.text.push(index + url);
+ }
+ yield item;
+ }
+ }
+ function generateOrphanedList (tabItems) {
+ for (let [i, tabItem] in Iterator(tabItems)) {
+ let indicator = getIndicator(tabItem.tab),
+ index = (tabItem.tab._tPos + 1) + ": ";
+ label = tabItem.tab.label || UNTITLE_LABEL,
+ url = getURLFromTab(tabItem.tab);
+ yield {
+ text: [index + label, index + url],
+ url: template.highlightURL(url),
+ indicator: indicator,
+ icon: tabItem.tab.image || DEFAULT_FAVICON
+ };
+ }
+ }
+
+ completion.buffer = function bufferCompletion (context, all) {
+ context.anchored = false;
+ context.keys = { text: "text", description: "url", icon: "icon" };
+ context.compare = CompletionContext.Sort.number;
+ let process = context.process[0];
+ context.process = [
+ function (item, text) <>
+ <span highlight="Indicator" style="display: inline-block; width: 2em; text-align: center">{item.item.indicator}</span>
+ { process.call(this, item, text) }
+ </>
+ ];
+ context.title = ["Buffers"];
+ context.completions = [item for (item in generateVisibleTabs())];
+ if (!all)
+ return;
+ let self = this;
+ TV._initFrame(function() {
+ let groups = TV._window.GroupItems;
+ let activeGroup = groups.getActiveGroupItem();
+ let activeGroupId = activeGroup === null ? null : activeGroup.id;
+ for (let [i, group] in Iterator(groups.groupItems)) {
+ if (group.id != activeGroupId) {
+ let groupName = group.getTitle();
+ context.fork("GROUP_" + group.id, 0, self, function (context) {
+ context.title = [groupName || UNTITLE_LABEL];
+ context.completions = [item for (item in generateGroupList(group, groupName))];
+ });
+ }
+ }
+ let orphanedTabs = [tabItem for ([, tabItem] in Iterator(groups.getOrphanedTabs())) if (tabItem.tab.hidden)];
+ if (orphanedTabs.length == 0)
+ return;
+ context.fork("__ORPHANED__", 0, self, function (context) {
+ context.title = ["Orphaned"];
+ context.completions = [item for (item in generateOrphanedList(orphanedTabs))];
+ });
+ });
+ };
+
+})(window.TabView, window.gBrowser);
+
+// }}}
+
+// vim: sw=2 ts=2 et fdm=marker: