diff --git a/botanjs/src/Components/Vim/Actions/DELETE.js b/botanjs/src/Components/Vim/Actions/DELETE.js index 113a417..c42b2ef 100644 --- a/botanjs/src/Components/Vim/Actions/DELETE.js +++ b/botanjs/src/Components/Vim/Actions/DELETE.js @@ -1,13 +1,24 @@ (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 Mesg = __import( "Components.Vim.Message" ); + var occurence = __import( "System.utils.Perf.CountSubstr" ); + /** @type {Components.Vim.Cursor.IAction} */ var DELETE = function( Cursor ) { /** @type {Components.Vim.Cursor} */ this.__cursor = Cursor; + this.__nline = 0; }; DELETE.prototype.allowMovement = true; @@ -17,15 +28,65 @@ }; - DELETE.prototype.handler = function( e ) + DELETE.prototype.handler = function( e, sp ) { e.preventDefault(); + /** @type {Components.Vim.State.Registers} */ + var reg = e.target.registers; + + var cur = this.__cursor; + var feeder = cur.feeder; + + var c = feeder.content; + + var s = sp; + var e = cur.aPos; + + if( e < s ) + { + s = cur.aPos; + e = sp; + } + + var removed = c.substring( s, e + 1 ); + reg.change( removed ); + + this.__nline = occurence( removed, "\n" ); + + feeder.content = c.substring( 0, s ) + c.substring( e + 1 ); + + var stator = new Stator( cur, s ); + var stack = new Stack(); + + c = c[ e + 1 ]; + if( c == "\n" || c == undefined ) + { + cur.suppressEvent(); + cur.moveX( -1 ); + cur.unsuppressEvent(); + } + + var f = stator.save( 0, removed ); + stack.store( function() { + f(); + // Offset correction after REDO / UNDO + cur.moveX( 1 ); + } ); + + cur.rec.record( stack ); + + feeder.pan(); }; DELETE.prototype.getMessage = function() { - return " DELETE COMMAND"; + if( this.__nline ) + { + return Mesg( "LINE_FEWER", this.__nline ); + } + + return ""; }; ns[ NS_EXPORT ]( EX_CLASS, "DELETE", DELETE ); diff --git a/botanjs/src/Components/Vim/Actions/INSERT.js b/botanjs/src/Components/Vim/Actions/INSERT.js index a199a5f..e67f083 100644 --- a/botanjs/src/Components/Vim/Actions/INSERT.js +++ b/botanjs/src/Components/Vim/Actions/INSERT.js @@ -2,9 +2,11 @@ var ns = __namespace( "Components.Vim.Actions" ); /** @type {Components.Vim.State.Stack} */ - var Stack = __import( "Components.Vim.State.Stack" ); + var Stack = __import( "Components.Vim.State.Stack" ); + /** @type {Components.Vim.State.Stator} */ + var Stator = __import( "Components.Vim.State.Stator" ); /** @type {System.Debug} */ - var debug = __import( "System.Debug" ); + var debug = __import( "System.Debug" ); var Mesg = __import( "Components.Vim.Message" ); @@ -27,7 +29,7 @@ /** @type {Components.Vim.Cursor} */ this.__cursor = Cursor; - this.__startState = this.__saveCur(); + this.__Stator = new Stator( Cursor ); // Initialize this stack this.__rec( "", true ); @@ -35,66 +37,12 @@ INSERT.prototype.allowMovement = false; - INSERT.prototype.__saveCur = function() - { - var c = this.__cursor; - var obj = { - p: c.P - , x: c.X - , y: c.Y - , px: c.feeder.panX - , py: c.feeder.panY - }; - - if( 0 < obj.x ) - { - obj.p -= 1; - obj.x -= 1; - } - - return obj; - } - INSERT.prototype.dispose = function() { this.__cursor.moveX( -1 ); this.__rec( "", true ); }; - INSERT.prototype.__storeState = function() - { - var cur = this.__cursor; - var feeder = cur.feeder; - var insertLength = this.__insertLength; - var contentUndo = this.__contentUndo; - var startPos = this.__startPosition; - var sSt = this.__startState; - var eSt = this.__saveCur(); - - var st = sSt; - // Calling this repeatedly will swap between UNDO / REDO state - return function() { - var contentRedo = feeder.content.substr( startPos, insertLength ); - feeder.content = - feeder.content.substring( 0, startPos ) - + contentUndo - + feeder.content.substring( startPos + insertLength ); - insertLength = contentUndo.length; - contentUndo = contentRedo; - - cur.PStart = st.p; - cur.PEnd = st.p + 1; - cur.X = st.x; - cur.Y = st.y; - feeder.panX = st.px; - feeder.panY = st.py; - - feeder.pan(); - - st = ( st == sSt ) ? eSt : sSt; - }; - }; - INSERT.prototype.__rec = function( c, newRec ) { if( newRec || !this.__stack ) @@ -107,7 +55,7 @@ ) return; this.__stack.store( - this.__storeState() + this.__Stator.save( this.__insertLength, this.__contentUndo ) ); this.__cursor.rec.record( this.__stack ); @@ -116,7 +64,6 @@ this.__insertLength = 0; this.__contentUndo = ""; this.__stack = new Stack(); - this.__startPosition = this.__cursor.aPos; } if( c == "\n" ) @@ -132,45 +79,37 @@ var cur = this.__cursor; var feeder = cur.feeder; - switch( e.keyCode ) + // Backspace + if( e.kMap( "BS" ) ) { - case 8: // Backspace - var oY = feeder.panY + cur.Y; - if( cur.X == 0 && feeder.panY == 0 && cur.Y == 0 ) return; + var oY = feeder.panY + cur.Y; + if( cur.X == 0 && feeder.panY == 0 && cur.Y == 0 ) return; - cur.moveX( -1, true, true ); + cur.moveX( -1, true, true ); - var f = cur.aPos; + var f = cur.aPos; - if( this.__insertLength <= 0 ) - { - this.__contentUndo = feeder.content.substr( f, 1 ) + this.__contentUndo; - this.__startPosition --; - } - else - { - this.__insertLength --; - } + if( this.__insertLength <= 0 ) + { + this.__contentUndo = feeder.content.substr( f, 1 ) + this.__contentUndo; + this.__insertLength --; + } - feeder.content = - feeder.content.substring( 0, f ) - + feeder.content.substring( f + 1 ); - - break; - case 46: // Delete - var f = cur.aPos; - - this.__contentUndo += feeder.content.substr( f, 1 ); - - feeder.content = - feeder.content.substring( 0, f ) - + feeder.content.substring( f + 1 ); - - break; - default: - // Do nothing - return; + feeder.content = + feeder.content.substring( 0, f ) + + feeder.content.substring( f + 1 ); } + else if( e.kMap( "Del" ) ) + { + var f = cur.aPos; + + this.__contentUndo += feeder.content.substr( f, 1 ); + + feeder.content = + feeder.content.substring( 0, f ) + + feeder.content.substring( f + 1 ); + } + else return; feeder.pan(); feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); diff --git a/botanjs/src/Components/Vim/Actions/VISUAL.js b/botanjs/src/Components/Vim/Actions/VISUAL.js index ab9671b..659b058 100644 --- a/botanjs/src/Components/Vim/Actions/VISUAL.js +++ b/botanjs/src/Components/Vim/Actions/VISUAL.js @@ -23,6 +23,7 @@ this.__leaveMesg = ""; Cursor.blink = false; + Cursor.pSpace = true; }; VISUAL.prototype.allowMovement = true; @@ -31,6 +32,7 @@ { this.__msg = this.__leaveMesg; this.__cursor.blink = true; + this.__cursor.pSpace = false; this.__cursor.PStart = this.__selStart; this.__cursor.PEnd = this.__selStart + 1; }; @@ -41,27 +43,18 @@ if( e.ModKeys ) return; + var cur = this.__cursor; var Action = null; switch( true ) { case e.kMap( "y" ): - Action = new YANK( this.__cursor ); + Action = new YANK( cur ); break; case e.kMap( "d" ): - Action = new DELETE( this.__cursor ); + Action = new DELETE( cur ); break; } - if( Action ) - { - Action.handler( e ); - this.__leaveMesg = Action.getMessage(); - Action.dispose(); - - return true; - } - - var cur = this.__cursor; var prevPos = this.__start; var newPos = cur.PStart; @@ -78,6 +71,18 @@ this.__selStart = prevPos; } + if( Action ) + { + cur.suppressEvent(); + Action.handler( e, this.__startaP ); + this.__leaveMesg = Action.getMessage(); + Action.dispose(); + cur.unsuppressEvent(); + + this.__selStart = cur.PStart; + return true; + } + cur.PStart = prevPos; cur.PEnd = newPos; }; diff --git a/botanjs/src/Components/Vim/Controls.js b/botanjs/src/Components/Vim/Controls.js index ce1e947..7bcc195 100644 --- a/botanjs/src/Components/Vim/Controls.js +++ b/botanjs/src/Components/Vim/Controls.js @@ -14,6 +14,7 @@ var KEY_ALT = 18; var BACKSPACE = 8; + var DELETE = 46; var _0 = 48; var _1 = 49; var _2 = 50; var _3 = 51; var _4 = 52; var _5 = 53; var _6 = 54; var _7 = 55; var _8 = 56; var _9 = 57; @@ -31,6 +32,8 @@ var F6 = 117; var F7 = 118; var F8 = 119; var F9 = 120; var F10 = 121; var F11 = 122; var F12 = 123; + var COMMA = 188; var FULLSTOP = 190; + var __maps = {}; var Map = function( str ) { @@ -62,6 +65,9 @@ var kCode; switch( sCode ) { + case "BS": kCode = Mod + BACKSPACE; break; + case "Del": kCode = Mod + DELETE; break; + case "A": Mod = SHIFT; case "a": kCode = Mod + A; break; case "B": Mod = SHIFT; case "b": kCode = Mod + B; break; case "C": Mod = SHIFT; case "c": kCode = Mod + C; break; @@ -99,8 +105,11 @@ case "*": Mod = SHIFT; case "8": kCode = Mod + _8; break; case "(": Mod = SHIFT; case "9": kCode = Mod + _9; break; case ")": Mod = SHIFT; case "0": kCode = Mod + _0; break; + case "<": Mod = SHIFT; case ",": kCode = Mod + COMMA; break; + case ">": Mod = SHIFT; case ".": kCode = Mod + FULLSTOP; break; + default: - throw new Error( "No such keys: " + str ); + throw new Error( "Unsupport keys: " + str ); } return __maps[ str ] = kCode; @@ -209,7 +218,7 @@ var ccur = this.__ccur; var x = ccur.X; - ccur.moveX( a, b, c ); + ccur.moveX( a, b, c || ccur.pSpace ); if( ccur.X == x ) beep(); }; @@ -245,7 +254,7 @@ var cursorHandled = true; switch( kCode ) { - case BACKSPACE: this.__cMoveX( -1, true ); break; // Backspace, go back 1 char, regardless of line + case BACKSPACE: this.__cMoveX( -1, true ); break; // Backspace, go back 1 char case H: this.__cMoveX( -1 ); break; // Left case L: this.__cMoveX( 1 ); break; // Right case K: this.__cMoveY( -1 ); break; // Up @@ -259,7 +268,7 @@ ccur.lineStart(); break; case SHIFT + _4: // $, End - ccur.lineEnd(); + ccur.lineEnd( ccur.pSpace ); break; case SHIFT + G: // Goto last line ccur.moveY( Number.MAX_VALUE ); @@ -293,10 +302,10 @@ * */ Controls.prototype.handler = function( sender, e ) { - // Neve capture these keys - if( e.ModKeys + // Never capture these keys + if( e.keyCode == ( ALT + D ) // F2 - F12 - || ( F1 < e.keyCode && e.keyCode < 124 ) + || ( F1 < e.keyCode && e.keyCode <= F12 ) ) return; // Clear composite command @@ -334,36 +343,32 @@ return; } - if( this.__cursorCommand( e ) ) - { - e.preventDefault(); - return; - } + e.preventDefault(); - if( this.__actionCommand( e ) ) - { - e.preventDefault(); - return; - } + if( this.__cursorCommand( e ) ) return; + if( this.__actionCommand( e ) ) return; }; - var InputEvent = function( e ) + var InputEvent = function( sender, e ) { this.__e = e; + this.__target = sender; var c = this.__e.keyCode; this.__escape = c == ESC || ( e.ctrlKey && c == C ); this.__kCode = c + ( e.shiftKey || e.getModifierState( "CapsLock" ) ? SHIFT : 0 ) - + ( e.ctrlKey ? CTRL : 0 ); + + ( e.ctrlKey ? CTRL : 0 ) + + ( e.altKey ? ALT : 0 ); this.__modKeys = c == KEY_SHIFT || c == KEY_CTRL || c == KEY_ALT; this.__key = e.key; }; - __readOnly( InputEvent.prototype, "key", function() { return this.__key; } ); - __readOnly( InputEvent.prototype, "keyCode", function() { return this.__kCode; } ); + __readOnly( InputEvent.prototype, "target", function() { return this.__target; } ); + __readOnly( InputEvent.prototype, "key", function() { return this.__key; } ); + __readOnly( InputEvent.prototype, "keyCode", function() { return this.__kCode; } ); __readOnly( InputEvent.prototype, "ModKeys", function() { return this.__modKeys; } ); __readOnly( InputEvent.prototype, "Escape", function() { return this.__escape; } ); diff --git a/botanjs/src/Components/Vim/Cursor.js b/botanjs/src/Components/Vim/Cursor.js index 0481f30..5f99f5d 100644 --- a/botanjs/src/Components/Vim/Cursor.js +++ b/botanjs/src/Components/Vim/Cursor.js @@ -80,6 +80,9 @@ this.action = null; this.blink = true; + this.pSpace = false; + + this.__suppEvt = 0; }; // Set by VimArea @@ -118,12 +121,12 @@ // Include empty lines befor cursor end if( ( phantomSpace && cLen - 1 <= x ) || ( cLen == 1 && c == undefined ) ) { - x = d > 0 ? cLen - 1 : 0; + x = 0 < d ? cLen - 1 : 0; } // ( 2 < cLen ) Exclude empty lines at cursor end - else if( ( 2 < cLen && x == cLen - 1 && c == " " ) || c == undefined ) + else if( ( 2 <= cLen && x == cLen - 1 && c == " " ) || c == undefined ) { - x = d > 0 ? cLen - 2 : 0; + x = 0 < d ? cLen - 2 : 0; } else if( c == "\n" ) { @@ -158,6 +161,16 @@ this.PEnd = P + 1; this.__p = P; + this.__fireUpdate(); + }; + + Cursor.prototype.__fireUpdate = function() + { + if( 0 < this.__suppEvt ) + { + debug.Info( "Event suppressed, suppression level is: " + this.__suppEvt ); + return; + } this.feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); }; @@ -270,7 +283,7 @@ this.action = new (Actions[ name ])( this ); this.__pulseMsg = null; - this.feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); + this.__fireUpdate(); }; Cursor.prototype.closeAction = function() @@ -280,7 +293,7 @@ this.__pulseMsg = this.action.getMessage(); this.action = null; - this.feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); + this.__fireUpdate(); }; // Open, Run, then close an action @@ -292,9 +305,12 @@ this.__pulseMsg = action.getMessage(); action.dispose(); - this.feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) ); + this.__fireUpdate(); }; + Cursor.prototype.suppressEvent = function() { ++ this.__suppEvt; }; + Cursor.prototype.unsuppressEvent = function() { -- this.__suppEvt; }; + Cursor.prototype.getLine = function() { var feeder = this.feeder; diff --git a/botanjs/src/Components/Vim/State/Stator.js b/botanjs/src/Components/Vim/State/Stator.js new file mode 100644 index 0000000..a17687d --- /dev/null +++ b/botanjs/src/Components/Vim/State/Stator.js @@ -0,0 +1,71 @@ +(function(){ + var ns = __namespace( "Components.Vim.State" ); + + var Stator = function( cur, start ) + { + this.__cursor = cur; + this.__startPosition = start == undefined ? cur.aPos : start; + this.__startState = this.__saveCur(); + }; + + Stator.prototype.save = function( insertLength, contentUndo ) + { + var cur = this.__cursor; + var feeder = cur.feeder; + var startPos = this.__startPosition; + + if( insertLength < 0 ) + { + startPos += insertLength; + insertLength = 0; + } + + var sSt = this.__startState; + var eSt = this.__saveCur(); + + var st = sSt; + // Calling this repeatedly will swap between UNDO / REDO state + return function() { + var contentRedo = feeder.content.substr( startPos, insertLength ); + feeder.content = + feeder.content.substring( 0, startPos ) + + contentUndo + + feeder.content.substring( startPos + insertLength ); + insertLength = contentUndo.length; + contentUndo = contentRedo; + + cur.PStart = st.p; + cur.PEnd = st.p + 1; + cur.X = st.x; + cur.Y = st.y; + feeder.panX = st.px; + feeder.panY = st.py; + + feeder.pan(); + + st = ( st == sSt ) ? eSt : sSt; + }; + }; + + Stator.prototype.__saveCur = function() + { + var c = this.__cursor; + var obj = { + p: c.PStart + , x: c.X + , y: c.Y + , px: c.feeder.panX + , py: c.feeder.panY + }; + + if( 0 < obj.x ) + { + obj.p -= 1; + obj.x -= 1; + } + + return obj; + }; + + ns[ NS_EXPORT ]( EX_CLASS, "Stator", Stator ); +})(); diff --git a/botanjs/src/Components/Vim/VimArea.js b/botanjs/src/Components/Vim/VimArea.js index 368cecb..54c7864 100644 --- a/botanjs/src/Components/Vim/VimArea.js +++ b/botanjs/src/Components/Vim/VimArea.js @@ -32,7 +32,7 @@ if ( e.keyCode ) code = e.keyCode; else if ( e.which ) code = e.which; - handler( sender, new InputEvent( e ) ); + handler( sender, new InputEvent( sender, e ) ); }; }; diff --git a/botanjs/src/Components/Vim/_this.js b/botanjs/src/Components/Vim/_this.js index 04060da..f9e572e 100644 --- a/botanjs/src/Components/Vim/_this.js +++ b/botanjs/src/Components/Vim/_this.js @@ -17,6 +17,8 @@ , "UNDO_LIMIT": "Already at oldest change" , "REDO_LIMIT": "Already at newest change" + + , "LINE_FEWER": "%1 fewer lines" }; var errors = { diff --git a/botanjs/src/externs/Components.Vim.Controls.InputEvent.js b/botanjs/src/externs/Components.Vim.Controls.InputEvent.js index ffd085e..a8e1bdb 100644 --- a/botanjs/src/externs/Components.Vim.Controls.InputEvent.js +++ b/botanjs/src/externs/Components.Vim.Controls.InputEvent.js @@ -1,6 +1,8 @@ /** @constructor */ Components.Vim.Controls.InputEvent = function(){}; +/** @type {Components.Vim.VimArea} */ +Components.Vim.Controls.InputEvent.target; /** @type String */ Components.Vim.Controls.InputEvent.key; /** @type Boolean */ diff --git a/botanjs/src/externs/Components.Vim.Cursor.js b/botanjs/src/externs/Components.Vim.Cursor.js index 76fef9d..2f42cdb 100644 --- a/botanjs/src/externs/Components.Vim.Cursor.js +++ b/botanjs/src/externs/Components.Vim.Cursor.js @@ -26,9 +26,15 @@ Components.Vim.Cursor.openAction; Components.Vim.Cursor.openRunAction; /** @type Function */ Components.Vim.Cursor.closeAction; +/** @type Function */ +Components.Vim.Cursor.suppressEvent; +/** @type Function */ +Components.Vim.Cursor.unsuppressEvent; /** @type {Boolean} */ Components.Vim.Cursor.blink; +/** @type {Boolean} */ +Components.Vim.Cursor.pSpace; /** @type {Array} */ Components.Vim.Cursor.lineBuffers; /** @type Number */