diff --git a/NEWS b/NEWS index 14f40802..1a21de7c 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@
 2007-XX-XX:
 	* version 0.5.3
+	* new gb and gB mappings to repeat the last :buffer[!] command,
 	* :q doesn't close the whole browser, if there are more than one windows
 	* new :winclose command
 	* b calls :buffer! now instead of :buffer
diff --git a/content/buffers.js b/content/buffers.js
index 07facc69..8d6288f6 100644
--- a/content/buffers.js
+++ b/content/buffers.js
@@ -31,6 +31,9 @@ function Buffer() //{{{
     ////////////////////////////////////////////////////////////////////////////////
     ////////////////////// PRIVATE SECTION /////////////////////////////////////////
     /////////////////////////////////////////////////////////////////////////////{{{
+    // used for the "B" mapping to remember the last :buffer[!] command
+    var lastBufferSwitchArgs = ""; 
+    var lastBufferSwitchSpecial = true;
 
     var zoom_manager = ZoomManager.prototype.getInstance();
     const ZOOM_INTERVAL = 25;
@@ -162,7 +165,6 @@ function Buffer() //{{{
         return result;
     }
 
-    // TODO: move to v.buffers.list()
     this.list = function(fullmode)
     {
         if (fullmode)
@@ -373,6 +375,66 @@ function Buffer() //{{{
         vimperator.bufferwindow.selectItem(getBrowser().mTabContainer.selectedIndex);
     }
 
+    // XXX: should this be in v.buffers. or v.tabs.?
+    // "buffer" is a string which matches the URL or title of a buffer, if it
+    // is null, the last used string is used again
+    this.switchTo = function(buffer, allowNonUnique, count, reverse)
+    {
+        if (buffer != null)
+        {
+            // store this command, so it can be repeated with "B"
+            lastBufferSwitchArgs = buffer;
+            lastBufferSwitchSpecial = allowNonUnique;
+        }
+        else
+        {
+            buffer = lastBufferSwitchArgs;
+            if (typeof allowNonUnique == "undefined" || allowNonUnique == null)
+                allowNonUnique = lastBufferSwitchSpecial;
+        }
+
+        if (!count || count < 1)
+            count = 1;
+        if (typeof reverse != "boolean")
+            reverse = false;
+
+        var match;
+        if (match = buffer.match(/^(\d+):?/))
+            return vimperator.tabs.select(parseInt(match[1]) - 1, false); // make it zero-based
+
+        var matches = [];
+        var lower_buffer = buffer.toLowerCase();
+        var first = vimperator.tabs.index() + (reverse ? 0 : 1);
+        for (var i = 0; i < getBrowser().browsers.length; i++)
+        {
+            var index = (i + first) % getBrowser().browsers.length;
+            var url = getBrowser().getBrowserAtIndex(index).contentDocument.location.href;
+            var title = getBrowser().getBrowserAtIndex(index).contentDocument.title.toLowerCase();
+            if (url == buffer)
+                return vimperator.tabs.select(index, false);
+
+            if (url.indexOf(buffer) >= 0 || title.indexOf(lower_buffer) >= 0)
+                matches.push(index);
+        }
+        if (matches.length == 0)
+            vimperator.echoerr("E94: No matching buffer for " + buffer);
+        else if (matches.length > 1 && !allowNonUnique)
+            vimperator.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;
+
+            vimperator.tabs.select(matches[index], false);
+        }
+    };
+
     this.zoomIn = function(steps)
     {
         bumpZoomLevel(steps);
diff --git a/content/commands.js b/content/commands.js
index 6aa954b8..c73b11cd 100644
--- a/content/commands.js
+++ b/content/commands.js
@@ -346,34 +346,7 @@ function Commands() //{{{
         }
     ));
     addDefaultCommand(new Command(["b[uffer]"],
-        // TODO: move to v.tabs/buffers
-        function(args, special)
-        {
-            var match;
-            if (match = args.match(/^(\d+):?/))
-                return vimperator.tabs.select(parseInt(match[1]) - 1, false); // make it zero-based
-
-            var matches = [];
-            var lower_args = args.toLowerCase();
-            var first = vimperator.tabs.index() + 1;
-            for (var i = 0; i < getBrowser().browsers.length; i++)
-            {
-                var index = (i + first) % getBrowser().browsers.length;
-                var url = getBrowser().getBrowserAtIndex(index).contentDocument.location.href;
-                var title = getBrowser().getBrowserAtIndex(index).contentDocument.title.toLowerCase();
-                if (url == args)
-                    return vimperator.tabs.select(index, false);
-
-                if (url.indexOf(args) >= 0 || title.indexOf(lower_args) >= 0)
-                    matches.push(index);
-            }
-            if (matches.length == 0)
-                vimperator.echoerr("E94: No matching buffer for " + args);
-            else if (matches.length > 1 && !special)
-                vimperator.echoerr("E93: More than one match for " + args);
-            else
-                vimperator.tabs.select(matches[0], false);
-        },
+        function(args, special) { vimperator.buffer.switchTo(args, special); },
         {
             usage: ["b[uffer][!] {url|index}"],
             short_help: "Go to buffer from buffer list",
diff --git a/content/mappings.js b/content/mappings.js
index 8defcd2b..927fc32e 100644
--- a/content/mappings.js
+++ b/content/mappings.js
@@ -382,6 +382,22 @@ function Mappings() //{{{
                   "WARNING: This mapping may be removed/changed in future."
         }
     ));
+    addDefaultMap(new Map(vimperator.modes.NORMAL, ["gb"],
+        function(count) { vimperator.buffer.switchTo(null, null, count, false); },
+        {
+            short_help: "Repeat last :buffer[!] command",
+            help: "This is useful to quickly jump between buffers which have a similar URL or title.",
+            flags: Mappings.flags.COUNT
+        }
+    ));
+    addDefaultMap(new Map(vimperator.modes.NORMAL, ["gB"],
+        function(count) { vimperator.buffer.switchTo(null, null, count, true); },
+        {
+            short_help: "Repeat last :buffer[!] command in reverse direction",
+            help: "Just like gb but in the other direction.",
+            flags: Mappings.flags.COUNT
+        }
+    ));
     addDefaultMap(new Map(vimperator.modes.NORMAL, ["d"],
         function(count) { vimperator.tabs.remove(getBrowser().mCurrentTab, count, false, 0); },
         {