From 3ed7d2be846607d28ebdc417711d45a1d99b416d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=96=9F=E9=85=8C=20=E9=B5=AC=E5=85=84?= <tgckpg@gmail.com>
Date: Sat, 2 Apr 2016 18:35:12 +0800
Subject: [PATCH] History stacks

---
 botanjs/src/Components/Vim/Controls.js        |  15 ++-
 .../Vim/Ex/{Search.js => Command.js}          | 105 ++++++++++++++----
 botanjs/src/Components/Vim/State/History.js   |  96 ++++++++++++++++
 botanjs/src/Components/Vim/VimArea.js         |  16 +--
 .../Components.Vim.Controls.InputEvent.js     |   4 +
 .../externs/Components.Vim.State.History.js   |   9 ++
 6 files changed, 212 insertions(+), 33 deletions(-)
 rename botanjs/src/Components/Vim/Ex/{Search.js => Command.js} (55%)
 create mode 100644 botanjs/src/Components/Vim/State/History.js
 create mode 100644 botanjs/src/externs/Components.Vim.State.History.js

diff --git a/botanjs/src/Components/Vim/Controls.js b/botanjs/src/Components/Vim/Controls.js
index e1293578..631d736d 100644
--- a/botanjs/src/Components/Vim/Controls.js
+++ b/botanjs/src/Components/Vim/Controls.js
@@ -4,8 +4,8 @@
 	/** @type {System.Debug} */
 	var debug = __import( "System.Debug" );
 
-	/** @type {Components.Vim.Ex.Search} */
-	var ExSearch = __import( "Components.Vim.Ex.Search" );
+	/** @type {Components.Vim.Ex.Command} */
+	var ExCommand = __import( "Components.Vim.Ex.Command" );
 
 	var beep = ns[ NS_INVOKE ]( "Beep" );
 
@@ -441,10 +441,10 @@
 				}, _8 );
 				break;
 
-			case SLASH: // "/" Seach movement
+			case SLASH: // "/" Search movement
 				this.__cMovement = true;
 
-				this.__divedCCmd = new ExSearch( ccur );
+				this.__divedCCmd = new ExCommand( ccur, "/" );
 				this.__divedCCmd.handler( e );
 				break;
 			default:
@@ -499,7 +499,8 @@
 				this.__cMovement = false;
 				this.__divedCCmd = null;
 			}
-			else return;
+
+			if( e.canceled ) return;
 		}
 
 		var cfeeder = this.__cfeeder;
