diff --git a/botanjs/src/Components/Vim/Actions/EDITOR_COMMAND.js b/botanjs/src/Components/Vim/Actions/EDITOR_COMMAND.js index 7b2a6f1..78ff532 100644 --- a/botanjs/src/Components/Vim/Actions/EDITOR_COMMAND.js +++ b/botanjs/src/Components/Vim/Actions/EDITOR_COMMAND.js @@ -79,6 +79,9 @@ case "registers": out[ CMD_TYPE ] = "REGISTERS"; break; + case "marks": + out[ CMD_TYPE ] = "MARKS"; + break; case "ver": case "version": out[ CMD_TYPE ] = "VERSION"; @@ -90,6 +93,13 @@ case "varec": out[ CMD_TYPE ] = "VA_REC"; break; + + case "": // Range jumping + pattern.push( true ); + case "p": + allowRange = true; + out[ CMD_TYPE ] = "PRINT"; + break; } if( range !== "" ) diff --git a/botanjs/src/Components/Vim/Actions/MARKS.js b/botanjs/src/Components/Vim/Actions/MARKS.js new file mode 100644 index 0000000..4ffe01f --- /dev/null +++ b/botanjs/src/Components/Vim/Actions/MARKS.js @@ -0,0 +1,83 @@ +(function(){ + var ns = __namespace( "Components.Vim.Actions" ); + + /** @type {System.Debug} */ + var debug = __import( "System.Debug" ); + + var VimError = __import( "Components.Vim.Error" ); + var Mesg = __import( "Components.Vim.Message" ); + + /** @type {System.Debug} */ + var Marks = __import( "Components.Vim.State.Marks" ); + var Keys = Marks.Keys; + + /** @type {Components.Vim.IAction} */ + var MARKS = function( Cursor ) + { + /** @type {Components.Vim.Cursor} */ + this.__cursor = Cursor; + this.__msg = ""; + Cursor.suppressEvent(); + }; + + MARKS.prototype.dispose = function() + { + this.__cursor.unsuppressEvent(); + }; + + MARKS.prototype.handler = function( e, p ) + { + e.preventDefault(); + + /** @type {Components.Vim.State.Marks} */ + var marks = e.target.marks; + + var msg = ":marks"; + + /** + * Regarding to marks 0-9, from Vim docs + * Numbered marks '0 to '9 are quite different. They can not be set directly. + * They are only present when using a viminfo file viminfo-file. Basically '0 + * is the location of the cursor when you last exited Vim, '1 the last but one + * time, etc. Use the "r" flag in 'viminfo' to specify files for which no + * Numbered mark should be stored. See viminfo-file-marks. + * TODO: Need to redefine marks 0-9 + **/ + + // Fuck this, use silly paddings + msg += "\nmark line col file/text"; + + var feeder = this.__cursor.feeder; + for( var i = 0, j = Keys[ i ]; j != undefined; i ++, j = Keys[ i ] ) + { + var r = marks.get( j ); + if( !r ) continue; + + var line = ( r[0] + 1 ) + ""; + var col = ( r[1] + 1 ) + ""; + var t = feeder.line( r[0] - 1 ).replace( /^[\t ]+/, "" ); + + var ll = 4 - line.length; + for( var il = 0; il < ll; il ++ ) line = " " + line; + + var ll = 3 - col.length; + for( var il = 0; il < ll; il ++ ) col = " " + col; + + msg += "\n " + j + " " + line + " " + col + " " + t; + } + + 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; + }; + + MARKS.prototype.getMessage = function() + { + return this.__msg; + }; + + ns[ NS_EXPORT ]( EX_CLASS, "MARKS", MARKS ); +})(); diff --git a/botanjs/src/Components/Vim/Actions/PRINT.js b/botanjs/src/Components/Vim/Actions/PRINT.js new file mode 100644 index 0000000..7e8a9ed --- /dev/null +++ b/botanjs/src/Components/Vim/Actions/PRINT.js @@ -0,0 +1,48 @@ +(function(){ + var ns = __namespace( "Components.Vim.Actions" ); + + /** @type {System.Debug} */ + var debug = __import( "System.Debug" ); + + /** @type {Components.Vim.IAction} */ + var PRINT = function( Cursor ) + { + /** @type {Components.Vim.Cursor} */ + this.__cursor = Cursor; + this.__msg = ""; + }; + + PRINT.prototype.dispose = function() { }; + PRINT.prototype.handler = function( e, args, range ) + { + e.preventDefault(); + + if( args[0] === true ) + { + switch( range ) + { + case "%": + case "$": + this.__cursor.moveY( Number.MAX_VALUE ); + return; + case ".": + this.__cursor.lineStart( true ); + break; + case "": + default: + var lineNum = parseInt( range ) - 1; + if( lineNum ) + { + this.__cursor.gotoLine( 0 < lineNum ? lineNum : 0 ); + } + } + } + }; + + PRINT.prototype.getMessage = function() + { + return this.__msg; + }; + + ns[ NS_EXPORT ]( EX_CLASS, "PRINT", PRINT ); +})(); diff --git a/botanjs/src/Components/Vim/Controls.js b/botanjs/src/Components/Vim/Controls.js index 20654f1..7057be3 100644 --- a/botanjs/src/Components/Vim/Controls.js +++ b/botanjs/src/Components/Vim/Controls.js @@ -478,16 +478,17 @@ { var kCode = e.keyCode; - if( this.__cMovement ) + if( this.__captureComp ) { if( !e.ModKeys ) { this.__composite( e ); - this.__cMovement = false; + this.__captureComp = false; return true; } } + var inst = this.__vimArea; var ccur = this.__ccur; var cfeeder = ccur.feeder; @@ -610,10 +611,22 @@ break; + case M: + this.__captureComp = true; + + var marks = this.__vimArea.marks; + this.__composite( e, function( e2 ) { + var line = ccur.getLine().lineNum; + if( !marks.set( e2.key, line, ccur.aX ) ) + { + beep(); + } + }, ANY_KEY ); + break; case SHIFT + T: // To case T: // To - this.__cMovement = true; + this.__captureComp = true; this.__composite( e, function( e2 ) { var oX = ccur.X; @@ -632,7 +645,7 @@ break; case SHIFT + F: // To case F: // To - this.__cMovement = true; + this.__captureComp = true; this.__composite( e, function( e2 ) { ccur.openRunAction( "TO", e, e2 ); @@ -659,7 +672,7 @@ var analyzer = this.__vimArea.contentAnalyzer; - this.__cMovement = true; + this.__captureComp = true; // Word boundary this.__composite( e, function( e2 ) { @@ -703,7 +716,7 @@ case G: - this.__cMovement = true; + this.__captureComp = true; // Go to top this.__composite( e, function() { @@ -775,7 +788,7 @@ break; case SLASH: // "/" Search movement - this.__cMovement = true; + this.__captureComp = true; this.__divedCCmd = new ExCommand( ccur, "/" ); this.__divedCCmd.handler( e ); @@ -788,7 +801,7 @@ break; } - this.__cMovement = true; + this.__captureComp = true; var exCmd = new ExCommand( ccur, ":" ); exCmd.handler( e ); @@ -827,7 +840,7 @@ if( e.Escape ) { var b = false; - this.__cMovement = false; + this.__captureComp = false; if( this.__compositeReg ) { @@ -853,7 +866,7 @@ if( this.__divedCCmd.handler( e ) ) { this.__divedCCmd.dispose(); - this.__cMovement = false; + this.__captureComp = false; this.__divedCCmd = null; return; } @@ -864,7 +877,7 @@ var cfeeder = this.__cfeeder; var ccur = this.__ccur; - if( !this.__cMovement && ( !ccur.action || ccur.action.allowMovement ) ) + if( !this.__captureComp && ( !ccur.action || ccur.action.allowMovement ) ) { this.__modCommand( e ); if( e.canceled ) return; diff --git a/botanjs/src/Components/Vim/Cursor.js b/botanjs/src/Components/Vim/Cursor.js index ab3d28b..c1d7ceb 100644 --- a/botanjs/src/Components/Vim/Cursor.js +++ b/botanjs/src/Components/Vim/Cursor.js @@ -106,6 +106,8 @@ // Move to an absolute position Cursor.prototype.moveTo = function( aPos, phantomSpace, skipTabs ) { + this.__suppressUpdate(); + var content = this.feeder.content; var pline = this.getLine(); var lastLineNum = pline.lineNum; @@ -131,10 +133,7 @@ } var jumpY = expLineNum - lastLineNum; - if( jumpY ) - { - this.moveY( jumpY ); - } + if( jumpY ) this.moveY( jumpY ); pline = this.getLine(); @@ -163,6 +162,50 @@ this.moveX( - Number.MAX_VALUE, false, false, true ); this.moveX( jumpX, false, phantomSpace, skipTabs ); + + this.__unsuppressUpdate(); + }; + + // A line-only variant of moveTo, de-generalized for the sake of performance + Cursor.prototype.gotoLine = function( n ) + { + this.__suppressUpdate(); + + var content = this.feeder.content; + var pline = this.getLine(); + var lastLineNum = pline.lineNum; + + if( pline.placeholder ) + { + lastLineNum = 0; + this.Y = 0; + } + + var expLineNum = 0; + var lineStart = 0; + for( var i = content.indexOf( "\n" ); 0 <= i ; i = content.indexOf( "\n", i ) ) + { + if( expLineNum == n ) break; + lineStart = i; + i ++; + expLineNum ++; + } + + if( expLineNum < n ) n = expLineNum; + + var jumpY = expLineNum - lastLineNum; + if( jumpY ) this.moveY( jumpY ); + + pline = this.getLine(); + + if( pline.lineNum != expLineNum ) + { + this.moveY( expLineNum - pline.lineNum ); + } + + this.lineStart( true ); + + this.__unsuppressUpdate(); }; // 0 will be treated as default ( 1 ) @@ -323,7 +366,7 @@ else if( c == "\n" ) { x += d; - } + } // Wordwrap phantomSpace movement compensation on max filled lines if( feeder.wrap && boundary && !hasPhantomSpace && phantomSpace ) @@ -369,10 +412,12 @@ this.moveX( Number.MAX_VALUE, false, phantomSpace, true ); }; + // Because LineOffset is costly, suppress unnecessary calls Cursor.prototype.updatePosition = function() { - var feeder = this.feeder; - var P = this.X + LineOffset( feeder.lineBuffers, this.Y ) + this.__off; + if( 0 < this.__suppUpdate ) return; + + var P = this.X + LineOffset( this.feeder.lineBuffers, this.Y ) + this.__off; this.PStart = P; this.PEnd = P + 1; @@ -538,6 +583,13 @@ Cursor.prototype.suppressEvent = function() { ++ this.__suppEvt; }; Cursor.prototype.unsuppressEvent = function() { -- this.__suppEvt; }; + Cursor.prototype.__suppressUpdate = function() { ++ this.__suppUpdate; }; + Cursor.prototype.__unsuppressUpdate = function() + { + -- this.__suppUpdate; + this.updatePosition(); + }; + Cursor.prototype.getLine = function( display ) { var feeder = this.feeder; @@ -568,20 +620,7 @@ __readOnly( Cursor.prototype, "rawLine", function() { - var str = this.feeder.content; - var lineNum = this.getLine().lineNum - 1; - var i = str.indexOf( "\n" ), j = 0; - - for( ; 0 <= i; i = str.indexOf( "\n", i ), j ++ ) - { - if( lineNum == j ) break; - i ++; - } - - if( j == 0 && i == -1 ) i = 0; - - var end = str.indexOf( "\n", i + 1 ); - return str.substring( i + 1, end ); + return this.feeder.line( this.getLine().lineNum - 1 ); } ); // The position offset relative to current line diff --git a/botanjs/src/Components/Vim/LineFeeder.js b/botanjs/src/Components/Vim/LineFeeder.js index 4098773..b939daf 100644 --- a/botanjs/src/Components/Vim/LineFeeder.js +++ b/botanjs/src/Components/Vim/LineFeeder.js @@ -10,6 +10,7 @@ var Cursor = ns[ NS_INVOKE ]( "Cursor" ); var occurence = __import( "System.utils.Perf.CountSubstr" ); + var C_LINE = 0; var Feeder = function( rows, cols ) { @@ -35,6 +36,7 @@ this.cursor = new Cursor( this ); this.dispatcher = new EventDispatcher(); + this.__lineCache = []; this.__clseLine = null; this.__moreAt = -1; this.__rows = rows; @@ -42,7 +44,7 @@ Feeder.prototype.init = function( content, wrap ) { - this.content = content; + this.__content = content; this.setWrap( wrap ); this.firstBuffer.Push( content, this.wrap, 0 ); @@ -142,7 +144,7 @@ if( Y < 0 ) Y = 0; // Compensate the last "\n" content placeholder - var cont = this.content.slice( 0, -1 ); + var cont = this.__content.slice( 0, -1 ); if( 0 < Y ) { f = cont.indexOf( "\n" ); @@ -158,7 +160,7 @@ } } - this.firstBuffer.Push( this.content.substr( f + 1 ), this.wrap, i ); + this.firstBuffer.Push( this.__content.substr( f + 1 ), this.wrap, i ); this.panX = X; this.panY = Y; @@ -171,8 +173,27 @@ this.__softRender(); }; + Feeder.prototype.line = function( n ) + { + if( this.__lineCache[ n ] ) + return this.__lineCache[ n ]; + var str = this.__content; + var i = str.indexOf( "\n" ), j = 0; + + for( ; 0 <= i; i = str.indexOf( "\n", i ), j ++ ) + { + if( n == j ) break; + i ++; + } + + if( j == 0 && i == -1 ) i = 0; + + var end = str.indexOf( "\n", i + 1 ); + return ( this.__lineCache[ n ] = str.substring( i + 1, end ) ); + }; + __readOnly( Feeder.prototype, "linesTotal", function() { - return occurence( this.content, "\n" ); + return occurence( this.__content, "\n" ); } ); __readOnly( Feeder.prototype, "firstBuffer", function() { @@ -216,7 +237,7 @@ var i = l - X; do { - if( this.content[ i + 1 ] == "\t" ) tabs ++; + if( this.__content[ i + 1 ] == "\t" ) tabs ++; i ++; } while( i < l ) @@ -254,6 +275,15 @@ return pos; } ); + Object.defineProperty( Feeder.prototype, "content", { + get: function() { return this.__content; } + , set: function( v ) + { + this.__lineCache = []; + this.__content = v; + } + } ); + __readOnly( Feeder.prototype, "linesOccupied", function() { var line = this.firstBuffer; if( line.placeholder ) return 0; diff --git a/botanjs/src/Components/Vim/State/Marks.js b/botanjs/src/Components/Vim/State/Marks.js new file mode 100644 index 0000000..102af97 --- /dev/null +++ b/botanjs/src/Components/Vim/State/Marks.js @@ -0,0 +1,31 @@ +(function(){ + var ns = __namespace( "Components.Vim.State" ); + + /** @type {System.Debug} */ + var debug = __import( "System.Debug" ); + + var Keys = "'ABCDEFGHIJKLMNOPQRSTUVWXYabcdefghijklmnopqrstuvwxy\"[]^.<>"; + + var Marks = function() + { + this.__marks = {}; + }; + + Marks.prototype.set = function( t, line, col ) + { + if( Keys.indexOf( t ) == -1 ) return false; + + this.__marks[ t ] = [ line, col ]; + return true; + }; + + Marks.prototype.get = function( t ) + { + return this.__marks[ t ]; + }; + + __readOnly( Marks, "Keys", function() { return Keys; } ); + + ns[ NS_EXPORT ]( EX_CLASS, "Marks", Marks ); + +})(); diff --git a/botanjs/src/Components/Vim/VimArea.js b/botanjs/src/Components/Vim/VimArea.js index 27e5874..8986ce6 100644 --- a/botanjs/src/Components/Vim/VimArea.js +++ b/botanjs/src/Components/Vim/VimArea.js @@ -14,6 +14,8 @@ /** @type {Components.Vim.State.Registers} */ var Registers = __import( "Components.Vim.State.Registers" ); + /** @type {Components.Vim.State.Marks} */ + var Marks = __import( "Components.Vim.State.Marks" ); /** @type {Components.Vim.Syntax.Analyzer} */ var SyntaxAnalyzer = __import( "Components.Vim.Syntax.Analyzer" ); @@ -243,6 +245,7 @@ this.statusFeeder = sfeeder; this.statusBar = statusBar; this.registers = new Registers(); + this.marks = new Marks(); this.__cursor = cfeeder.cursor; diff --git a/botanjs/src/externs/Components.Vim.Cursor.js b/botanjs/src/externs/Components.Vim.Cursor.js index 2b60b30..634cd47 100644 --- a/botanjs/src/externs/Components.Vim.Cursor.js +++ b/botanjs/src/externs/Components.Vim.Cursor.js @@ -13,6 +13,8 @@ Components.Vim.Cursor.rec; /** @type Function */ Components.Vim.Cursor.moveTo; /** @type Function */ +Components.Vim.Cursor.gotoLine; +/** @type Function */ Components.Vim.Cursor.moveX; /** @type Function */ Components.Vim.Cursor.moveY; diff --git a/botanjs/src/externs/Components.Vim.LineFeeder.js b/botanjs/src/externs/Components.Vim.LineFeeder.js index eb500db..5edec59 100644 --- a/botanjs/src/externs/Components.Vim.LineFeeder.js +++ b/botanjs/src/externs/Components.Vim.LineFeeder.js @@ -43,6 +43,8 @@ Components.Vim.LineFeeder.linesOccupied; /** @type String */ Components.Vim.LineFeeder.docPos; /** @type String */ +Components.Vim.LineFeeder.line; +/** @type String */ Components.Vim.LineFeeder.lineStat; /** @type {String} */ Components.Vim.LineFeeder.content; diff --git a/botanjs/src/externs/Components.Vim.State.Marks.js b/botanjs/src/externs/Components.Vim.State.Marks.js new file mode 100644 index 0000000..99a4ade --- /dev/null +++ b/botanjs/src/externs/Components.Vim.State.Marks.js @@ -0,0 +1,10 @@ +/** @constructor */ +Components.Vim.State.Marks = function(){}; + +/** @type Function */ +Components.Vim.State.Marks.set; +/** @type Function */ +Components.Vim.State.Marks.get; + +/** @type String */ +Components.Vim.State.Marks.Keys; diff --git a/botanjs/src/externs/Components.Vim.VimArea.js b/botanjs/src/externs/Components.Vim.VimArea.js index 756c6c3..a2286d1 100644 --- a/botanjs/src/externs/Components.Vim.VimArea.js +++ b/botanjs/src/externs/Components.Vim.VimArea.js @@ -12,6 +12,11 @@ Components.Vim.VimArea.statusFeeder; /** @type {Components.Vim.StatusBar} */ Components.Vim.VimArea.statusBar; +/** @type {Components.Vim.State.Registers} */ +Components.Vim.VimArea.registers; +/** @type {Components.Vim.State.Marks} */ +Components.Vim.VimArea.marks; + /** @type Function */ Components.Vim.VimArea.demo; /** @type Function */