diff --git a/botanjs/src/Components/Vim/Actions/INSERT.js b/botanjs/src/Components/Vim/Actions/INSERT.js index 5a4b30a..c35c167 100644 --- a/botanjs/src/Components/Vim/Actions/INSERT.js +++ b/botanjs/src/Components/Vim/Actions/INSERT.js @@ -204,6 +204,9 @@ { var ind = this.__phantomIndent; if( !this.__phantomIndent ) return; + + debug.Info( "Realize Indentation: " + ind ); + l = ind[ IN_END ]; for( var i = ind[ IN_START ]; i < l; i ++ ) { @@ -261,6 +264,7 @@ a[ IN_DEL ] = inDel; this.__phantomIndent = a; + debug.Info( "Phantom indent: " + a ); } }; @@ -269,6 +273,8 @@ var ind = this.__phantomIndent; if( !ind ) return ""; + debug.Info( "Erase phantom indent: " + ind ); + var cur = this.__cursor; var feeder = cur.feeder; diff --git a/botanjs/src/Components/Vim/Actions/REDO.js b/botanjs/src/Components/Vim/Actions/REDO.js index 43ca88b..94485d2 100644 --- a/botanjs/src/Components/Vim/Actions/REDO.js +++ b/botanjs/src/Components/Vim/Actions/REDO.js @@ -24,8 +24,10 @@ var stack = this.__cursor.rec.redo(); if( stack ) { + this.__cursor.suppressEvent(); stack.play(); - this.__message = "<>; before #" + stack.id + " " + stack.time; + this.__cursor.unsuppressEvent(); + this.__message = Mesg( "NCHANGES", "", stack.id, stack.time ); } else { diff --git a/botanjs/src/Components/Vim/Actions/REPLACE.js b/botanjs/src/Components/Vim/Actions/REPLACE.js index 1acf895..0161595 100644 --- a/botanjs/src/Components/Vim/Actions/REPLACE.js +++ b/botanjs/src/Components/Vim/Actions/REPLACE.js @@ -92,7 +92,7 @@ this.__cursor.unsuppressEvent(); }; - REPLACE.prototype.handler = function( e, p, range ) + REPLACE.prototype.handler = function( e, p ) { e.preventDefault(); @@ -143,13 +143,16 @@ var content = feeder.content.slice( 0, -1 ) .replace( search, this.__replCallback ) + "\n"; - if( !this.__replacedGroups.length ) + var numSubs = this.__replacedGroups.length; + if( !numSubs ) { this.__msg = VimError( "E486", spattern.join( "" ) ); } feeder.content = content; + this.__msg = Mesg( "REPLACE", numSubs, "" ); + // Record this step for UNDO / REDO this.__rec(); diff --git a/botanjs/src/Components/Vim/Actions/SHIFT_LINES.js b/botanjs/src/Components/Vim/Actions/SHIFT_LINES.js new file mode 100644 index 0000000..a539c17 --- /dev/null +++ b/botanjs/src/Components/Vim/Actions/SHIFT_LINES.js @@ -0,0 +1,242 @@ +(function(){ + var ns = __namespace( "Components.Vim.Actions" ); + + /** @type {System.Debug} */ + var debug = __import( "System.Debug" ); + var beep = __import( "Components.Vim.Beep" ); + + /** @type {Components.Vim.State.Stack} */ + var Stack = __import( "Components.Vim.State.Stack" ); + + var VimError = __import( "Components.Vim.Error" ); + var Mesg = __import( "Components.Vim.Message" ); + + var occurence = __import( "System.utils.Perf.CountSubstr" ); + + var REPL_BEFORE = 0; + var REPL_OFFSET = 1; + var REPL_LENGTH = 2; + + /** @type {Components.Vim.IAction} + * Cursor @param {Components.Vim.Cursor} + * e @param {Components.Vim.ActionEvent} + **/ + var SHIFT_LINES = function( Cursor, e ) + { + /** @type {Components.Vim.Cursor} */ + this.__cursor = Cursor; + this.__startX = Cursor.aPos; + this.__msg = ""; + + this.__slineNum = Cursor.getLine().lineNum; + + this.__lines = e.count; + debug.Info( "Open shift: " + this.__lines + " line(s) below the cursor" ); + + this.__direction = e.kMap( ">" ) ? 1 : -1; + debug.Info( "Direction is: " + ( this.__direction == 1 ? ">" : "<" ) ); + + Cursor.suppressEvent(); + }; + + SHIFT_LINES.prototype.allowMovement = true; + + SHIFT_LINES.prototype.dispose = function() + { + this.__cursor.unsuppressEvent(); + }; + + SHIFT_LINES.prototype.handler = function( e, sp ) + { + e.preventDefault(); + + if( e.ModKeys || e.kMap( "i" ) ) return; + + var cur = this.__cursor; + var feeder = cur.feeder; + + var Triggered = false; + var dir = this.__direction; + + var start = this.__slineNum; + var nline = this.__lines; + + if( 1 < e.count ) + { + nline += e.count; + } + + var end = start; + + var shiftCount = 1; + if( sp == undefined ) + { + Triggered = true; + + sp = this.__startX; + + var currAp = cur.aPos; + + if( this.__startX != currAp ) + { + if( e.kMap( "h" ) || e.kMap( "l" ) ){} + else if( e.kMap( "j" ) ) + { + end = start + nline; + } + else if( e.kMap( "k" ) ) + { + start -= nline; + } + else // TODO: Dectect movement line count + { + } + } + else + { + if( !( ( 0 < dir && ( e.kMap( ">" ) || e.kMap( "l" ) ) ) + || ( dir < 0 && ( e.kMap( "<" ) || e.kMap( "h" ) ) ) + ) ) + { + beep(); + return true; + } + } + } + + // last "\n" padding + var c = feeder.content.slice( 0, -1 ); + + var indents = c.match( /^[\t ]+/gm ); + var indentChar = "\t"; + var tabwidth = feeder.firstBuffer.tabWidth; + + if( indents ) + { + var l = indents.length - 1; + + if( 1 < l ) + { + debug.Info( "Guessing the tabstop:" ); + var tabOccr = 0; + var spOccr = 0; + + // Guess indent + var tabStat = {}; + + for( var i = 0; i < l; i ++ ) + { + var ind = indents[ i ]; + var indNext = indents[ i + 1 ]; + tabOccr += occurence( ind, "\t" ); + spOccr += occurence( ind, " " ); + var d = indNext.length - ind.length; + if( d == 0 ) continue; + + d = d < 0 ? -d : d; + + if( !tabStat[ d ] ) tabStat[ d ] = 0; + + tabStat[ d ] ++; + } + + var upperDiff = 0; + var indentCLen = 0; + for( var i in tabStat ) + { + var p = tabStat[ i ]; + if( upperDiff < p ) + { + upperDiff = p; + indentCLen = i; + } + } + + spOccr /= indentCLen; + + if( tabOccr < spOccr ) + { + indentChar = ""; + for( var i = 0; i < indentCLen; i ++ ) indentChar += " "; + } + + tabwidth = indentCLen; + + debug.Info( "\tTab count: " + tabOccr ); + debug.Info( "\tSpace count: " + spOccr ); + debug.Info( "\ti.e. indent using " + JSON.stringify( indentChar ) ); + } + else + { + debug.Info( "Not enough tabs to determine the tabstop, using default" ); + } + } + + debug.Info( "Start: " + start, "End: " + end ); + var rBlock = ""; + + var started = false; + var indentTimes = 1; + + feeder.content = ""; + nline = 0; + + for( var i = 0, j = 0; 0 <= i; i = c.indexOf( "\n", i ), j ++ ) + { + i ++; + + if( j < start ) continue; + else if( !started ) + { + started = true; + feeder.content = c.substring( 0, i - 1 ); + } + + if( end < j ) break; + + var line = c.substring( 1 < i ? i : i - 1, c.indexOf( "\n", i ) ); + + if( 1 < i ) feeder.content += "\n"; + + if( line !== "" ) + { + if( 0 < dir ) + { + feeder.content += indentChar + line; + } + else + { + for( var si = 0, sj = 1; si < indentTimes; si ++ ) + { + var startC = line[ si ]; + if( startC == " " ) + { + for( ; sj < tabwidth; sj ++ ) + { + if( !~"\t ".indexOf( line[ si + sj ] ) ) break; + } + } + else if( startC != "\t" ) break; + } + + feeder.content += line.substring( si + sj - 1 ); + } + nline ++; + } + } + + feeder.content += "\n" + c.substring( i ) + "\n"; + feeder.pan(); + + this.__msg = Mesg( "LINES_SHIFTED", nline, dir < 0 ? "<" : ">", 1 ); + + return Triggered; + }; + + SHIFT_LINES.prototype.getMessage = function() + { + return this.__msg; + }; + + ns[ NS_EXPORT ]( EX_CLASS, "SHIFT_LINES", SHIFT_LINES ); +})(); diff --git a/botanjs/src/Components/Vim/Actions/UNDO.js b/botanjs/src/Components/Vim/Actions/UNDO.js index 6f81f52..86db7cf 100644 --- a/botanjs/src/Components/Vim/Actions/UNDO.js +++ b/botanjs/src/Components/Vim/Actions/UNDO.js @@ -24,8 +24,10 @@ var stack = this.__cursor.rec.undo(); if( stack ) { + this.__cursor.suppressEvent(); stack.play(); - this.__message = "<>; before #" + stack.id + " " + stack.time; + this.__cursor.unsuppressEvent(); + this.__message = Mesg( "NCHANGES", "", stack.id, stack.time ); } else { diff --git a/botanjs/src/Components/Vim/Actions/YANK.js b/botanjs/src/Components/Vim/Actions/YANK.js index 44e3c27..9c9a37a 100644 --- a/botanjs/src/Components/Vim/Actions/YANK.js +++ b/botanjs/src/Components/Vim/Actions/YANK.js @@ -50,22 +50,18 @@ var currAp = cur.aPos; if( this.__startX != currAp ) { - // Remove to start if( e.kMap( "^" ) ) { sp --; } - // Remove char in cursor else if( e.kMap( "l" ) ) { cur.moveX( -1 ); } - // Remove char before cursor else if( e.kMap( "h" ) ) { sp = currAp; } - // Remove the current and the following line else if( e.kMap( "j" ) ) { newLine = true; @@ -75,7 +71,6 @@ cur.lineStart(); this.__startX = cur.aPos; } - // Remove the current and the preceding line else if( e.kMap( "k" ) ) { newLine = true; @@ -94,7 +89,6 @@ cur.moveTo( this.__startX ); } } - // Remove the current line else { if( e.kMap( "y" ) ) @@ -111,8 +105,6 @@ } else if( e.kMap( "^" ) ) { - // Do nothing as nothing can be removed - // since there is no successful movement return true; } // this is the same as kMap( "h" ) above diff --git a/botanjs/src/Components/Vim/Controls.js b/botanjs/src/Components/Vim/Controls.js index fac0875..c34a4d9 100644 --- a/botanjs/src/Components/Vim/Controls.js +++ b/botanjs/src/Components/Vim/Controls.js @@ -205,7 +205,7 @@ case A: // Append ccur.moveX( 1, true, true ); case I: // Insert - ccur.openAction( "INSERT" ); + ccur.openAction( "INSERT", e ); break; case S: // Delete Char and start insert @@ -213,18 +213,18 @@ { ccur.openRunAction( "DELETE", e, ccur.aPos ); } - ccur.openAction( "INSERT" ); + ccur.openAction( "INSERT", e ); break; case SHIFT + O: // new line before insert ccur.lineStart(); - ccur.openAction( "INSERT" ); + ccur.openAction( "INSERT", e ); ccur.action.handler( new ActionEvent( e.sender, "Enter" ) ); ccur.moveY( -1 ); break; case O: // new line insert ccur.lineEnd( true ); - ccur.openAction( "INSERT" ); + ccur.openAction( "INSERT", e ); ccur.action.handler( new ActionEvent( e.sender, "Enter" ) ); break; @@ -236,10 +236,10 @@ break; case D: // Del with motion - ccur.openAction( "DELETE" ); + ccur.openAction( "DELETE", e ); break; case Y: // Yank with motion - ccur.openAction( "YANK" ); + ccur.openAction( "YANK", e ); break; case P: // Put @@ -272,7 +272,7 @@ case V: // Visual case SHIFT + V: // Visual line - ccur.openAction( "VISUAL" ); + ccur.openAction( "VISUAL", e ); ccur.action.handler( e ); break; @@ -281,6 +281,11 @@ this.__divedCCmd.handler( e ); break; + case SHIFT + COMMA: // < + case SHIFT + FULLSTOP: // > + ccur.openAction( "SHIFT_LINES", e ); + break; + case F1: // F1, help break; default: @@ -349,15 +354,21 @@ var Count = e.key; var recurNum = function( e ) { + var intercept = e.ModKeys; switch( e.keyCode ) { case _0: case _1: case _2: case _3: case _4: case _5: case _6: case _7: case _8: case _9: Count += e.key; - _self.__composite( e, recurNum, ANY_KEY ); - e.cancel(); - return; + intercept = true; + } + + if( intercept ) + { + _self.__composite( e, recurNum, ANY_KEY ); + e.cancel(); + return; } e.__count = Number( Count ); diff --git a/botanjs/src/Components/Vim/Cursor.js b/botanjs/src/Components/Vim/Cursor.js index e9b6d13..f8d2dd9 100644 --- a/botanjs/src/Components/Vim/Cursor.js +++ b/botanjs/src/Components/Vim/Cursor.js @@ -376,13 +376,13 @@ // Open an action handler // i.e. YANK, VISUAL, INSERT, UNDO, etc. - Cursor.prototype.openAction = function( name ) + Cursor.prototype.openAction = function( name, e ) { if( this.action ) this.action.dispose(); debug.Info( "openAction: " + name ); - this.action = new (Actions[ name ])( this ); + this.action = new (Actions[ name ])( this, e ); this.__pulseMsg = null; this.__visualUpdate(); diff --git a/botanjs/src/Components/Vim/_this.js b/botanjs/src/Components/Vim/_this.js index ab26706..dedf3f4 100644 --- a/botanjs/src/Components/Vim/_this.js +++ b/botanjs/src/Components/Vim/_this.js @@ -19,10 +19,12 @@ VIMRE_VERSION = "1.0.0b"; , "UNDO_LIMIT": "Already at oldest change" , "REDO_LIMIT": "Already at newest change" + , "NCHANGES": "%1 change(s); before #%2 %3" , "LINES_FEWER": "%1 fewer line(s)" , "LINES_MORE": "%1 more line(s)" , "LINES_YANKED": "%1 line(s) yanked" + , "LINES_SHIFTED": "%1 line(s) %2ed %3 time(s)" , "SEARCH_HIT_BOTTOM": "search hit BOTTOM, continuing at TOP" , "SEARCH_HIT_TOP": "search hit TOP, continuing at BOTTOM"