diff --git a/botanjs/src/Components/Vim/Actions/DELETE.js b/botanjs/src/Components/Vim/Actions/DELETE.js index 30cb180..86922c1 100644 --- a/botanjs/src/Components/Vim/Actions/DELETE.js +++ b/botanjs/src/Components/Vim/Actions/DELETE.js @@ -33,7 +33,7 @@ this.__cursor.unsuppressEvent(); }; - DELETE.prototype.handler = function( e, sp ) + DELETE.prototype.handler = function( e, sp, newLine ) { e.preventDefault(); @@ -45,8 +45,14 @@ var cur = this.__cursor; var feeder = cur.feeder; + // Do nothing if content is considered empty + if( feeder.firstBuffer.next.placeholder && feeder.content.length < 2 ) + { + debug.Info( "Content is empty" ); + return true; + } + var Triggered = false; - var newLine = false; if( sp == undefined ) { @@ -135,7 +141,8 @@ } } - var c = feeder.content; + // last "\n" padding + var c = feeder.content.slice( 0, -1 ); var s = sp; var e = cur.aPos; @@ -152,6 +159,7 @@ this.__nline = occurence( removed, "\n" ); feeder.content = c.substring( 0, s ) + c.substring( e + 1 ); + if( feeder.content === "" ) feeder.content = "\n"; // Try to keep the original panning if possible feeder.pan( undefined diff --git a/botanjs/src/Components/Vim/Actions/INSERT.js b/botanjs/src/Components/Vim/Actions/INSERT.js index 759aec6..ed9ebd8 100644 --- a/botanjs/src/Components/Vim/Actions/INSERT.js +++ b/botanjs/src/Components/Vim/Actions/INSERT.js @@ -30,6 +30,8 @@ this.__cursor = Cursor; this.__stator = new Stator( Cursor ); + this.__minReach = 0; + this.__insertLen = 0; // Initialize this stack this.__rec( "", true ); @@ -57,18 +59,27 @@ if( this.__stack ) { // If nothings changed - if( this.__insertLength == 0 + if( this.__minReach == 0 + && this.__punch == 0 && this.__contentUndo === "" ) return; + if( this.__punch < this.__minReach ) + { + this.__minReach = this.__punch; + } + this.__stack.store( - this.__stator.save( this.__insertLength, this.__contentUndo ) + this.__stator.save( + this.__insertLen + , this.__contentUndo + , -this.__minReach ) ); this.__cursor.rec.record( this.__stack ); } - this.__insertLength = 0; + this.__punch = 0; this.__contentUndo = ""; this.__stack = new Stack(); } @@ -78,7 +89,14 @@ // todo } - this.__insertLength += c.length; + if( this.__punch < this.__minReach ) + { + this.__insertLen = 0; + this.__minReach = this.__punch; + } + + this.__punch += c.length; + this.__insertLen += c.length; }; INSERT.prototype.__specialKey = function( e, inputChar ) @@ -92,11 +110,9 @@ var oY = feeder.panY + cur.Y; if( cur.X == 0 && feeder.panY == 0 && cur.Y == 0 ) return; - cur.moveX( -1, true, true ); + var f = cur.aPos - 1; - var f = cur.aPos; - - if( this.__insertLength <= 0 ) + if( this.__punch <= this.__minReach ) { this.__contentUndo = feeder.content.substr( f, 1 ) + this.__contentUndo; } @@ -105,7 +121,12 @@ feeder.content.substring( 0, f ) + feeder.content.substring( f + 1 ); - this.__insertLength --; + feeder.pan(); + + cur.moveX( -1, true, true ); + + if( 0 < this.__insertLen ) this.__insertLen --; + this.__punch --; } else if( e.kMap( "Del" ) ) { @@ -116,16 +137,20 @@ feeder.content = feeder.content.substring( 0, f ) + feeder.content.substring( f + 1 ); + + feeder.pan(); } else return; - feeder.pan(); feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); }; INSERT.prototype.handler = function( e ) { e.preventDefault(); + + if( e.ModKeys ) return; + var inputChar = Translate( e.key ); if( inputChar.length != 1 ) @@ -160,7 +185,6 @@ feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); this.__rec( inputChar ); - }; INSERT.prototype.getMessage = function() diff --git a/botanjs/src/Components/Vim/Actions/TO.js b/botanjs/src/Components/Vim/Actions/TO.js index 1ff0bcd..eb1e00e 100644 --- a/botanjs/src/Components/Vim/Actions/TO.js +++ b/botanjs/src/Components/Vim/Actions/TO.js @@ -40,9 +40,6 @@ var lowerLimmit = p; - var cX = cur.X; - var tX = cX; - var Char = et.key; if( et.kMap( "Tab" ) ) { @@ -55,15 +52,16 @@ return; } + var tX = -1; // Forward if( em.kMap( "t" ) || em.kMap( "f" ) ) { - tX = f.content.indexOf( Char, p + cX + 1 ); + tX = f.content.indexOf( Char, cur.aPos + 1 ); } // backward else { - tX = f.content.lastIndexOf( Char, p + cX - 1 ); + tX = f.content.lastIndexOf( Char, cur.aPos - 1 ); } if( lowerLimmit <= tX && tX < upperLimit ) diff --git a/botanjs/src/Components/Vim/Actions/VISUAL.js b/botanjs/src/Components/Vim/Actions/VISUAL.js index bd9085d..d650abd 100644 --- a/botanjs/src/Components/Vim/Actions/VISUAL.js +++ b/botanjs/src/Components/Vim/Actions/VISUAL.js @@ -11,12 +11,44 @@ /** @type {Components.Vim.IAction} */ var DELETE = ns[ NS_INVOKE ]( "DELETE" ); + var MODE_NULL = -1; + var MODE_VISUAL = 0; + var MODE_LINE = 1; + + // The offset of given line relative to content + var offsetY = function( cur, l ) + { + if( l == 0 ) return 0; + + var f = cur.feeder; + + var j = 0; + + var last = -1; + for( var i = f.content.indexOf( "\n" ); 0 <= i; i = f.content.indexOf( "\n", i + 1 ) ) + { + last = i; + j ++; + if( l <= j ) break; + } + + if( f.EOF ) i = last; + + // "\n" compensation + var c = f.content[ i + 1 ]; + if(!( c == undefined || c == "\n" )) + { + i ++; + } + + return i; + }; + /** @type {Components.Vim.IAction} */ var VISUAL = function( Cursor ) { this.__reset( Cursor ); - this.__msg = Mesg( "VISUAL" ); - + this.__msg = ""; Cursor.blink = false; Cursor.pSpace = true; }; @@ -25,9 +57,25 @@ { /** @type {Components.Vim.Cursor} */ this.__cursor = Cursor; - this.__startaP = Cursor.aPos; - this.__start = Cursor.PStart; - this.__selStart = Cursor.PStart; + + var s = { + lineNum: Cursor.getLine().lineNum + , X: Cursor.X + , aPos: Cursor.aPos + , pstart: Cursor.PStart + }; + + s.aStart = s.aPos - Cursor.aX; + + Cursor.suppressEvent(); + Cursor.lineEnd( true ); + + s.aEnd = Cursor.aPos; + + Cursor.moveTo( s.aPos ); + Cursor.unsuppressEvent(); + + this.__startLine = s; }; VISUAL.prototype.allowMovement = true; @@ -38,8 +86,7 @@ c.blink = true; c.pSpace = false; - c.PStart = this.__selStart; - c.PEnd = this.__selStart + 1; + c.updatePosition(); // This fix the highlighting position of missing phantomSpace // for maximum filled line @@ -59,8 +106,11 @@ if( e.ModKeys ) return; var cur = this.__cursor; + var feeder = cur.feeder; var Action = null; + var dispatchUpdate = false; + if( e.kMap( "y" ) ) { Action = new YANK( cur ); @@ -69,27 +119,73 @@ { Action = new DELETE( cur ); } + else if( e.kMap( "V" ) ) + { + if( this.__mode == MODE_LINE ) return true; + else + { + dispatchUpdate = true; + this.__mode = MODE_LINE; + this.__msg = Mesg( "VISLINE" ); + } + } + else if( e.kMap( "v" ) ) + { + if( this.__mode == MODE_VISUAL ) return true; + else + { + dispatchUpdate = true; + this.__mode = MODE_VISUAL; + this.__msg = Mesg( "VISUAL" ); + cur.updatePosition(); + } + } + + if( dispatchUpdate ) + feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); + + if( this.__mode == MODE_NULL ) + { + debug.Error( new Error( "Mode is undefined" ) ); + return true; + } + + var startLine = this.__startLine; if( Action ) { cur.suppressEvent(); - // Low-level cursor position adjustment + var lineMode = this.__mode == MODE_LINE; + if( lineMode ) + { + if( startLine.aPos < cur.aPos ) + { + cur.lineEnd( true ); + startLine.aPos = startLine.aStart; + } + else + { + cur.lineStart(); + startLine.aPos = startLine.aEnd; + } + } + // Cursor position adjustment // this swap the cursor direction from LTR to RTL // i.e. treat all delete as "e<----s" flow // to keep the cursor position as the top on UNDO / REDO - if( Action.constructor == DELETE && this.__startaP < cur.aPos ) + if( Action.constructor == DELETE && startLine.aPos < cur.aPos ) { var o = cur.aPos; - cur.moveTo( this.__startaP, true ); - this.__startaP = o; + cur.moveTo( startLine.aPos, true ); + startLine.aPos = o; } - Action.handler( e, this.__startaP ); + Action.handler( e, startLine.aPos, lineMode ); if( Action.constructor != DELETE ) { - cur.moveTo( this.__startaP ); + cur.moveTo( startLine.aPos ); } this.__msg = Action.getMessage(); @@ -97,7 +193,7 @@ Action.dispose(); cur.unsuppressEvent(); - this.__selStart = cur.PStart; + startLine.pstart = cur.PStart; return true; } @@ -109,30 +205,119 @@ var r = e.range; - if( cur.aPos == this.__startaP ) + if( cur.aPos == startLine.aPos ) { cur.moveTo( r.open, true ); this.__reset( cur ); + startLine = this.__startLine; } cur.unsuppressEvent(); cur.moveTo( r.close, true ); } - var prevPos = this.__start; - var newPos = cur.PStart; + var currAp = cur.aPos; + + // Calculate the visible max min aPos of the current screen + var line = feeder.firstBuffer; + var firstLine = line.lineNum; + var minAp = offsetY( cur, firstLine ); + var maxAp = offsetY( cur, firstLine + feeder.moreAt + 1 ) - 1; + + debug.Info( "Min aPos: " + minAp, "Max aPos: " + maxAp ); + + var pstart = startLine.X; + var nstart = cur.PStart; + + // highlight from the start + if( startLine.aPos < minAp ) + { + pstart = 0; + } + // highlight from the end + else if( maxAp < startLine.aPos ) + { + pstart = -2; + var i = 0; + do + { + if( line.placeholder ) break; + if( i <= feeder.moreAt ) + { + pstart += line.toString().length + 1; + } + i ++; + } + while( line = line.next ); + } + else + { + var l = startLine.lineNum; + if( this.__mode == MODE_LINE ) + { + cur.suppressEvent(); + pstart = 0; + + if( currAp < startLine.aPos ) + { + pstart = -1; + l ++; + + cur.lineStart(); + nstart = cur.PStart; + } + else if( startLine.aPos < currAp ) + { + cur.lineEnd( true ); + nstart = cur.PStart; + } + // aPos == currPos + else + { + cur.lineStart(); + nstart = cur.PStart; + cur.lineEnd( true ); + pstart = cur.PStart; + l = line.lineNum; + } + + cur.moveTo( currAp, true ); + + cur.unsuppressEvent(); + } + else if( this.__mode == MODE_VISUAL ) + { + if( currAp == startLine.aPos ) return; + } + + // Append the Y offset + var i = 0; + do + { + if( line.lineNum == l || line.placeholder ) break; + pstart += line.toString().length + 1; + } + while( line = line.next ); + } + + var prevPos = pstart; + var newPos = nstart; var posDiff = newPos - prevPos; - if( 0 <= posDiff ) + + var currAp = cur.aPos; + + // Sets the visual position + // s-->e + if( 0 < posDiff ) { - this.__selStart = newPos; newPos = newPos + 1; } + // e<--s else if( posDiff < 0 ) { prevPos += posDiff; - newPos = this.__start + 1; - this.__selStart = prevPos; + newPos = pstart + 1; } cur.PStart = prevPos; diff --git a/botanjs/src/Components/Vim/Actions/YANK.js b/botanjs/src/Components/Vim/Actions/YANK.js index 86ba222..44e3c27 100644 --- a/botanjs/src/Components/Vim/Actions/YANK.js +++ b/botanjs/src/Components/Vim/Actions/YANK.js @@ -27,7 +27,7 @@ this.__cursor.unsuppressEvent(); }; - YANK.prototype.handler = function( e, sp ) + YANK.prototype.handler = function( e, sp, newLine ) { e.preventDefault(); @@ -41,7 +41,6 @@ var Triggered = false; - var newLine = false; if( sp == undefined ) { Triggered = true; diff --git a/botanjs/src/Components/Vim/Controls.js b/botanjs/src/Components/Vim/Controls.js index f38b847..aace42f 100644 --- a/botanjs/src/Components/Vim/Controls.js +++ b/botanjs/src/Components/Vim/Controls.js @@ -271,10 +271,9 @@ break; case V: // Visual - ccur.openAction( "VISUAL" ); - break; case SHIFT + V: // Visual line - ccur.openAction( "VISUAL_LINE" ); + ccur.openAction( "VISUAL" ); + ccur.action.handler( e ); break; case SHIFT + SEMI_COLON: // ":" Command line diff --git a/botanjs/src/Components/Vim/Cursor.js b/botanjs/src/Components/Vim/Cursor.js index 3eaaac0..e9b6d13 100644 --- a/botanjs/src/Components/Vim/Cursor.js +++ b/botanjs/src/Components/Vim/Cursor.js @@ -147,7 +147,9 @@ if( 0 < this.__off ) { - d += this.__off; + if( 0 < d && phantomSpace ) + d += this.__off; + this.__off = 0; } @@ -215,7 +217,7 @@ if( boundary ) { - x = 0 < d ? lineEnd : 0; + x = 0 < x ? lineEnd : 0; } else if( c == "\n" ) { @@ -435,7 +437,7 @@ return null; }; - // The absX for current Line + // The position offset relative to current line __readOnly( Cursor.prototype, "aX", function() { var X = this.X; @@ -473,6 +475,7 @@ } } } + else return this.X; return w; } ); diff --git a/botanjs/src/Components/Vim/State/Stator.js b/botanjs/src/Components/Vim/State/Stator.js index a17687d..e3efae5 100644 --- a/botanjs/src/Components/Vim/State/Stator.js +++ b/botanjs/src/Components/Vim/State/Stator.js @@ -8,17 +8,12 @@ this.__startState = this.__saveCur(); }; - Stator.prototype.save = function( insertLength, contentUndo ) + Stator.prototype.save = function( insertLength, contentUndo, removeLen ) { + if( removeLen == undefined ) removeLen = 0; var cur = this.__cursor; var feeder = cur.feeder; - var startPos = this.__startPosition; - - if( insertLength < 0 ) - { - startPos += insertLength; - insertLength = 0; - } + var startPos = this.__startPosition - removeLen; var sSt = this.__startState; var eSt = this.__saveCur(); @@ -38,6 +33,7 @@ cur.PEnd = st.p + 1; cur.X = st.x; cur.Y = st.y; + cur.pX = st.cpX - 1; feeder.panX = st.px; feeder.panY = st.py; @@ -54,6 +50,7 @@ p: c.PStart , x: c.X , y: c.Y + , cpX: c.pX , px: c.feeder.panX , py: c.feeder.panY }; diff --git a/botanjs/src/Components/Vim/VimArea.js b/botanjs/src/Components/Vim/VimArea.js index 1817deb..563cd33 100644 --- a/botanjs/src/Components/Vim/VimArea.js +++ b/botanjs/src/Components/Vim/VimArea.js @@ -33,6 +33,7 @@ { return function( e ) { + sender.__active = true; e = e || window.event; if ( e.keyCode ) code = e.keyCode; else if ( e.which ) code = e.which; @@ -124,7 +125,7 @@ else { var t = ""; - i -= 3; + -- i; for( var k = 0; k < i; k ++ ) t += "."; area.value = t; @@ -144,7 +145,7 @@ var testHeight = function() { area.value += m(); - l ++; + ++ l; if( oHeight == area.scrollHeight ) { @@ -222,8 +223,14 @@ _self.select( cfeeder.cursor.position ); }; + cfeeder.dispatcher.addEventListener( "SelectionChanged", function() + { + _self.select( cfeeder.cursor.position ); + } ); + cfeeder.dispatcher.addEventListener( "VisualUpdate", Update ); - Update(); + element.value = "Please wait ..."; + Cycle.delay( Update, 70 ); this.__visualUpdate = Update; diff --git a/botanjs/src/System/Cycle/_this.js b/botanjs/src/System/Cycle/_this.js index f31bb5c..02cd746 100644 --- a/botanjs/src/System/Cycle/_this.js +++ b/botanjs/src/System/Cycle/_this.js @@ -19,11 +19,6 @@ var stepper = function() { var thisTime = new Date().getTime(); - // 0: Callback - // 1: scheduled run time - // 2: Permanent - // ( 3: id ) - // 4: interval for ( var i in tList ) { var f = tList[i]; @@ -55,7 +50,12 @@ // Should bind "func" before register var registerDelay = function (func, milliSec) { - tList[ tList.length ] = [ func, new Date().getTime() + milliSec, true ]; + var a = []; + a[ C_CALLBACK ] = func; + a[ C_TIME ] = new Date().getTime() + milliSec; + a[ C_ONCE ] = true; + + tList[ tList.length ] = a; }; var registerPermanentTicker = function ( id, func, interval ) @@ -66,7 +66,14 @@ return false; } - tList[ tList.length ] = [ func, new Date().getTime() + interval, false, id, interval ]; + var a = []; + a[ C_CALLBACK ] = func; + a[ C_TIME ] = new Date().getTime() + interval; + a[ C_ONCE ] = false; + a[ C_ID ] = id; + a[ C_INTVL ] = interval; + + tList[ tList.length ] = a; }; var deletePermanentTicker = function ( id )