penguin/AstroJS

Javascript framework for my blog

commit 799a911e06355140a78cd1a0240715a863e21d81

author斟酌 鵬兄 <tgckpg@gmail.com>
date2016-03-29T20:32:36Z
subjectPartial % motion
commit 799a911e06355140a78cd1a0240715a863e21d81
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2016-03-29T20:32:36Z

    Partial % motion
---
 botanjs/src/Components/Vim/Controls.js             |  27 +++
 botanjs/src/Components/Vim/Cursor.js               |  45 +++-
 botanjs/src/Components/Vim/Syntax/Analyzer.js      | 267 +++++++++++++++++++++
 botanjs/src/Components/Vim/VimArea.js              |   6 +-
 .../src/externs/Components.Vim.Syntax.Analyzer.js  |   7 +
 .../externs/Components.Vim.Syntax.TokenMatch.js    |  14 ++
 botanjs/src/externs/Components.Vim.Syntax.js       |   2 +
 botanjs/src/externs/Components.Vim.VimArea.js      |   2 +
 8 files changed, 359 insertions(+), 11 deletions(-)

diff --git a/botanjs/src/Components/Vim/Controls.js b/botanjs/src/Components/Vim/Controls.js
index 8dba252..0909015 100644
--- a/botanjs/src/Components/Vim/Controls.js
+++ b/botanjs/src/Components/Vim/Controls.js
@@ -281,6 +281,30 @@
 				break
 
 			case SHIFT + _5: // %, Find next item
+				var analyzer = this.__vimArea.contentAnalyzer;
+
+				/** @type {Components.Vim.Syntax.TokenMatch} */
+				var bracketMatch = analyzer.bracketAt( ccur.aPos );
+
+				if( bracketMatch.open == -1 )
+				{
+					beep();
+					break;
+				}
+
+				var d = 1;
+				var at = bracketMatch.close;
+				if( bracketMatch.selected == at )
+				{
+					d = -1;
+					at = bracketMatch.open;
+				}
+
+				while( ccur.aPos != at )
+				{
+					ccur.moveX( d, true );
+				}
+
 				break;
 			case T: // To
 				break;
@@ -291,6 +315,8 @@
 					break;
 				}
 
+				var analyzer = this.__vimArea.contentAnalyzer;
+
 				this.__cMovement = true;
 				// Word boundary
 				this.__comp( kCode, function(){
@@ -307,6 +333,7 @@
 				}, SHIFT + S_BRACKET_L );
 				this.__comp( kCode, function(){
 					debug.Info( "Bracket boundary }" );
+					analyzer.bracketAt( ccur.aPos );
 				}, SHIFT + S_BRACKET_R );
 				break;
 
diff --git a/botanjs/src/Components/Vim/Cursor.js b/botanjs/src/Components/Vim/Cursor.js
index 5f99f5d..240dd10 100644
--- a/botanjs/src/Components/Vim/Cursor.js
+++ b/botanjs/src/Components/Vim/Cursor.js
@@ -88,9 +88,8 @@
 	// Set by VimArea
 	Cursor.prototype.Vim;
 