@@ -545,6 +546,7 @@
 	var InputEvent = function( sender, e )
 	{
 		this.__target = sender;
+		this.__canceled = false;
 
 		if( typeof( e ) == "string" )
 		{
@@ -572,11 +574,14 @@
 		this.__range = null;
 	};
 
+	InputEvent.prototype.cancel = function() { this.__canceled = true; };
+
 	__readOnly( InputEvent.prototype, "target", function() { return this.__target; } );
 	__readOnly( InputEvent.prototype, "key", function() { return this.__key; } );
 	__readOnly( InputEvent.prototype, "keyCode", function() { return this.__kCode; } );
 	__readOnly( InputEvent.prototype, "ModKeys", function() { return this.__modKeys; } );
 	__readOnly( InputEvent.prototype, "Escape", function() { return this.__escape; } );
+	__readOnly( InputEvent.prototype, "canceled", function() { return this.__canceled; } );
 
 	__readOnly( InputEvent.prototype, "range", function() {
 
diff --git a/botanjs/src/Components/Vim/Ex/Search.js b/botanjs/src/Components/Vim/Ex/Command.js
similarity index 55%
rename from botanjs/src/Components/Vim/Ex/Search.js
rename to botanjs/src/Components/Vim/Ex/Command.js
index 169f5a55..cd8600be 100644
--- a/botanjs/src/Components/Vim/Ex/Search.js
+++ b/botanjs/src/Components/Vim/Ex/Command.js
@@ -8,27 +8,37 @@
 	/** @type {System.utils.Perf} */
 	var Perf                         = __import( "System.utils.Perf" );
 
-	var Mesg = __import( "Components.Vim.Message" );
+	/**j@type {Components.Vim.State.History} */
+	var History                                 = __import( "Components.Vim.State.History" );
+	var Mesg                                    = __import( "Components.Vim.Message" );
+	var beep                                    = __import( "Components.Vim.Beep" );
+
+	// This is for security & privacy concerns?
+	var ZMap = {
+		"/": Perf.uuid
+		, ":" : Perf.uuid
+	};
 
 	/** @type {Components.Vim.Cursor.IAction} */
-	var Search = function( Cursor )
+	var Command = function( Cursor, Mode )
 	{
 		var _self = this;
+		if( !ZMap[ Mode ] ) throw new Error( "Unsupport mode: " + Mode );
 
 		/** @type {Components.Vim.Cursor} */
 		this.__cursor = Cursor;
 
 		this.__statusBar = Cursor.Vim.statusBar;
 
+		this.__mode = Mode;
+		this.__hist = new History( ZMap[ Mode ] );
+
 		this.__command = [];
-		this.__blinkId = "ExSearchBlinkCycle" + Perf.uuid;
+		this.__currentCommand = null;
+		this.__blinkId = "ExCommandBlinkCycle" + Perf.uuid;
 		this.__curPos = 0;
 
-		this.__disp = function()
-		{
-		};
-
-        var feeder = Cursor.feeder;
+		var feeder = Cursor.feeder;
 
 		var __blink = false;
 		var __holdBlink = false;
@@ -77,7 +87,7 @@
 		this.__statusBar.override = this.__doBlink;
 	};
 
-	Search.prototype.dispose = function()
+	Command.prototype.dispose = function()
 	{
 		this.__statusBar.override = null;
 
@@ -86,7 +96,7 @@
 		feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) );
 	};
 
-	Search.prototype.handler = function( e )
+	Command.prototype.handler = function( e )
 	{
 		e.preventDefault();
 
@@ -96,6 +106,8 @@
 
 		var InputKey = null;
 
+		var histNav = false;
+
 		if( e.kMap( "Tab" ) )
 		{
 			InputKey = "^I";
@@ -106,8 +118,15 @@
 		}
 		else if( e.kMap( "BS" ) )
 		{
+			if( this.__curPos == 1 && 1 < this.__command.length )
+				return false;
+
 			this.__command.splice( --this.__curPos, 1 );
-			if( this.__command.length == 0 ) return true;
+			if( this.__command.length == 0 )
+			{
+				e.cancel();
+				return true;
+			}
 		}
 		else if( e.kMap( "Del" ) )
 		{
@@ -115,23 +134,56 @@
 		}
 		else if( e.kMap( "Enter" ) )
 		{
+			this.__process();
 			return true;
 		}
-		else if( e.kMap( "Up" ) ) // History navigations
-		{
-		}
-		else if( e.kMap( "Down" ) )
-		{
-		}
 		else if( e.kMap( "Left" ) )
 		{
-			if( 0 < this.__curPos ) this.__curPos --;
+			if( 1 < this.__curPos ) this.__curPos --;
 		}
 		else if( e.kMap( "Right" ) )
 		{
 			if( this.__curPos < this.__command.length )
 				this.__curPos ++;
 		}
+
+		// History stepping
+		else if( histNav = e.kMap( "Up" ) ) // History navigations
+		{
+			if( !this.__currentCommand )
+			{
+				this.__currentCommand = this.__command;
+			}
+
+			var n = this.__hist.prev( this.__currentCommand );
+
+			if( n )
+			{
+				this.__command = n;
+				this.__curPos = n.length;
+			}
+			else
+			{
+				beep();
+			}
+		}
+		else if( histNav = e.kMap( "Down" ) )
+		{
+			var n = this.__hist.next( this.__currentCommand );
+
+			if( n )
+			{
+				this.__command = n;
+				this.__curPos = n.length;
+			}
+			else if( this.__currentCommand )
+			{
+				this.__command = this.__currentCommand;
+				this.__currentCommand = null;
+			}
+
+			else beep();
+		}
 		else
 		{
 			InputKey = e.key;
@@ -142,9 +194,22 @@
 			this.__command.splice( this.__curPos ++, 0, InputKey );
 		}
 
+		if( !histNav )
+		{
+			this.__hist.reset();
+			if( this.__currentCommand ) this.__currentCommand = this.__command;
+		}
+
+		e.cancel();
+
 		var feeder = this.__cursor.feeder;
 		feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) );
-	}
+	};
 
-	ns[ NS_EXPORT ]( EX_CLASS, "Search", Search );
+	Command.prototype.__process = function()
+	{
+		this.__hist.push( this.__command );
+	};
+
+	ns[ NS_EXPORT ]( EX_CLASS, "Command", Command );
 })();
