diff --git a/botanjs/src/Components/Vim/Actions/BUFFERS.js b/botanjs/src/Components/Vim/Actions/BUFFERS.js index e02dc78..9248877 100644 --- a/botanjs/src/Components/Vim/Actions/BUFFERS.js +++ b/botanjs/src/Components/Vim/Actions/BUFFERS.js @@ -40,6 +40,9 @@ var cur = this.__cursor; var Vim = cur.Vim; + + /** @type {Components.Vim.VimArea} */ + var VimArea = shadowImport( "Components.Vim.VimArea" ); var Insts = VimArea.Instances; diff --git a/botanjs/src/Components/Vim/Actions/FIND.js b/botanjs/src/Components/Vim/Actions/FIND.js index 86db51d..461ddc4 100644 --- a/botanjs/src/Components/Vim/Actions/FIND.js +++ b/botanjs/src/Components/Vim/Actions/FIND.js @@ -39,7 +39,7 @@ } // The root bracket as back ref 0 - var RegEx = new RegExp( "(" + parsed + ")", "g" ); + var RegEx = new RegExp( "(" + parsed + ")", "gm" ); return RegEx; }; @@ -104,9 +104,20 @@ var FirstHit; var PrevStack = []; + var LoopGuard; while( ( r = search.exec( content ) ) !== null ) { - if( !FirstHit ) FirstHit = r.index; + if( FirstHit == undefined ) + { + FirstHit = r.index; + } + + if( LoopGuard == r.index ) + { + this.__msg = VimError( "EX2", PATTERN.slice( 1 ).join( "" ) ); + return true; + } + if( p < r.index ) { Hit = r.index; @@ -114,6 +125,7 @@ } PrevStack.push( r.index ); + LoopGuard = r.index; } if( e.kMap( "N" ) ) diff --git a/botanjs/src/Components/Vim/Actions/JOIN_LINES.js b/botanjs/src/Components/Vim/Actions/JOIN_LINES.js new file mode 100644 index 0000000..4f21af6 --- /dev/null +++ b/botanjs/src/Components/Vim/Actions/JOIN_LINES.js @@ -0,0 +1,89 @@ +(function(){ + var ns = __namespace( "Components.Vim.Actions" ); + + /** @type {System.Debug} */ + var debug = __import( "System.Debug" ); + /** @type {Components.Vim.State.Stator} */ + var Stator = __import( "Components.Vim.State.Stator" ); + /** @type {Components.Vim.State.Stack} */ + var Stack = __import( "Components.Vim.State.Stack" ); + + var beep = __import( "Components.Vim.Beep" ); + var Mesg = __import( "Components.Vim.Message" ); + + var occurance = __import( "System.utils.Perf.CountSubstr" ); + + /** @type {Components.Vim.IAction} */ + var JOIN_LINES = function( Cursor ) + { + /** @type {Components.Vim.Cursor} */ + this.__cursor = Cursor; + this.__msg = ""; + Cursor.suppressEvent(); + }; + + JOIN_LINES.prototype.dispose = function() + { + this.__cursor.unsuppressEvent(); + }; + + JOIN_LINES.prototype.handler = function( e, range ) + { + e.preventDefault(); + + var cur = this.__cursor; + var feeder = cur.feeder; + + var start; + var end; + + var stack; + var stator; + + var contentUndo; + if( range ) + { + start = range.start; + end = range.close; + } + else + { + var oPos = cur.aPos; + cur.lineEnd( true ); + stator = new Stator( cur ); + start = cur.aPos; + cur.moveY( 1 ); + cur.lineStart(); + end = cur.aPos; + + // This happens on the last line + if( end < start ) + { + cur.moveTo( oPos ); + beep(); + return true; + } + + var content = feeder.content; + + contentUndo = feeder.content.substring( start, end ); + feeder.content = content.substring( 0, start ) + " " + content.substr( end ); + } + + feeder.pan(); + + cur.moveTo( start ); + + var stack = new Stack(); + stack.store( stator.save( 1, contentUndo ) ); + + cur.rec.record( stack ); + }; + + JOIN_LINES.prototype.getMessage = function() + { + return this.__msg; + }; + + ns[ NS_EXPORT ]( EX_CLASS, "JOIN_LINES", JOIN_LINES ); +})(); diff --git a/botanjs/src/Components/Vim/Actions/TO.js b/botanjs/src/Components/Vim/Actions/TO.js new file mode 100644 index 0000000..1ff0bcd --- /dev/null +++ b/botanjs/src/Components/Vim/Actions/TO.js @@ -0,0 +1,82 @@ +(function(){ + var ns = __namespace( "Components.Vim.Actions" ); + + /** @type {System.Debug} */ + var debug = __import( "System.Debug" ); + + var beep = __import( "Components.Vim.Beep" ); + + /** @type {Components.Vim.IAction} */ + var TO = function( Cursor ) + { + /** @type {Components.Vim.Cursor} */ + this.__cursor = Cursor; + this.__msg = ""; + Cursor.suppressEvent(); + }; + + TO.prototype.dispose = function() + { + this.__cursor.unsuppressEvent(); + }; + + TO.prototype.handler = function( em, et ) + { + et.preventDefault(); + + var cur = this.__cursor; + var f = cur.feeder; + var n = cur.getLine().lineNum; + + var p = 0; + for( i = 0; p != -1 && i < n; i ++ ) + { + p = f.content.indexOf( "\n", p + 1 ); + } + + var upperLimit = f.content.indexOf( "\n", p + 1 ); + + if( 0 < n ) p ++; + + var lowerLimmit = p; + + var cX = cur.X; + var tX = cX; + + var Char = et.key; + if( et.kMap( "Tab" ) ) + { + Char = "\t"; + } + + if( 1 < Char.length ) + { + beep(); + return; + } + + // Forward + if( em.kMap( "t" ) || em.kMap( "f" ) ) + { + tX = f.content.indexOf( Char, p + cX + 1 ); + } + // backward + else + { + tX = f.content.lastIndexOf( Char, p + cX - 1 ); + } + + if( lowerLimmit <= tX && tX < upperLimit ) + { + cur.moveTo( tX ); + } + else beep(); + }; + + TO.prototype.getMessage = function() + { + return this.__msg; + }; + + ns[ NS_EXPORT ]( EX_CLASS, "TO", TO ); +})(); diff --git a/botanjs/src/Components/Vim/Actions/WORD.js b/botanjs/src/Components/Vim/Actions/WORD.js new file mode 100644 index 0000000..7d69be4 --- /dev/null +++ b/botanjs/src/Components/Vim/Actions/WORD.js @@ -0,0 +1,74 @@ +(function(){ + var ns = __namespace( "Components.Vim.Actions" ); + + /** @type {System.Debug} */ + var debug = __import( "System.Debug" ); + + /** @type {Components.Vim.IAction} */ + var WORD = function( Cursor ) + { + /** @type {Components.Vim.Cursor} */ + this.__cursor = Cursor; + this.__msg = ""; + Cursor.suppressEvent(); + }; + + WORD.prototype.dispose = function() + { + this.__cursor.unsuppressEvent(); + }; + + WORD.prototype.handler = function( e ) + { + e.preventDefault(); + + var cur = this.__cursor; + var feeder = cur.feeder; + + var analyzer = cur.Vim.contentAnalyzer; + var p = cur.aPos; + + + var d = 1; + // forward + if( e.kMap( "w" ) || e.kMap( "W" ) ) + { + if( feeder.content[ p + 1 ] == "\n" ) + { + p ++; + } + + var wordRange = analyzer.wordAt( p ); + if( wordRange.open != -1 ) + { + p = wordRange.close + 1; + } + } + // Backward + if( e.kMap( "b" ) || e.kMap( "B" ) ) + { + if( p == 0 ) return; + d = -1; + + var wordRange = analyzer.wordAt( p - 1 ); + if( wordRange.open != -1 ) + { + p = wordRange.open; + } + } + + while( " \t".indexOf( feeder.content[ p ] ) != -1 ) + { + p += d; + } + + cur.moveTo( p ); + }; + + WORD.prototype.getMessage = function() + { + return this.__msg; + }; + + ns[ NS_EXPORT ]( EX_CLASS, "WORD", WORD ); +})(); diff --git a/botanjs/src/Components/Vim/Controls.js b/botanjs/src/Components/Vim/Controls.js index b52b7c2..f38b847 100644 --- a/botanjs/src/Components/Vim/Controls.js +++ b/botanjs/src/Components/Vim/Controls.js @@ -47,6 +47,8 @@ var COMMA = 188; var FULLSTOP = 190; var SLASH = 191; var BACK_SLASH = 220; + var ANY_KEY = -1; + var __maps = {}; var Map = function( str ) { @@ -169,8 +171,9 @@ { var compReg = this.__compositeReg[i]; var keys = compReg.keys; + var key = keys[ compReg.i ++ ]; - if( keys[ compReg.i ++ ] == kCode ) + if( key == ANY_KEY || key == kCode ) { if( compReg.i == keys.length ) { @@ -262,6 +265,7 @@ case SHIFT + I: // Append before the line start, after spaces break; case SHIFT + J: // Join lines + ccur.openRunAction( "JOIN_LINES", e ); break; case SHIFT + K: // Find the manual entry break; @@ -374,7 +378,11 @@ break; case SHIFT + L: // Last line buffer break; - case SHIFT + _6: // ^, Start + + case _0: // Really - line Start + ccur.lineStart(); + break; + case SHIFT + _6: // ^, line Start, XXX: skip tabs ccur.lineStart(); break; case SHIFT + _4: // $, End @@ -404,8 +412,45 @@ ); break; + + + case SHIFT + T: // To case T: // To + this.__cMovement = true; + + this.__composite( e, function( e2 ) { + var oX = ccur.X; + ccur.openRunAction( "TO", e, e2 ); + + if( ccur.X < oX ) + { + ccur.moveX( 1 ); + } + else if( oX < ccur.X ) + { + ccur.moveX( -1 ); + } + }, ANY_KEY ); + break; + case SHIFT + F: // To + case F: // To + this.__cMovement = true; + + this.__composite( e, function( e2 ) { + ccur.openRunAction( "TO", e, e2 ); + }, ANY_KEY ); + + break; + + case W: // word + case SHIFT + W: + case B: + case SHIFT + B: + ccur.openRunAction( "WORD", e ); + break + + case I: // In between boundary if( !ccur.action ) { diff --git a/botanjs/src/Components/Vim/Cursor.js b/botanjs/src/Components/Vim/Cursor.js index 2fd9833..3eaaac0 100644 --- a/botanjs/src/Components/Vim/Cursor.js +++ b/botanjs/src/Components/Vim/Cursor.js @@ -137,7 +137,6 @@ this.moveX( - Number.MAX_VALUE ); this.moveX( jumpX, false, phantomSpace ); - }; // 0 will be treated as default ( 1 ) diff --git a/botanjs/src/Components/Vim/Syntax/Word.js b/botanjs/src/Components/Vim/Syntax/Word.js index d879e59..e76818b 100644 --- a/botanjs/src/Components/Vim/Syntax/Word.js +++ b/botanjs/src/Components/Vim/Syntax/Word.js @@ -199,6 +199,10 @@ // C1 Controls and Latin-1 Supplement (Extended ASCII) [ 0x00A1, 0x00AC ], [ 0x00AE, 0x00BF ] ] + , + [ // Spaces & tabs + [ 0x0020, 0x0020 ], [ 0x0009, 0x0009 ] + ] ]; var NUM_KINGDOM = KINGDOMS.length; diff --git a/botanjs/src/Components/Vim/VimArea.js b/botanjs/src/Components/Vim/VimArea.js index 3614809..1817deb 100644 --- a/botanjs/src/Components/Vim/VimArea.js +++ b/botanjs/src/Components/Vim/VimArea.js @@ -42,16 +42,27 @@ }; /* stage @param {Dandelion.IDOMElement} */ - var VimArea = function( stage ) + var VimArea = function( stage, detectScreenSize ) { - if( !stage ) return; + if( !stage ) throw new Error( "Invalid argument" ); + + stage = IDOMElement( stage ); var element = stage.element; - if( element.nodeName != "TEXTAREA" ) + if(!( element && element.nodeName == "TEXTAREA" )) { - debug.Error( "Element is not compatible for VimArea" ); - return; + throw new Error( "This element is not compatible for VimArea" ); + } + + for( var i in Insts ) + { + var inst = Insts[ i ]; + if( inst.stage.element == element ) + { + debug.Info( "Instance exists" ); + return inst; + } } stage.setAttribute( new DataKey( "vimarea", 1 ) ); @@ -69,10 +80,16 @@ , new EventKey( "Blur", function() { _self.__active = false; } ) ]; - this.__removeText - // Init - this.VisualizeVimFrame( element.value ); + if( detectScreenSize ) + { + var val = element.value; + this.__testScreen(function() { _self.VisualizeVimFrame( val ); }); + } + else + { + this.VisualizeVimFrame( element.value ); + } // Set buffer index this.__instIndex = InstIndex ++; @@ -81,6 +98,68 @@ Insts[ this.__instIndex ] = this; }; + VimArea.prototype.__testScreen = function( handler ) + { + var area = this.stage.element; + area.value = ""; + + var msg = "Please wait while Vim;Re is testing for screen dimensions"; + var m = function() { return msg[ i ++ ] || "."; }; + + var i = 0; + + var oX = area.style.overflowX; + var oY = area.style.overflowY; + + area.style.whiteSpace = "nowrap"; + + var oWidth = area.scrollWidth; + var testWidth = function() + { + area.value += m(); + if( oWidth == area.scrollWidth ) + { + Cycle.next( testWidth ); + } + else + { + var t = ""; + i -= 3; + for( var k = 0; k < i; k ++ ) t += "."; + area.value = t; + + area.style.whiteSpace = ""; + m = function() { return "\n" + t; }; + testHeight(); + } + }; + + testWidth(); + + var oHeight = area.scrollHeight; + + var l = 0; + + var _self = this; + + var testHeight = function() { + area.value += m(); + l ++; + + if( oHeight == area.scrollHeight ) + { + Cycle.next( testHeight ); + } + else + { + _self.rows = l; + _self.cols = i; + + handler(); + } + }; + }; + VimArea.prototype.select = function( sel ) { if( !this.__active ) return; diff --git a/botanjs/src/Components/Vim/_this.js b/botanjs/src/Components/Vim/_this.js index d55ec63..ab26706 100644 --- a/botanjs/src/Components/Vim/_this.js +++ b/botanjs/src/Components/Vim/_this.js @@ -38,6 +38,7 @@ VIMRE_VERSION = "1.0.0b"; // EXtended Errors , "EX1": "Pattern Error( %1 )" + , "EX2": "This pattern is causing infinite loop: %1" , "TODO": "%1 is not implemented yet" , "MISSING_FEATURE": "Sorry, I thought this command wasn't useful enough to implement. Please file a feature request titled \"Implement %1\" in github if you think this is important." diff --git a/botanjs/src/System/Cycle/_this.js b/botanjs/src/System/Cycle/_this.js index f9431a1..f31bb5c 100644 --- a/botanjs/src/System/Cycle/_this.js +++ b/botanjs/src/System/Cycle/_this.js @@ -10,6 +10,12 @@ var tList = []; + var C_CALLBACK = 0; + var C_TIME = 1; + var C_ONCE = 2; + var C_ID = 3; + var C_INTVL = 4; + var stepper = function() { var thisTime = new Date().getTime(); @@ -21,11 +27,11 @@ for ( var i in tList ) { var f = tList[i]; - if( f && thisTime > f[1] ) + if( f && thisTime > f[ C_TIME ] ) { try { - f[0](); + f[ C_CALLBACK ](); } catch(e) { @@ -34,13 +40,13 @@ continue; } - if( f[2] ) + if( f[ C_ONCE ] ) { delete tList[i]; } else { - f[1] = thisTime + f[4]; + f[ C_TIME ] = thisTime + f[ C_INTVL ]; } } } @@ -56,7 +62,7 @@ { for ( var i in tList ) { - if( tList[i][3] == id ) + if( tList[i][ C_ID ] == id ) return false; } @@ -68,14 +74,19 @@ // 3: id for ( var i in tList ) { - if( tList[i][3] == id ) + if( tList[i][ C_ID ] == id ) delete tList[i]; } }; var next = function( func ) { - tList[ tList.length ] = [ func, 0, true ]; + var a = []; + a[ C_CALLBACK ] = func; + a[ C_TIME ] = 0; + a[ C_ONCE ] = true; + + tList[ tList.length ] = a; }; var ourTick = new Tick();