commit 799a911e06355140a78cd1a0240715a863e21d81
| author | 斟酌 鵬兄 <tgckpg@gmail.com> |
| date | 2016-03-29T20:32:36Z |
| subject | Partial % 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} */