diff --git a/botanjs/src/Components/Vim/State/History.js b/botanjs/src/Components/Vim/State/History.js
new file mode 100644
index 00000000..18c67be1
--- /dev/null
+++ b/botanjs/src/Components/Vim/State/History.js
@@ -0,0 +1,96 @@
+(function(){
+	var ns = __namespace( "Components.Vim.State" );
+
+	/** @type {System.Debug} */
+	var debug                        = __import( "System.Debug" );
+
+	// private static
+	var Zones = {};
+
+	var PartialBA = function( a, b )
+	{
+		var l = b.length;
+		if( a.length < l ) return false;
+
+		for( var i = 0; i < l; i ++ )
+		{
+			if( a[ i ] != b[ i ] ) return false;
+		}
+
+		return true;
+	};
+
+	var ExactAB = function( a, b )
+	{
+		var l = a.length < b.length ? b.length : a.length;
+		for( var i = 0; i < l; i ++ )
+		{
+			if( a[ i ] != b[ i ] ) return false;
+		}
+
+		return true;
+	}
+
+	var History = function( z )
+	{
+		if( !Zones[ z ] ) Zones[ z ] = [];
+
+		this.__pi = 0;
+		this.__zone = Zones[ z ];
+		this.reset();
+	};
+
+	History.prototype.push = function( stack )
+	{
+		if( this.__zone.length
+			&& ExactAB( this.__zone[ this.__zone.length - 1 ], stack )
+		) {
+			debug.Info( "This is the previous command, skipping" );
+			return;
+		}
+		this.__zone.push( stack );
+	};
+
+	History.prototype.prev = function( stack )
+	{
+		if( this.__zone.length <= this.__i ) this.reset();
+
+		while( -1 < this.__i )
+		{
+			var st = this.__zone[ this.__i -- ];
+			if( st && PartialBA( st, stack ) )
+			{
+				return st.slice();
+			}
+		}
+
+		return null;
+	};
+
+	History.prototype.next = function( stack )
+	{
+		if( this.__i < 0 )
+		{
+			this.__i ++;
+			this.next( stack );
+		}
+
+		while( this.__i < this.__zone.length )
+		{
+			var st = this.__zone[ this.__i ++ ];
+			if( st && PartialBA( st, stack ) )
+			{
+				return st.slice();
+			}
+		}
+
+		return null;
+	};
+
+	History.prototype.reset =  function()
+	{
+		this.__i = this.__zone.length - 1;
+	};
+
+	ns[ NS_EXPORT ]( EX_CLASS, "History", History );
+})();
diff --git a/botanjs/src/Components/Vim/VimArea.js b/botanjs/src/Components/Vim/VimArea.js
index c5480ecf..ee76302b 100644
--- a/botanjs/src/Components/Vim/VimArea.js
+++ b/botanjs/src/Components/Vim/VimArea.js
@@ -2,18 +2,18 @@
 	var ns = __namespace( "Components.Vim" );
 
 	/** @type {Dandelion.IDOMElement} */
-	var IDOMElement                             = __import( "Dandelion.IDOMElement" );
+	var IDOMElement							 = __import( "Dandelion.IDOMElement" );
 	/** @type {System.utils.DataKey} */
-	var DataKey                                 = __import( "System.utils.DataKey" );
+	var DataKey								 = __import( "System.utils.DataKey" );
 	/** @type {System.Cycle} */
-	var Cycle                                   = __import( "System.Cycle" );
+	var Cycle								   = __import( "System.Cycle" );
 	/** @type {System.Debug} */
-	var debug                                   = __import( "System.Debug" );
+	var debug								   = __import( "System.Debug" );
 
 	/** @type {Components.Vim.State.Registers} */
-	var Registers                                 = __import( "Components.Vim.State.Registers" );
+	var Registers								 = __import( "Components.Vim.State.Registers" );
 	/** @type {Components.Vim.Syntax.Analyzer} */
-	var SyntaxAnalyzer                            = __import( "Components.Vim.Syntax.Analyzer" );
+	var SyntaxAnalyzer							= __import( "Components.Vim.Syntax.Analyzer" );
 
 	/** @type {Components.Vim.LineFeeder} */
 	var LineFeeder = ns[ NS_INVOKE ]( "LineFeeder" );
@@ -96,7 +96,7 @@
 
 		// Content feeder
 		var cfeeder = new LineFeeder( cRange, c );
-        var contentAnalyzer = new SyntaxAnalyzer( cfeeder );
+		var contentAnalyzer = new SyntaxAnalyzer( cfeeder );
 
 		// Feed the contents to content feeder
 		// This "\n" fixes the last line "\n" not displaying
@@ -136,7 +136,7 @@
 		Update();
 
 		this.contentFeeder = cfeeder;
-        this.contentAnalyzer = contentAnalyzer;
+		this.contentAnalyzer = contentAnalyzer;
 		this.statusFeeder = sfeeder;
 		this.statusBar = statusBar;
 		this.registers = new Registers();
diff --git a/botanjs/src/externs/Components.Vim.Controls.InputEvent.js b/botanjs/src/externs/Components.Vim.Controls.InputEvent.js
index afe109df..6678a271 100644
--- a/botanjs/src/externs/Components.Vim.Controls.InputEvent.js
+++ b/botanjs/src/externs/Components.Vim.Controls.InputEvent.js
@@ -11,7 +11,11 @@ Components.Vim.Controls.InputEvent.key;
 Components.Vim.Controls.InputEvent.ModKeys;
 /** @type Boolean */
 Components.Vim.Controls.InputEvent.Escape;
+/** @type Boolean */
+Components.Vim.Controls.InputEvent.canceled;
 /** @type Number */
 Components.Vim.Controls.InputEvent.keyCode;
 /** @type Function */
 Components.Vim.Controls.InputEvent.kMap;
+/** @type Function */
+Components.Vim.Controls.InputEvent.cancel;
diff --git a/botanjs/src/externs/Components.Vim.State.History.js b/botanjs/src/externs/Components.Vim.State.History.js
new file mode 100644
index 00000000..f28f3483
--- /dev/null
+++ b/botanjs/src/externs/Components.Vim.State.History.js
@@ -0,0 +1,9 @@
+/** @constructor */
+Components.Vim.State.History = function(){};
+
+/** @type Function */
+Components.Vim.State.History.push;
+/** @type Function */
+Components.Vim.State.History.prev;
+/** @type Function */
+Components.Vim.State.History.next;