-	// Can only be 1, -1
-	// 0 will be treated as undefined
-	Cursor.prototype.moveX = function( d, penentrate, phantomSpace )
+	// 0 will be treated as default ( 1 )
+	Cursor.prototype.moveX = function( d, penetrate, phantomSpace )
 	{
 		var x = this.pX;
 
@@ -101,7 +100,7 @@
 
 		var buffs = this.feeder.lineBuffers;
 
-		if( penentrate )
+		if( penetrate )
 		{
 			if( x < 0 && ( 0 < this.feeder.panY || 0 < this.Y ) )
 			{
@@ -118,15 +117,41 @@
 
 		var c = content[ x ];
 
-		// Include empty lines befor cursor end
+		// Motion includes empty lines before cursor end
 		if( ( phantomSpace && cLen - 1 <= x ) || ( cLen == 1 && c == undefined ) )
 		{
-			x = 0 < d ? cLen - 1 : 0;
+			if( 0 < d )
+			{
+				x = cLen - 1;
+				if( penetrate )
+				{
+					this.X = 0;
+					this.moveY( 1 );
+					return;
+				}
+			}
+			else
+			{
+				x = 0;
+			}
 		}
-		// ( 2 < cLen ) Exclude empty lines at cursor end
+		// ( 2 < cLen ) motion excludes empty lines at cursor end
 		else if( ( 2 <= cLen && x == cLen - 1 && c == " " ) || c == undefined )
 		{
-			x = 0 < d ? cLen - 2 : 0;
+			if( 0 < d )
+			{
+				x = cLen - 2;
+				if( penetrate )
+				{
+					this.X = 0;
+					this.moveY( 1 );
+					return;
+				}
+			}
+			else
+			{
+				x = 0;
+			}
 		}
 		else if( c == "\n" )
 		{
@@ -174,7 +199,7 @@
 		this.feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) );
 	};
 
-	Cursor.prototype.moveY = function( d, penentrate )
+	Cursor.prototype.moveY = function( d, penetrate )
 	{
 		var i;
 		var Y = this.Y + d;
@@ -197,7 +222,7 @@
 		{
 			var feeder = this.feeder;
 
-			if( penentrate )
+			if( penetrate )
 			{
 				feeder.pan( undefined, Y - moreAt );
 			}
diff --git a/botanjs/src/Components/Vim/Syntax/Analyzer.js b/botanjs/src/Components/Vim/Syntax/Analyzer.js
new file mode 100644
index 0000000..b21aace
--- /dev/null
+++ b/botanjs/src/Components/Vim/Syntax/Analyzer.js
@@ -0,0 +1,267 @@
+(function(){
+	var ns = __namespace( "Components.Vim.Syntax" );
+
+	/** @type {System.Debug} */
+	var debug                                   = __import( "System.Debug" );
+
+	var TOK_OPEN = 0;
+	var TOK_CLOSED = 1;
+	var TOK_LEVEL = 2;
+	var TOK_PARENT = 3;
+
+	var TOK_SEP = "\n";
+
+	var TOK_JOIN = function( a, b ) { return a + TOK_SEP + b; };
+
+	/*{{{ Private Class */
+	var TokenPairs = function( tok, content, esc )
+	{
+		var l = content.length;
+		var toks = tok.split( TOK_SEP );
+		var openToken = toks[0];
+		var closeToken = toks[1];
+
+		var opStack = [];
+
+		var unmatchedEd = [];
+
+		var lv = 0;
+
+		var pairs = [];
+
+		var lvUp = function( i )
+		{
+			opStack[ lv ] = i;
+			lv ++;
+		};
+
+		var lvDown = function( i )
+		{
+			if( lv == 0 )
+			{
+				// Cannot level down. i.e. Unmatched tokens
+				unmatchedEd.push( i );
+				return;
+			}
+
+			var Token = [];
+			Token[ TOK_OPEN ] = opStack[ -- lv ];
+			Token[ TOK_CLOSED ] = i;
+			Token[ TOK_LEVEL ] = lv;
+			Token[ TOK_PARENT ] =  0 < lv ? opStack[ lv - 1 ] : -1;
+
+			pairs.push( Token );
+		};
+
+		var opLen = openToken.length;
+		var edLen = closeToken.length;
+		for( var i = 0; i < l; i ++ )
+		{
+			var opTok = content.substr( i, opLen );
+			var edTok = content.substr( i, edLen );
+			if( opTok == openToken )
+			{
+				lvUp( i );
+				i += opLen - 1;
+			}
+			else if( edTok == closeToken )
+			{
+				lvDown( i );
+				i += edLen - 1;
+			}
+		}
+
+		if( unmatchedEd.length )
+		{
+			debug.Info( "Unmatched opening \"" + openToken + "\"@" + unmatchedEd.join( ", " ) );
+		}
+
+		if( 0 < lv )
+		{
+			debug.Info( "Unmatched closing \"" + closeToken + "\"@" + opStack.slice( 0, lv ) );
+		}
+
+		this.__pairs = pairs;
+		this.token = toks;
+	};
+
+	TokenPairs.prototype.token = "";
+
+	TokenPairs.prototype.matched = function()
+	{
+		return this.__pairs.sort(
+				function( a, b ) { return a[ TOK_OPEN ] - b[ TOK_OPEN ]; }
+				);
+	};
+
+	TokenPairs.prototype.find = function( pos, state )
+	{
+		if( state == undefined ) state = TOK_OPEN;
+
+		var pairs = this.__pairs;
+		var l = pairs.length;
+
+		for( var i = 0; i < l; i ++ )
+		{
+			var pair = pairs[i];
+			if( pair[ state ] == pos )
+			{
+				return pair;
+			}
+		}
+
+		return null;
+	};
+	/* End Private Class }}}*/
+
+	var Analyzer = function( feeder )
+	{
+		/* @type {Components.Vim.LineFeeder} */
+		this.__feeder = feeder;
+		this.__tokpairs = {};
+	};
+
+	Analyzer.prototype.bracketAt = function( p )
+	{
+		var c = this.__feeder.content;
+		var tokState = TOK_CLOSED;
+
+		var BracketPairs = null;
+		var cTok = c[ p ];
+
+		switch( cTok )
+		{
+			case "{": tokState = TOK_OPEN;
+			case "}":
+					  BracketPairs = this.GetPairs( TOK_JOIN( "{", "}" ) );
+				break;
+
+			case "[": tokState = TOK_OPEN;
+			case "]":
+				BracketPairs = this.GetPairs( TOK_JOIN( "[", "]" ) );
+				break;
+
+			case "(": tokState = TOK_OPEN;
+			case ")":
+				BracketPairs = this.GetPairs( TOK_JOIN( "(", ")" ) );
+				break;
+
+			case "/":
+				if( c[ p - 1 ] == "*" )
+				{
+					cTok = "*/";
+					p --;
+					break;
+				}
+				else if( c[ p + 1 ] == "*" )
+				{
+					cTok = "/*";
+					break;
+				}
+				return new TokenMatch();
+
+			case "*":
+				if( c[ p - 1 ] == "/" )
+				{
+					cTok = "/*";
+					p --;
+					break;
+				}
+				else if( c[ p + 1 ] == "/" )
+				{
+					cTok = "*/";
+					break;
+				}
+				return new TokenMatch();
+
+			default:
+				return new TokenMatch();
+		}
+
+		// Long Switch
+		if( !BracketPairs ) switch( cTok )
+		{
+			case "/*": tokState = TOK_OPEN;
+			case "*/":
+				BracketPairs = this.GetPairs( TOK_JOIN( "/*", "*/" ) );
+				break;
+
+			default:
+				return new TokenMatch();
+		}
+
+		var SetParent = function( pair )
+		{
+			if( !pair ) throw new Error( "Parent not found" );
+
+			var tMatch = new TokenMatch();
+			tMatch.__level = pair[ TOK_LEVEL ];
+			tMatch.__open = pair[ TOK_OPEN ];
+			tMatch.__close = pair[ TOK_CLOSED ];
+
+			if( -1 < pair[ TOK_PARENT ] )
+			{
+				var rPair = BracketPairs.find( pair[ TOK_PARENT ] );
+				tMatch.__parent = SetParent( rPair );
+			}
+
+			return tMatch;
+		};
+
+		var rPair = BracketPairs.find( p, tokState );
+		var tMatch = SetParent( rPair )
+		tMatch.__selected = p;
+
+		return tMatch;
+	};
+
+	Analyzer.prototype.GetPairs = function( def, reload )
+	{
+		if( !reload && this.__tokpairs[ def ] )
+		{
+			return this.__tokpairs[ def ];
+		}
+
+		var c = this.__feeder.content;
+		var pairs = new TokenPairs( def, c );
+
+		this.__tokpairs[ def ] = pairs;
+
+		return pairs;
+	};
+
+	Analyzer.prototype.quoteAt = function( p )
+	{
+		var c = this.__feeder.content;
+		switch( c[ p ] )
+		{
+			case "`":
+			case "\"":
+			case "\'":
+			default:
+				return {
+					level: 0
+					, open: -1
+					, close: -1
+				};
+		}
+	};
+
+	var TokenMatch = function()
+	{
+		this.__open = -1;
+		this.__close = -1;
+		this.__selected = -1;
+		this.__level = -1;
+		this.__parent = null;
+	};
+
+	__readOnly( TokenMatch.prototype, "parent", function() { return this.__parent; } );
+	__readOnly( TokenMatch.prototype, "open", function() { return this.__open; } );
+	__readOnly( TokenMatch.prototype, "close", function() { return this.__close; } );
+	__readOnly( TokenMatch.prototype, "level", function() { return this.__level; } );
+	__readOnly( TokenMatch.prototype, "selected", function() { return this.__selected; } );
+
+	ns[ NS_EXPORT ]( EX_CLASS, "Analyzer", Analyzer );
+	ns[ NS_EXPORT ]( EX_CLASS, "TokenMatch", TokenMatch );
+})();
diff --git a/botanjs/src/Components/Vim/VimArea.js b/botanjs/src/Components/Vim/VimArea.js
index 54c7864..116bd46 100644
--- a/botanjs/src/Components/Vim/VimArea.js
+++ b/botanjs/src/Components/Vim/VimArea.js
@@ -11,7 +11,9 @@
 	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" );
 
 	/** @type {Components.Vim.LineFeeder} */
 	var LineFeeder = ns[ NS_INVOKE ]( "LineFeeder" );
@@ -94,6 +96,7 @@
 
 		// Content feeder
 		var cfeeder = new LineFeeder( cRange, c );
+        var contentAnalyzer = new SyntaxAnalyzer( cfeeder );
 
 		// Feed the contents to content feeder
 		// This "\n" fixes the last line "\n" not displaying
@@ -132,6 +135,7 @@
 		Update();
 
 		this.contentFeeder = cfeeder;
+        this.contentAnalyzer = contentAnalyzer;
 		this.statusFeeder = sfeeder;
 		this.statusBar = statusBar;
 		this.registers = new Registers();
diff --git a/botanjs/src/externs/Components.Vim.Syntax.Analyzer.js b/botanjs/src/externs/Components.Vim.Syntax.Analyzer.js
new file mode 100644
index 0000000..f255903
--- /dev/null
+++ b/botanjs/src/externs/Components.Vim.Syntax.Analyzer.js
@@ -0,0 +1,7 @@
+/** @constructor */
+Components.Vim.Syntax.Analyzer = function(){};
+
+/** @type Function */
+Components.Vim.Syntax.Analyzer.bracketAt;
+/** @type Function */
+Components.Vim.Syntax.Analyzer.quoteAt;
diff --git a/botanjs/src/externs/Components.Vim.Syntax.TokenMatch.js b/botanjs/src/externs/Components.Vim.Syntax.TokenMatch.js
new file mode 100644
index 0000000..916f3cd
--- /dev/null
+++ b/botanjs/src/externs/Components.Vim.Syntax.TokenMatch.js
@@ -0,0 +1,14 @@
+/** @constructor */
+Components.Vim.Syntax.TokenMatch = function(){};
+
+/** @type {Components.Vim.Syntax.TokenMatch} */
+Components.Vim.Syntax.TokenMatch.parent;
+
+/** @type Number */
+Components.Vim.Syntax.TokenMatch.open;
+/** @type Number */
+Components.Vim.Syntax.TokenMatch.close;
+/** @type Number */
+Components.Vim.Syntax.TokenMatch.level;
+/** @type Number */
+Components.Vim.Syntax.TokenMatch.selected;
diff --git a/botanjs/src/externs/Components.Vim.Syntax.js b/botanjs/src/externs/Components.Vim.Syntax.js
new file mode 100644
index 0000000..97fc80c
--- /dev/null
+++ b/botanjs/src/externs/Components.Vim.Syntax.js
@@ -0,0 +1,2 @@
+/** @object */
+Components.Vim.Syntax = {};
diff --git a/botanjs/src/externs/Components.Vim.VimArea.js b/botanjs/src/externs/Components.Vim.VimArea.js
index 982acda..51c9825 100644
--- a/botanjs/src/externs/Components.Vim.VimArea.js
+++ b/botanjs/src/externs/Components.Vim.VimArea.js
@@ -3,6 +3,8 @@ Components.Vim.VimArea = function(){};
 
 /** @type {Components.Vim.LineFeeder} */
 Components.Vim.VimArea.contentFeeder;
+/** @type {Components.Vim.Syntax.Analyzer} */
+Components.Vim.VimArea.contentAnalyzer;
 /** @type {Components.Vim.LineFeeder} */
 Components.Vim.VimArea.statusFeeder;
 /** @type {Components.Vim.StatusBar} */