diff --git a/botanjs/service/jclassresv.py b/botanjs/service/jclassresv.py index 33d53e7..acd30ea 100644 --- a/botanjs/service/jclassresv.py +++ b/botanjs/service/jclassresv.py @@ -204,7 +204,7 @@ class BotanClassResolver: return cFHash if self.returnHash else self.BotanCache( cFile ) elif self.useCache( oFile, dates ): - self.JWork.saveCache.delay( + self.JWork.saveCache( oFile # Content is None to initiate a compression , None @@ -253,7 +253,7 @@ class BotanClassResolver: outputJs = wrapScope( outputJs ) - [ self.JWork.saveCache if self.returnHash else self.JWork.saveCache.delay ][0] ( + [ self.JWork.saveCache if self.returnHash else self.JWork.saveCache ][0] ( os.path.join( self.CR, md5[0] ) , outputJs , "js" @@ -288,7 +288,7 @@ class BotanClassResolver: for f in self.cleanList( cList ): outputCss += self.BotanFile( f ) - [ self.JWork.saveCache if self.returnHash else self.JWork.saveCache.delay ][0] ( + [ self.JWork.saveCache if self.returnHash else self.JWork.saveCache ][0] ( os.path.join( self.CR, md5[0] ), outputCss, "css" ) diff --git a/botanjs/service/jwork.py b/botanjs/service/jwork.py index 97e4fc9..1a3da0f 100644 --- a/botanjs/service/jwork.py +++ b/botanjs/service/jwork.py @@ -25,7 +25,6 @@ else: class JWork: - @app.task() def saveCache( location, content = None, mode = None, externs = "" ): if content != None: log.info( "Writing file(" + str( len( content ) ) + "): " + os.path.abspath( location ) ) diff --git a/botanjs/src/Components/Vim/Actions/DELETE.js b/botanjs/src/Components/Vim/Actions/DELETE.js index a5a7f6c..6f1d33d 100644 --- a/botanjs/src/Components/Vim/Actions/DELETE.js +++ b/botanjs/src/Components/Vim/Actions/DELETE.js @@ -15,7 +15,7 @@ var occurence = __import( "System.utils.Perf.CountSubstr" ); /** @type {Components.Vim.IAction} */ - var DELETE = function( Cursor ) + var DELETE = function( Cursor, e ) { /** @type {Components.Vim.Cursor} */ this.__cursor = Cursor; @@ -23,6 +23,10 @@ this.__startX = Cursor.aPos; this.__panY = this.__cursor.feeder.panY; + this.__cMode = e.kMap( "c" ); + this.__cMode_c = false; + this.__enterEvent = e; + Cursor.suppressEvent(); }; @@ -30,7 +34,26 @@ DELETE.prototype.dispose = function() { - this.__cursor.unsuppressEvent(); + var cur = this.__cursor; + cur.unsuppressEvent(); + + if( this.__cMode ) + { + if( this.__cMode_c ) // Append, a + { + cur.fixTab(); + cur.moveX( 1, false, true, true ); + } + else // Insert, i + { + cur.moveX( -1, true ); + cur.moveX( 1, true, true, true ); + } + + setTimeout( function() { + cur.openAction( "INSERT", this.__enterEvent ); + }, 20 ); + } }; DELETE.prototype.handler = function( e, sp, newLine ) @@ -117,6 +140,13 @@ sp = cur.aPos; cur.lineStart(); } + else if( this.__cMode && e.kMap( "c" ) ) + { + cur.lineEnd(); + sp = cur.aPos; + cur.lineStart( true ); + this.__cMode_c = true; + } else if( e.range ) { sp = e.range.close; @@ -153,6 +183,9 @@ e = sp; } + // For removing the very last line + if( c[ sp ] == undefined ) s --; + var removed = c.substring( s, e + 1 ); reg.change( removed, newLine ); diff --git a/botanjs/src/Components/Vim/Actions/EDITOR_COMMAND.js b/botanjs/src/Components/Vim/Actions/EDITOR_COMMAND.js index d4de6dd..7b2a6f1 100644 --- a/botanjs/src/Components/Vim/Actions/EDITOR_COMMAND.js +++ b/botanjs/src/Components/Vim/Actions/EDITOR_COMMAND.js @@ -87,6 +87,9 @@ case "help": out[ CMD_TYPE ] = "HELP"; break; + case "varec": + out[ CMD_TYPE ] = "VA_REC"; + break; } if( range !== "" ) diff --git a/botanjs/src/Components/Vim/Actions/INSERT.js b/botanjs/src/Components/Vim/Actions/INSERT.js index 4beaca5..be97b56 100644 --- a/botanjs/src/Components/Vim/Actions/INSERT.js +++ b/botanjs/src/Components/Vim/Actions/INSERT.js @@ -60,7 +60,8 @@ } this.__msg = ""; this.__rec( "", true ); - this.__cursor.moveX( -1 ); + this.__cursor.moveX( -1, false, false, true ); + this.__cursor.fixTab(); }; INSERT.prototype.__rec = function( c, newRec ) @@ -129,14 +130,16 @@ this.__contentUndo = feeder.content.substr( f, 1 ) + this.__contentUndo; } + cur.moveX( + feeder.content[f] == "\t" ? -feeder.firstBuffer.tabWidth : -1 + , true, true, true ); + feeder.content = feeder.content.substring( 0, f ) + feeder.content.substring( f + 1 ); feeder.pan(); - cur.moveX( -1, true, true ); - if( 0 < this.__insertLen ) this.__insertLen --; this.__punch --; } @@ -197,7 +200,7 @@ { this.__realizeIndent(); feeder.pan(); - cur.moveX( inputChar == "\t" ? feeder.firstBuffer.tabWidth : 1, false, true ); + cur.moveX( inputChar == "\t" ? feeder.firstBuffer.tabWidth : 1, false, true, true ); } feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); @@ -262,7 +265,7 @@ feeder.softReset(); feeder.pan(); - cur.moveX( i * feeder.firstBuffer.tabWidth, false, true ); + cur.moveX( i, false, true ); var a = []; a[ IN_START ] = f; diff --git a/botanjs/src/Components/Vim/Actions/SHIFT_LINES.js b/botanjs/src/Components/Vim/Actions/SHIFT_LINES.js index 0e83a39..606e17d 100644 --- a/botanjs/src/Components/Vim/Actions/SHIFT_LINES.js +++ b/botanjs/src/Components/Vim/Actions/SHIFT_LINES.js @@ -345,7 +345,7 @@ f(); // Offset correction after REDO / UNDO cur.moveTo( recStart ); - cur.lineStart(); + cur.lineStart( true ); } ); cur.moveTo( recStart ); diff --git a/botanjs/src/Components/Vim/Actions/VA_REC.js b/botanjs/src/Components/Vim/Actions/VA_REC.js new file mode 100644 index 0000000..7685eb3 --- /dev/null +++ b/botanjs/src/Components/Vim/Actions/VA_REC.js @@ -0,0 +1,116 @@ +(function(){ + var ns = __namespace( "Components.Vim.Actions" ); + + /** @type {System.Debug} */ + var debug = __import( "System.Debug" ); + /** @type {System.utils.EventKey} */ + var EventKey = __import( "System.utils.EventKey" ); + /** @type {Components.Vim.ActionEvent} */ + var ActionEvent = __import( "Components.Vim.ActionEvent" ); + + var Mesg = __import( "Components.Vim.Message" ); + + // Recording Sessions + var Sessions = []; + + /** @type {Components.Vim.IAction} */ + var VA_REC = function( Cursor ) + { + /** @type {Components.Vim.Cursor} */ + this.__cursor = Cursor; + this.__msg = Mesg( "VA_REC_START" ); + Cursor.suppressEvent(); + }; + + VA_REC.prototype.dispose = function() + { + this.__cursor.unsuppressEvent(); + }; + + VA_REC.prototype.handler = function( e, args, range ) + { + if( args == true ) + { + var msg = Mesg( "VA_REC_END" ); + var lastLine = Mesg( "WAIT_FOR_INPUT" ); + + var l = this.__cursor.feeder.firstBuffer.cols; + for( var i = msg.length; i < l; i ++ ) msg += " "; + + this.__msg = msg + "\n" + lastLine; + return; + } + + e.preventDefault(); + var inst = this.__cursor.Vim; + var sender = inst.stage; + var sIndex = inst.index; + + var session; + + if( Sessions[ sIndex ] ) + { + session = Sessions[ sIndex ]; + } + else + { + session = Sessions[ sIndex ] = {}; + } + + if( session.started ) + { + session.__dispose(); + var head = "Press Escape to conitnue edit\n===\n"; + var data = JSON.stringify( session.data ); + + var element = sender.element; + setTimeout( function() { + inst.display( + head + data, function(){ + element.selectionStart = head.length; + element.selectionEnd = element.selectionStart + data.length; + } ); + }, 1 ); + return; + } + else + { + session.started = true; + } + + var sessData = session.data = []; + var lastTime = Date.now(); + + session.__event = new EventKey( + "KeyDown", function( e2 ) + { + var evt = new ActionEvent( sender, e2 ); + if( [ "Control", "Alt", "Shift" ].indexOf( e2.key ) != -1 ) return; + + var now = Date.now(); + sessData.push( now - lastTime, evt.keyCode ); + lastTime = now; + } + ); + + var feeder = this.__cursor.feeder; + + // Handles quit event on VimArea + session.__dispose = function() { + debug.Info( "VA_REC: Disposing active session" ); + delete Sessions[ sIndex ]; + sender.removeEventListener( session.__event ); + inst.removeEventListener( "Dispose", session.__dispose ); + }; + + inst.addEventListener( "Dispose", session.__dispose ); + sender.addEventListener( session.__event ); + }; + + VA_REC.prototype.getMessage = function() + { + return this.__msg; + }; + + ns[ NS_EXPORT ]( EX_CLASS, "VA_REC", VA_REC ); +})(); diff --git a/botanjs/src/Components/Vim/Actions/VISUAL.js b/botanjs/src/Components/Vim/Actions/VISUAL.js index 55aa192..3bdb8d3 100644 --- a/botanjs/src/Components/Vim/Actions/VISUAL.js +++ b/botanjs/src/Components/Vim/Actions/VISUAL.js @@ -220,13 +220,13 @@ if( cur.aPos == startLine.aPos ) { - cur.moveTo( r.open, true ); + cur.moveTo( r.open, true, false, true ); this.__reset( cur ); startLine = this.__startLine; } cur.unsuppressEvent(); - cur.moveTo( r.close, true ); + cur.moveTo( r.close, true, false, true ); } var currAp = cur.aPos; diff --git a/botanjs/src/Components/Vim/Actions/WORD.js b/botanjs/src/Components/Vim/Actions/WORD.js index 7d69be4..7bd3b03 100644 --- a/botanjs/src/Components/Vim/Actions/WORD.js +++ b/botanjs/src/Components/Vim/Actions/WORD.js @@ -3,6 +3,7 @@ /** @type {System.Debug} */ var debug = __import( "System.Debug" ); + var beep = __import( "Components.Vim.Beep" ); /** @type {Components.Vim.IAction} */ var WORD = function( Cursor ) @@ -29,37 +30,87 @@ var p = cur.aPos; - var d = 1; - // forward + // Forword WORD start if( e.kMap( "w" ) || e.kMap( "W" ) ) { - if( feeder.content[ p + 1 ] == "\n" ) + // +2 because there is a closing "\n" + if( feeder.content[ p + 2 ] == undefined ) { - p ++; + beep(); + return; } var wordRange = analyzer.wordAt( p ); if( wordRange.open != -1 ) { p = wordRange.close + 1; + + while( " \t\n".indexOf( feeder.content[ p ] ) != -1 ) p ++; + + if( feeder.content[ p ] == undefined ) + { + // This is the last character + p --; + } } } - // Backward + + // Forward WORD end + if( e.kMap( "e" ) || e.kMap( "E" ) ) + { + if( feeder.content[ p + 2 ] == undefined ) + { + beep(); + return; + } + + p ++; + while( " \t\n".indexOf( feeder.content[ p ] ) != -1 ) p ++; + + // This is the last character + if( feeder.content[ p ] == undefined ) + { + p --; + } + else + { + var wordRange = analyzer.wordAt( p ); + + if( wordRange.open != -1 ) + { + p = wordRange.close; + } + } + } + + // Backward WORD start if( e.kMap( "b" ) || e.kMap( "B" ) ) { - if( p == 0 ) return; - d = -1; - - var wordRange = analyzer.wordAt( p - 1 ); - if( wordRange.open != -1 ) + if( p == 0 ) { - p = wordRange.open; + beep(); + return; } - } - while( " \t".indexOf( feeder.content[ p ] ) != -1 ) - { - p += d; + p --; + while( " \t".indexOf( feeder.content[ p ] ) != -1 ) p --; + + // No more results + if( p == -1 ) + { + p = 0; + } + else + { + var wordRange = analyzer.wordAt( p ); + if( wordRange.open != -1 ) + { + p = wordRange.open; + } + + // If the very first char is " " or "\t" + if( " \t".indexOf( feeder.content[ p ] ) != -1 ) p ++; + } } cur.moveTo( p ); diff --git a/botanjs/src/Components/Vim/Controls.js b/botanjs/src/Components/Vim/Controls.js index cdb320c..5f4e4d7 100644 --- a/botanjs/src/Components/Vim/Controls.js +++ b/botanjs/src/Components/Vim/Controls.js @@ -90,6 +90,7 @@ case "Del": kCode = Mod + DELETE; break; case "Enter": kCode = Mod + ENTER; break; case "Tab": kCode = Mod + TAB; break; + case "Escape": kCode = Mod + ESC; break; case "Up": kCode = Mod + UP; break; case "Down": kCode = Mod + DOWN; break; @@ -148,7 +149,7 @@ { switch( kCode ) { - case SPACE: return " "; + case SPACE: case SHIFT + SPACE: return " "; case A: return "a"; case B: return "b"; case C: return "c"; case D: return "d"; case E: return "e"; case F: return "f"; case G: return "g"; case H: return "h"; case I: return "i"; case J: return "j"; case K: return "k"; case L: return "l"; @@ -188,7 +189,8 @@ case SHIFT + S: return "S"; case SHIFT + T: return "T"; case SHIFT + U: return "U"; case SHIFT + V: return "V"; case SHIFT + W: return "W"; case SHIFT + X: return "X"; case SHIFT + Y: return "Y"; case SHIFT + Z: return "Z"; - case ESC: return "Escape"; case BACKSPACE: return "Backspace"; case DELETE: return "Delete"; + case SHIFT + BACKSPACE: case BACKSPACE: return "Backspace"; + case ESC: return "Escape"; case DELETE: return "Delete"; case SHIFT: return "Shift"; case ALT: return "Alt"; case CTRL: return "Control"; case ENTER: return "Enter"; case TAB: return "Tab"; } @@ -213,9 +215,10 @@ Controls.prototype.__composite = function( e, handler ) { + if( !this.__compositeReg ) this.__compositeReg = []; + if( handler ) { - if( !this.__compositeReg ) this.__compositeReg = []; this.__compositeReg.push({ keys: Array.prototype.slice.call( arguments, 2 ) , handler: handler @@ -259,9 +262,24 @@ { case SHIFT + A: // Append at the line end ccur.lineEnd(); - case A: // Append - ccur.moveX( 1, true, true ); + ccur.moveX( 1, false, true, true ); + ccur.openAction( "INSERT", e ); + break; case I: // Insert + if( 0 < ccur.X ) + { + ccur.moveX( -1, true ); + ccur.moveX( 1, true, true, true ); + } + ccur.openAction( "INSERT", e ); + break; + case A: // Append + ccur.fixTab(); + ccur.moveX( 1, false, true, true ); + ccur.openAction( "INSERT", e ); + break; + case SHIFT + I: // Append at line start + ccur.lineStart( true ); ccur.openAction( "INSERT", e ); break; @@ -293,6 +311,7 @@ break; case D: // Del with motion + case C: // Then insert ccur.openAction( "DELETE", e ); break; case Y: // Yank with motion @@ -469,7 +488,6 @@ } var ccur = this.__ccur; - var vima = this.__vimArea; var cfeeder = ccur.feeder; var cursorHandled = true; @@ -490,7 +508,6 @@ break; } - var oPan = cfeeder.panY; cfeeder.pan( undefined, cfeeder.moreAt ); cfeeder.softReset(); @@ -509,17 +526,60 @@ ccur.moveY( -ccur.Y ); if( !cfeeder.EOF ) ccur.moveY( cfeeder.moreAt ); break; + case CTRL + E: // Pan Y, Scroll Down + cfeeder.pan( undefined, 1 ); + cfeeder.softReset(); + + ccur.moveY( 0 < ccur.Y ? -1 : 0 ); + ccur.moveX(); + break; + case CTRL + Y: // Pan Y, Scroll Up + if( cfeeder.panY == 0 ) + { + beep(); + break; + } + + cfeeder.pan( undefined, -1 ); + cfeeder.softReset(); + + ccur.moveY( ccur.Y == cfeeder.moreAt ? 0 : 1 ); + ccur.moveX(); + break; + case CTRL + D: // Page Down, keep cursor.Y + cfeeder.pan( undefined, cfeeder.moreAt ); + cfeeder.softReset(); + ccur.moveY( 0 ); + break; + case CTRL + U: // Page Up, keep cursor.Y + if( cfeeder.panY == 0 ) + { + beep(); + break; + } + cfeeder.pan( undefined, -cfeeder.moreAt ); + cfeeder.softReset(); + ccur.moveY( 0 ); + break; case SHIFT + H: // First line buffer + ccur.moveY( -ccur.Y ); + ccur.lineStart( true ); + break; + case SHIFT + M: // Middle line buffer + ccur.moveY( Math.floor( 0.5 * cfeeder.moreAt ) - ccur.Y ); + ccur.lineStart( true ); break; case SHIFT + L: // Last line buffer + ccur.moveY( cfeeder.moreAt - ccur.Y ); + ccur.lineStart( true ); break; - case _0: // Really - line Start + case _0: // Really line Start ccur.lineStart(); break; - case SHIFT + _6: // ^, line Start, XXX: skip tabs - ccur.lineStart(); + case SHIFT + _6: // ^, line Start at word + ccur.lineStart( true ); break; case SHIFT + _4: // $, End ccur.lineEnd( ccur.pSpace ); @@ -583,6 +643,8 @@ case SHIFT + W: case B: case SHIFT + B: + case E: + case SHIFT + E: ccur.openRunAction( "WORD", e ); break @@ -648,6 +710,46 @@ ccur.moveX( -Number.MAX_VALUE, true ); }, G ); + // Wordwrap next display line + this.__composite( e, function() { + var dispLine = ccur.getLine( true ); + ccur.moveX( dispLine.content.length + 1 ); + }, J ); + + // Wordwrap prev display line + this.__composite( e, function() { + var dispLine = ccur.getLine( true ).prev; + var thisLine = ccur.getLine().lineNum; + + if( !dispLine ) + { + ccur.lineStart(); + beep(); + return; + } + + if( dispLine.content != "" && dispLine.lineNum == thisLine ) + { + ccur.moveX( -( dispLine.content.length + 1 ) ); + } + else + { + ccur.moveY( -1 ); + var lines = ccur.getLine().visualLines; + if( 1 < lines.length ) + { + var l = lines.length - 1; + var j = 0; + for( var i = 0; i < l; i ++ ) + { + j += lines[i].content.length; + } + j += Math.max( 0, Math.floor( j / dispLine.cols ) - 1 ); + ccur.moveX( j ); + } + } + }, K ); + // Print Hex this.__composite( e, function() { ccur.openRunAction( "PRINT_HEX", e ); @@ -792,6 +894,13 @@ this.__kCode = Map( e ); this.__escape = this.__kCode == ESC; } + else if( typeof( e ) == "number" ) + { + this.__key = RMap( e ); + this.__modKeys = 0; + this.__kCode = e; + this.__escape = this.__kCode == ESC || this.__kCode == ( CTRL + C ) || this.__key == ( CTRL + S_BRACKET_L ); + } else { this.__e = e; @@ -804,7 +913,7 @@ var c = this.__e.keyCode; - this.__escape = c == ESC || ( e.ctrlKey && c == C ); + this.__escape = c == ESC || ( e.ctrlKey && ( c == C || c == S_BRACKET_L ) ); this.__kCode = c + ( e.shiftKey || e.getModifierState( "CapsLock" ) ? SHIFT : 0 ) + ( e.ctrlKey ? CTRL : 0 ) diff --git a/botanjs/src/Components/Vim/Cursor.js b/botanjs/src/Components/Vim/Cursor.js index 810bf70..18e327b 100644 --- a/botanjs/src/Components/Vim/Cursor.js +++ b/botanjs/src/Components/Vim/Cursor.js @@ -103,7 +103,7 @@ Cursor.prototype.Vim; // Move to an absolute position - Cursor.prototype.moveTo = function( aPos, phantomSpace ) + Cursor.prototype.moveTo = function( aPos, phantomSpace, skipTabs ) { var content = this.feeder.content; var pline = this.getLine(); @@ -132,20 +132,26 @@ var jumpY = expLineNum - lastLineNum; var jumpX = aPos < lineStart ? lineStart - aPos : aPos - lineStart; - jumpX += Math.ceil( jumpX / pline.cols ) - 1; - jumpX += occurence( content.substring( lineStart + 1, aPos ), "\t" ) * ( pline.tabWidth - 1 ); + var kX = jumpX - pline.content.length; + while( 0 < kX ) + { + jumpX ++; + pline = pline.next + if(!( pline && pline.lineNum == expLineNum )) break; + kX -= pline.content.length; + } if( jumpY ) this.moveY( jumpY ); // This is needed because first line does not contain the first "\n" character if( 0 < this.getLine().lineNum && lineStart <= aPos ) jumpX --; - this.moveX( - Number.MAX_VALUE ); - this.moveX( jumpX, false, phantomSpace ); + this.moveX( - Number.MAX_VALUE, false, false, true ); + this.moveX( jumpX, false, phantomSpace, skipTabs ); }; // 0 will be treated as default ( 1 ) - Cursor.prototype.moveX = function( d, penetrate, phantomSpace ) + Cursor.prototype.moveX = function( d, penetrate, phantomSpace, skipTab ) { var x = this.pX; var updatePx = Boolean( d ); @@ -177,6 +183,8 @@ /** @type {Components.Vim.LineBuffer} */ var line = this.getLine(); + var tabStep = line.tabWidth - 1; + var rline = this.rawLine; var content = line.visualLines.join( "\n" ); var cLen = content.length; @@ -190,8 +198,7 @@ { // Begin check if this line contains phantomSpace // hasPhantomSpace = 0 < ( rawLine.displayLength ) % cols - var rline = this.rawLine; - hasPhantomSpace = 0 < ( rline.length + occurence( rline, "\t" ) * ( line.tabWidth - 1 ) ) % line.cols; + hasPhantomSpace = 0 < ( rline.length + occurence( rline, "\t" ) * tabStep ) % line.cols; if( hasPhantomSpace ) { @@ -203,6 +210,85 @@ } } + // Hacky tab compensations + if( skipTab ) + { + // Handles INSERT on first tab char + if( penetrate && 0 < d ) + { + if( ( content.length - 1 ) <= x ) + { + this.moveY( 1 ); + this.X = 0; + this.updatePosition(); + return; + } + } + } + else + { + // Handles INSERT on first tab char + if( penetrate ) + { + if( line.content[0] == "\t" && x < tabStep ) + { + this.moveY( -1 ); + this.lineEnd( phantomSpace ); + return; + } + } + + var s = this.aX; + var a = rline[ s + d ]; + var e = s; + if( d < 0 ) + { + if( rline[ s ] == "\t" ) + { + x -= tabStep; + if( x < 0 ) + x = tabStep; + } + + s += d; + + var ntabs = occurence( rline.substring( s, e ), "\t" ) - 1; + if( 0 < ntabs ) x -= ntabs * tabStep; + } + else if( updatePx ) // && 0 < d ( assuming d can never be 0 ) + { + // Going from one line to next + // linebreaks are *invisible* + var isLF = ( content[ x ] == "\n" ) ? 1 : 0; + + if( s == 0 ) + { + x = d; + if ( rline[ 0 ] == "\t" ) + { + x += tabStep; + } + } + + e += d; + + var ntabs = occurence( rline.substring( s + 1, e + 1 ), "\t" ); + if( 1 < ntabs && rline[ e ] == "\t" ) ntabs --; + x += ntabs * tabStep + isLF; + + // Reset the distance to 1 as x is now calculated + d = 1; + } + else // jk, non-X navigation. i.e., pX does not change + { + // s = 0, which is unused here + e = x + d; + x += ( occurence( rline.substring( 0, e ), "\t" ) ) * tabStep; + x += Math.floor( x / line.cols ); + if( 1 < d ) x += d - 1; + } + } + var c = content[ x ]; // Whether x is at line boundary @@ -211,6 +297,13 @@ if( boundary ) { x = 0 < x ? lineEnd : 0; + + // This happens on backspacing max filled lines on INSERT mode + if( d < 0 && 0 < x ) + { + boundary = false; + x += d; + } } else if( c == "\n" ) { @@ -227,22 +320,38 @@ if( updatePx ) { - this.pX = x; + this.pX = this.aX; this.updatePosition(); } }; - Cursor.prototype.lineStart = function() + // fix the tab position + Cursor.prototype.fixTab = function() { - this.pX = 0; + this.moveX( 1, false, true ); + this.moveX( -1 ); + }; + + Cursor.prototype.lineStart = function( atWord ) + { + if( atWord ) + { + var a = this.rawLine.match( /^[ \t]+/g ); + this.pX = a ? a[0].length : 0; + } + else + { + this.pX = 0; + } + this.moveX(); this.updatePosition(); }; Cursor.prototype.lineEnd = function( phantomSpace ) { - this.moveX( Number.MAX_VALUE, false, phantomSpace ); + this.moveX( Number.MAX_VALUE, false, phantomSpace, true ); }; Cursor.prototype.updatePosition = function() @@ -414,7 +523,7 @@ Cursor.prototype.suppressEvent = function() { ++ this.__suppEvt; }; Cursor.prototype.unsuppressEvent = function() { -- this.__suppEvt; }; - Cursor.prototype.getLine = function( raw ) + Cursor.prototype.getLine = function( display ) { var feeder = this.feeder; var line = feeder.firstBuffer; @@ -422,7 +531,21 @@ for( var i = 0; line != eBuffer; line = line.next ) { if( line.br ) i ++; - if( this.Y == i ) return line; + if( this.Y == i ) + { + // Return the display line + if( display ) + { + var x = this.aX + 1; + while( 0 < ( x -= line.content.length ) ) + { + if( !line.next ) return line; + line = line.next; + } + } + + return line; + } } return null; diff --git a/botanjs/src/Components/Vim/VimArea.js b/botanjs/src/Components/Vim/VimArea.js index 433be5a..a6c825d 100644 --- a/botanjs/src/Components/Vim/VimArea.js +++ b/botanjs/src/Components/Vim/VimArea.js @@ -24,7 +24,7 @@ var VimControls = ns[ NS_INVOKE ]( "Controls" ); var ActionEvent = ns[ NS_INVOKE ]( "ActionEvent" ); - var mesg = ns[ NS_INVOKE ]( "Message" ); + var Mesg = ns[ NS_INVOKE ]( "Message" ); var Insts = []; var InstIndex = 0; @@ -47,6 +47,8 @@ { if( !stage ) throw new Error( "Invalid argument" ); + EventDispatcher.call( this ); + stage = IDOMElement( stage ); var element = stage.element; @@ -85,11 +87,11 @@ if( detectScreenSize ) { var val = element.value; - this.__testScreen(function() { _self.VisualizeVimFrame( val ); }); + this.__testScreen(function() { _self.__visualize( val ); }); } else { - this.VisualizeVimFrame( element.value ); + this.__visualize( element.value ); } // Set buffer index @@ -99,6 +101,8 @@ Insts[ this.__instIndex ] = this; }; + __extends( VimArea, EventDispatcher ); + VimArea.prototype.__testScreen = function( handler ) { var area = this.stage.element; @@ -173,7 +177,8 @@ } }; - VimArea.prototype.VisualizeVimFrame = function( content ) + // Visualize the Vim Frame + VimArea.prototype.__visualize = function( content ) { var _self = this; this.content = content; @@ -205,7 +210,7 @@ // Set the stamps var statusBar = new StatusBar( c ); statusBar.stamp( -18, function(){ return cfeeder.lineStat; } ); - statusBar.stamp( -3, function(){ return mesg( cfeeder.docPos ); } ); + statusBar.stamp( -3, function(){ return Mesg( cfeeder.docPos ); } ); statusBar.stamp( 0, function(){ return cfeeder.cursor.message; } ); sfeeder.init( statusBar.statusText ); @@ -259,6 +264,95 @@ ); this.stage.addEventListeners( this.__stagedEvents ); + this.dispatchEvent( new BotanEvent( "Visualized" ) ); + }; + + VimArea.prototype.display = function( data, handler ) + { + var _self = this; + var stage = this.stage; + var cursor = this.__cursor; + + var evts = this.__stagedEvents; + for( var i in evts ) stage.removeEventListener( evts[ i ] ); + cursor.suppressEvent(); + this.__active = false; + + stage.removeAttribute( "data-vimarea" ); + setTimeout( function() { + stage.element.value = data; + if( handler ) handler(); + }, 100 ); + + var ContinueEdit = new EventKey( "KeyDown", function( e ) { + var evt = new ActionEvent( _self, e ); + if( evt.kMap( "Escape" ) ) + { + stage.removeEventListener( ContinueEdit ); + stage.setAttribute( new DataKey( "vimarea", 1 ) ); + stage.addEventListeners( _self.__stagedEvents ); + cursor.unsuppressEvent(); + cursor.feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); + _self.__active = true; + stage.element.focus(); + } + } ); + + stage.addEventListener( ContinueEdit ); + }; + + VimArea.prototype.demo = function( seq ) + { + if( this.__demoActive ) return; + + var _self = this; + + this.__demoActive = true; + var l = seq.length; + + var s = 0; + + var controls = new VimControls( this ); + var cursor = this.__cursor; + var statusBar = this.statusBar; + + var demoEnd = function() + { + statusBar.stamp( 1, false ); + controls.handler( _self, new ActionEvent( _self, "Escape" ) ); + setTimeout( function() { + cursor.openRunAction( "VA_REC", undefined, true ); + _self.__demoActive = false; + _self.stage.addEventListeners( _self.__stagedEvents ); + }, 100 ); + }; + + var demoChain = function() + { + _self.stage.element.focus(); + + var key = seq[ s + 1 ]; + controls.handler( _self, new ActionEvent( _self, key ) ); + s += 2; + + if( s < l ) + { + // Wait time cannot be 0 + setTimeout( demoChain, seq[ s ] || 20 ); + } + else + { + setTimeout( demoEnd, 100 ); + } + }; + + statusBar.stamp( 1, function(){ return Mesg( "VA_REC_REPLAY" ); } ); + + var evts = this.__stagedEvents; + for( var i in evts ) this.stage.removeEventListener( evts[ i ] ); + this.__active = true; + + setTimeout( demoChain, seq[ s ] ); }; VimArea.prototype.dispose = function() @@ -272,6 +366,7 @@ debug.Info( "Destroy instance: " + id ); feeder.dispatcher.removeEventListener( "VisualUpdate", this.__visualUpdate ); + this.dispatchEvent( new BotanEvent( "Dispose" ) ); stage.removeAttribute( "data-vimarea" ); diff --git a/botanjs/src/Components/Vim/_this.js b/botanjs/src/Components/Vim/_this.js index bfa1b4b..b221d18 100644 --- a/botanjs/src/Components/Vim/_this.js +++ b/botanjs/src/Components/Vim/_this.js @@ -1,4 +1,4 @@ -VIMRE_VERSION = "1.0.0b"; +var VIMRE_VERSION = "1.0.0"; (function(){ var ns = __namespace( "Components.Vim" ); @@ -6,7 +6,7 @@ VIMRE_VERSION = "1.0.0b"; "INSERT": "-- INSERT --" , "REPLACE": "-- REPLACE --" , "MORE": "-- MORE --" - , "VISUAL": "-- VISUAL --" + , "VISUAL": "-- VISUAL --" , "VISLINE": "-- VISUAL LINE --" , "REGISTERS": "--- Registers ---" , "WRITE": "\"%1\" %2L, %3C written" @@ -31,6 +31,10 @@ VIMRE_VERSION = "1.0.0b"; , "SEARCH_HIT_BOTTOM": "search hit BOTTOM, continuing at TOP" , "SEARCH_HIT_TOP": "search hit TOP, continuing at BOTTOM" , "REPLACE": "%1 substitution(s) on %2 line(s)" + + , "VA_REC_START": "Recording Session ..." + , "VA_REC_REPLAY": "Replaying Session ..." + , "VA_REC_END": "Session Ended" }; var errors = { diff --git a/botanjs/src/externs/Components.Vim.Cursor.js b/botanjs/src/externs/Components.Vim.Cursor.js index 5bdc8b1..2b60b30 100644 --- a/botanjs/src/externs/Components.Vim.Cursor.js +++ b/botanjs/src/externs/Components.Vim.Cursor.js @@ -23,6 +23,8 @@ Components.Vim.Cursor.lineEnd; /** @type Function */ Components.Vim.Cursor.updatePosition; /** @type Function */ +Components.Vim.Cursor.fixTab; +/** @type Function */ Components.Vim.Cursor.openAction; /** @type Function */ Components.Vim.Cursor.openRunAction; diff --git a/botanjs/src/externs/Components.Vim.VimArea.js b/botanjs/src/externs/Components.Vim.VimArea.js index 6d40d52..756c6c3 100644 --- a/botanjs/src/externs/Components.Vim.VimArea.js +++ b/botanjs/src/externs/Components.Vim.VimArea.js @@ -1,4 +1,6 @@ -/** @constructor */ +/** @constructor + * @extends {EventDispatcher} + */ Components.Vim.VimArea = function(){}; /** @type {Components.Vim.LineFeeder} */ @@ -10,6 +12,11 @@ Components.Vim.VimArea.statusFeeder; /** @type {Components.Vim.StatusBar} */ Components.Vim.VimArea.statusBar; +/** @type Function */ +Components.Vim.VimArea.demo; +/** @type Function */ +Components.Vim.VimArea.display; + /** @type Number */ Components.Vim.VimArea.index; /** @type Number */