diff --git a/botanjs/src/Components/Vim/Actions/INSERT.js b/botanjs/src/Components/Vim/Actions/INSERT.js index e131c88..e082baf 100644 --- a/botanjs/src/Components/Vim/Actions/INSERT.js +++ b/botanjs/src/Components/Vim/Actions/INSERT.js @@ -1,10 +1,11 @@ (function(){ var ns = __namespace( "Components.Vim.Actions" ); - /** @type {System.Debug} */ - var debug = __import( "System.Debug" ); var Mesg = __import( "Components.Vim.Message" ); + /** @type {Components.Vim.State.Stack} */ + var Stack = __import( "Components.Vim.State.Stack" ); + var Translate = function( c ) { switch( c ) @@ -16,15 +17,75 @@ } }; + /* @param {Components.Vim.LineFeeder} */ + var ContentPosition = function( f ) + { + var line = f.cursor.getLine(); + var n = line.lineNum; + + var p = 0; + if( 0 < n ) + { + p = f.content.indexOf( "\n" ); + for( i = 1; p != -1 && i < n; i ++ ) + { + p = f.content.indexOf( "\n", p + 1 ); + } + + if( f.wrap ) + { + // wordwrap offset + p ++; + } + } + + p += f.cursor.aX; + return p; + }; + /** @type {Components.Vim.Cursor.IAction} */ var INSERT = function( Cursor ) { /** @type {Components.Vim.Cursor} */ - this.cursor = Cursor; + this.__cursor = Cursor; + + // Initialize this stack + this.__rec( "", true ); }; INSERT.prototype.dispose = function() { + + }; + + INSERT.prototype.__storeState = function( c, pos ) + { + return function() { + debug.Inf( pos, c ); + }; + }; + + INSERT.prototype.__rec = function( c, newRec ) + { + if( newRec || !this.__stack ) + { + if( this.__stack ) + { + var c = this.__content; + + this.__stack.store( + this.__storeState( c, this.__startPosition ) + ); + + this.__cursor.rec.store( this.__stack ); + } + + this.__content = ""; + this.__stack = new Stack(); + this.__startPosition = ContentPosition( this.__cursor.feeder ); + } + + this.__content += c; }; INSERT.prototype.handler = function( e ) @@ -34,42 +95,27 @@ if( inputChar.length != 1 ) return; - var cur = this.cursor; + var cur = this.__cursor; var feeder = cur.feeder; - var line = cur.getLine(); - var n = line.lineNum; + var f = ContentPosition( feeder ); - var cont = feeder.content; + feeder.content = + feeder.content.substring( 0, f ) + + inputChar + + feeder.content.substring( f ); - var f = 0; - if( 0 < n ) - { - f = cont.indexOf( "\n" ); - for( i = 1; f != -1 && i < n; i ++ ) - { - f = cont.indexOf( "\n", f + 1 ); - } - - if( this.cursor.feeder.wrap ) - { - // wordwrap offset - f ++; - } - } - - f += cur.aX; - - feeder.content = cont.substring( 0, f ) + inputChar + cont.substring( f ); feeder.pan(); feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); + this.__rec( inputChar ); + cur.moveX( 1 ); }; INSERT.prototype.getMessage = function() { - var l = this.cursor.feeder.firstBuffer.cols; + var l = this.__cursor.feeder.firstBuffer.cols; var msg = Mesg( "INSERT" ); for( var i = msg.length; i < l; i ++ ) msg += " "; diff --git a/botanjs/src/Components/Vim/Actions/UNDO.js b/botanjs/src/Components/Vim/Actions/UNDO.js new file mode 100644 index 0000000..d7a76f8 --- /dev/null +++ b/botanjs/src/Components/Vim/Actions/UNDO.js @@ -0,0 +1,41 @@ +(function(){ + var ns = __namespace( "Components.Vim.Actions" ); + + var Mesg = __import( "Components.Vim.Message" ); + + /** @type {Components.Vim.Cursor.IAction} */ + var UNDO = function( Cursor ) + { + /** @type {Components.Vim.Cursor} */ + this.__cursor = Cursor; + this.__message = "UNDO COMMAND"; + }; + + UNDO.prototype.dispose = function() + { + + }; + + UNDO.prototype.handler = function( e ) + { + e.preventDefault(); + + /** @type {Components.Vim.State.Stack} */ + var stack = this.__cursor.rec.undo(); + if( stack ) + { + stack.play(); + } + else + { + this.__message = Mesg( "UNDO_LIMIT" ); + } + }; + + UNDO.prototype.getMessage = function() + { + return this.__message; + }; + + ns[ NS_EXPORT ]( EX_CLASS, "UNDO", UNDO ); +})(); diff --git a/botanjs/src/Components/Vim/Controls.js b/botanjs/src/Components/Vim/Controls.js index 33629ea..7e13dc6 100644 --- a/botanjs/src/Components/Vim/Controls.js +++ b/botanjs/src/Components/Vim/Controls.js @@ -62,10 +62,18 @@ case 65: // a cfeeder.cursor.openAction( "INSERT" ); break; - + case 73: // i + break; + case 85: // u, undo + cfeeder.cursor.openRunAction( "UNDO", e ); + break; + case 88: // x, del + break; case 1065: // A, append at the line end break; - case 73: // i + case 1088: // X, delete before + break; + case 1085: // U, undo previous changes in oneline break; case 1073: // I, append before the line start, after spaces break; diff --git a/botanjs/src/Components/Vim/Cursor.js b/botanjs/src/Components/Vim/Cursor.js index b73c4ed..abb5f1b 100644 --- a/botanjs/src/Components/Vim/Cursor.js +++ b/botanjs/src/Components/Vim/Cursor.js @@ -4,6 +4,9 @@ /** @type {System.Debug} */ var debug = __import( "System.Debug" ); + /** @type {Components.Vim.State.Recorder} */ + var Recorder = __import( "Components.Vim.State.Recorder" ); + var Actions = __import( "Components.Vim.Actions.*" ); var GetLine = function( buffs, l ) @@ -71,6 +74,9 @@ // The resulting position this.P = 0; + // State recorder + this.rec = new Recorder(); + this.action = null; }; @@ -210,6 +216,7 @@ { if( this.action ) this.action.dispose(); this.action = new (Actions[ name ])( this ); + this.__pulseMsg = null; this.feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); }; @@ -219,6 +226,18 @@ if( !this.action ) return; this.action.dispose(); this.action = null; + this.__pulseMsg = null; + + this.feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); + }; + + Cursor.prototype.openRunAction = function( name, e ) + { + /** @type {Components.Vim.IAction} */ + var action = new (Actions[ name ])( this ); + action.handler( e ); + this.__pulseMsg = action.getMessage(); + action.dispose(); this.feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); }; @@ -244,23 +263,52 @@ var X = this.X; var f = this.feeder; + var w = 1; + // Calculate wordwrap offset if( f.wrap ) { - var cols = f.firstBuffer.cols + 1; - var w = X < cols ? 0 : Math.floor( X / cols ); + var lines = this.getLine().visualLines; - if( 0 < w ) + for( var i in lines ) { - X -= w; + /** @type {Components.Vim.LineBuffer} */ + var vline = lines[ i ]; + + // Actual length + var aLen = vline.content.toString().length; + + // Visual length + var vLen = vline.toString().length; + + // Plus the "\n" character + X -= vLen + 1; + + if( 0 <= X ) + { + w += aLen; + } + else if( X < 0 ) + { + w += X + vLen; + break; + } } } - return X; + return w; } ); __readOnly( Cursor.prototype, "message", function() { + if( this.__pulseMsg ) + { + var m = this.__pulseMsg; + this.__pulseMsg = null; + + return m; + } + return this.action && this.action.getMessage(); } ); diff --git a/botanjs/src/Components/Vim/LineFeeder.js b/botanjs/src/Components/Vim/LineFeeder.js index 059fd04..58258b4 100644 --- a/botanjs/src/Components/Vim/LineFeeder.js +++ b/botanjs/src/Components/Vim/LineFeeder.js @@ -195,7 +195,7 @@ } ); __readOnly( Feeder.prototype, "lineStat", function() { - var X = this.cursor.aX; + var X = this.cursor.aX + 1; var line = this.cursor.getLine(); var tabStat = ""; diff --git a/botanjs/src/Components/Vim/State/Recorder.js b/botanjs/src/Components/Vim/State/Recorder.js new file mode 100644 index 0000000..e6cfe6b --- /dev/null +++ b/botanjs/src/Components/Vim/State/Recorder.js @@ -0,0 +1,41 @@ +(function(){ + var ns = __namespace( "Components.Vim.State" ); + + var Recorder = function() + { + this.__steps = []; + this.__i = 0; + }; + + Recorder.prototype.undo = function() + { + var i = this.__i - 1; + if( i == -1 || !this.__steps.length ) return null; + + + return this.__steps[ this.__i = i ]; + }; + + Recorder.prototype.redo = function() + { + var i = this.__i + 1; + if( i == -1 || !this.__steps.length ) return null; + + var State = this.__steps[ i ]; + if( State ) + { + this.__i = i; + return State; + } + + return null; + }; + + Recorder.prototype.record = function( StateObj ) + { + this.__steps[ this.__i ] = StateObj; + delete this.__steps[ ++ this.__i ]; + }; + + ns[ NS_EXPORT ]( EX_CLASS, "Recorder", Recorder ); +})(); diff --git a/botanjs/src/Components/Vim/State/Stack.js b/botanjs/src/Components/Vim/State/Stack.js new file mode 100644 index 0000000..a58e99d --- /dev/null +++ b/botanjs/src/Components/Vim/State/Stack.js @@ -0,0 +1,19 @@ +(function(){ + var ns = __namespace( "Components.Vim.State" ); + + var Stack = function() + { + }; + + Stack.prototype.store = function( handler ) + { + this.__handler = handler; + }; + + Stack.prototype.play = function() + { + if( this.__handler ) this.__handler(); + }; + + ns[ NS_EXPORT ]( EX_CLASS, "Stack", Stack ); +})(); diff --git a/botanjs/src/Components/Vim/StatusBar.js b/botanjs/src/Components/Vim/StatusBar.js index c02f82c..1a57d5c 100644 --- a/botanjs/src/Components/Vim/StatusBar.js +++ b/botanjs/src/Components/Vim/StatusBar.js @@ -32,7 +32,7 @@ if( text == undefined || text === "" ) continue; display += text.substr( 0, avail ); - i = display.length - 1; + i = display.length; } else display += " "; } diff --git a/botanjs/src/Components/Vim/_this.js b/botanjs/src/Components/Vim/_this.js index 0ed8cd0..b568ced 100644 --- a/botanjs/src/Components/Vim/_this.js +++ b/botanjs/src/Components/Vim/_this.js @@ -13,6 +13,8 @@ , "BOTTOM": "Bot" , "ALL": "All" , "EXIT": "Type :quit to exit Vim" + + , "UNDO_LIMIT": "Already at oldest change" }; var errors = { diff --git a/botanjs/src/externs/Components.Vim.Cursor.js b/botanjs/src/externs/Components.Vim.Cursor.js index 4564825..13b5624 100644 --- a/botanjs/src/externs/Components.Vim.Cursor.js +++ b/botanjs/src/externs/Components.Vim.Cursor.js @@ -5,6 +5,8 @@ Components.Vim.Cursor = function(){}; Components.Vim.Cursor.feeder; /** @type {Components.Vim.IAction} */ Components.Vim.Cursor.action; +/** @type {Components.Vim.State.Recorder} */ +Components.Vim.Cursor.rec; /** @type Function */ Components.Vim.Cursor.moveX; @@ -19,6 +21,8 @@ Components.Vim.Cursor.updatePosition; /** @type Function */ Components.Vim.Cursor.openAction; /** @type Function */ +Components.Vim.Cursor.openRunAction; +/** @type Function */ Components.Vim.Cursor.closeAction; /** @type {Array} */ diff --git a/botanjs/src/externs/Components.Vim.State.Recorder.js b/botanjs/src/externs/Components.Vim.State.Recorder.js new file mode 100644 index 0000000..0303938 --- /dev/null +++ b/botanjs/src/externs/Components.Vim.State.Recorder.js @@ -0,0 +1,9 @@ +/** @constructor */ +Components.Vim.State.Recorder = function(){}; + +/** @type Function */ +Components.Vim.State.undo; +/** @type Function */ +Components.Vim.State.redo; +/** @type Function */ +Components.Vim.State.record; diff --git a/botanjs/src/externs/Components.Vim.State.Stack.js b/botanjs/src/externs/Components.Vim.State.Stack.js new file mode 100644 index 0000000..c572081 --- /dev/null +++ b/botanjs/src/externs/Components.Vim.State.Stack.js @@ -0,0 +1,7 @@ +/** @constructor */ +Components.Vim.State.Stack = function(){}; + +/** @type Function */ +Components.Vim.State.Stack.play; +/** @type Function */ +Components.Vim.State.Stack.store; diff --git a/botanjs/src/externs/Components.Vim.State.js b/botanjs/src/externs/Components.Vim.State.js new file mode 100644 index 0000000..884e6dc --- /dev/null +++ b/botanjs/src/externs/Components.Vim.State.js @@ -0,0 +1,2 @@ +/** @object */ +Components.Vim.State = {};