(function(){ var ns = __namespace( "Components.Vim" ); /** @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 LineOffset = function( buffs, l ) { /** @type {Components.Vim.LineBuffer} */ var offset = 0; LineLoop: for( var i = 0, line = buffs[0]; line && i < l; i ++ ) { while( line ) { if( line.next && line.next.placeholder ) break LineLoop; // Using toString because tab is 1 byte // but variable width offset += line.toString().length + 1; line = line.next; if( line && line.br ) break; } } return offset; }; // Rush cursor to wanted position "d" then get the actual position var GetRushPos = function( c, d ) { var line = c.getLine(); var l = c.Y + d; var i = c.Y; // First line ( visual ) does not count if( line != c.feeder.firstBuffer ) i --; for( ; i < l; line = line.nextLine ) { if( line.placeholder ) break; if( line.br ) i ++; } return i; }; var Cursor = function( feeder ) { /** @type {Components.Vim.LineFeeder} */ this.feeder = feeder; this.cols = feeder.firstBuffer.cols; // The preferred X position this.pX = 0; // The displaying X position this.X = 0; // The current line resided this.Y = 0; // The resulting position this.PStart = 0; this.PEnd = 1; // State recorder this.rec = new Recorder(); this.action = null; this.blink = true; this.pSpace = false; this.__suppEvt = 0; }; // Set by VimArea Cursor.prototype.Vim; // Move to an absolute position Cursor.prototype.moveTo = function( aPos ) { var content = this.feeder.content; var lastLineNum = this.getLine().lineNum; var expLineNum = 0; var lineStart = 0; for( var i = content.indexOf( "\n" ); 0 <= i ; i = content.indexOf( "\n", i ) ) { if( aPos <= i ) { break; } lineStart = i; i ++; expLineNum ++; } var jumpY = expLineNum - lastLineNum; var jumpX = aPos < lineStart ? lineStart - aPos : aPos - lineStart; if( jumpY ) this.moveY( jumpY ); if( 0 < this.getLine().lineNum && lineStart <= aPos ) jumpX --; this.moveX( - Number.MAX_VALUE ); this.moveX( jumpX ); }; // 0 will be treated as default ( 1 ) Cursor.prototype.moveX = function( d, penetrate, phantomSpace ) { var x = this.pX; var updatePx = Boolean( d ); if( updatePx ) x = this.X + d; if( !d ) d = 1; var buffs = this.feeder.lineBuffers; if( penetrate ) { if( x < 0 && ( 0 < this.feeder.panY || 0 < this.Y ) ) { this.moveY( -1 ); this.lineEnd( phantomSpace ); return; } } /** @type {Components.Vim.LineBuffer} */ var line = this.getLine(); var content = line.visualLines.join( "\n" ); var cLen = content.length; var c = content[ x ]; // Motion includes empty lines before cursor end if( ( phantomSpace && cLen - 1 <= x ) || ( cLen == 1 && c == undefined ) ) { x = 0 < d ? cLen - 1 : 0; } // ( 2 < cLen ) motion excludes empty lines at cursor end else if( ( 2 <= cLen && x == cLen - 1 && c == " " ) || c == undefined ) { x = 0 < d ? cLen - 2 : 0; } else if( c == "\n" ) { x += d; } this.X = x; if( updatePx ) { this.pX = x; this.updatePosition(); } }; Cursor.prototype.lineStart = function() { this.pX = 0; this.moveX(); this.updatePosition(); }; Cursor.prototype.lineEnd = function( phantomSpace ) { this.moveX( Number.MAX_VALUE, false, phantomSpace ); }; Cursor.prototype.updatePosition = function() { var P = this.X + LineOffset( this.feeder.lineBuffers, this.Y ); this.PStart = P; 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" ) ); }; Cursor.prototype.moveY = function( d ) { var i; var Y = this.Y + d; var feeder = this.feeder; var line; if( Y < 0 ) { feeder.pan( undefined, Y ); this.Y = 0; this.moveX(); this.updatePosition(); feeder.softReset(); return; } // More at bottom, start panning else if( !feeder.EOF && feeder.moreAt < Y ) { var feeder = this.feeder; if( feeder.linesTotal < Y ) { while( !feeder.EOF ) { feeder.pan( undefined, 1 ); } i = GetRushPos( this, d ); } else { var lastLine = feeder.lastBuffer.lineNum; var lineShift = Y - feeder.moreAt; var thisLine = this.getLine().lineNum; if( !feeder.EOF ) feeder.pan( undefined, lineShift ); // The line number cursor need to be in Y = thisLine + d; // if it turns out to be the same line // OR the cursor can not reside on the needed line // before after panning // we keep scrolling it ( panning ) // until the entire line cosumes the screen while( !feeder.EOF && ( feeder.lastBuffer.lineNum == lastLine || feeder.lastBuffer.lineNum < Y ) ) { feeder.pan( undefined, 1 ); } i = this.Y; this.Y = 0; // Calculate the visual line position "i" for( var line = this.getLine(); line && line.lineNum != Y && !line.placeholder; this.Y ++, line = this.getLine() ) { } i = this.Y; // Check if this line is collapsed if( !feeder.EOF && feeder.lastBuffer.next.lineNum == line.lineNum ) { // If yes, step back to last visible line i --; } } this.Y = i; // Keep original position after panning this.moveX(); this.updatePosition(); // Because it is panned, soft reset is needed feeder.softReset(); return; } else if ( 0 < d ) { var line = this.getLine(); // If already at bottom if( line.nextLine.placeholder ) return; Y = GetRushPos( this, d ); } this.Y = Y; this.moveX(); this.updatePosition(); }; // Open an action handler // i.e. YANK, VISUAL, INSERT, UNDO, etc. Cursor.prototype.openAction = function( name ) { if( this.action ) this.action.dispose(); this.action = new (Actions[ name ])( this ); this.__pulseMsg = null; this.__fireUpdate(); }; Cursor.prototype.closeAction = function() { if( !this.action ) return; this.action.dispose(); this.__pulseMsg = this.action.getMessage(); this.action = null; this.__fireUpdate(); }; // Open, Run, then close an action 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.__fireUpdate(); }; Cursor.prototype.suppressEvent = function() { ++ this.__suppEvt; }; Cursor.prototype.unsuppressEvent = function() { -- this.__suppEvt; }; Cursor.prototype.getLine = function() { var feeder = this.feeder; var line = feeder.firstBuffer; var eBuffer = feeder.lastBuffer.next; for( var i = 0; line != eBuffer; line = line.next ) { if( line.br ) i ++; if( this.Y == i ) return line; } return null; }; // The absX for current Line __readOnly( Cursor.prototype, "aX", function() { var X = this.X; var f = this.feeder; var w = 1; // Calculate wordwrap offset if( f.wrap ) { var lines = this.getLine().visualLines; for( var i in lines ) { /** @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 w; } ); // The absolute content position __readOnly( Cursor.prototype, "aPos", function() { var f = this.feeder; var line = this.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 += this.aX; return p; } ); __readOnly( Cursor.prototype, "message", function() { if( this.__pulseMsg ) { var m = this.__pulseMsg; this.__pulseMsg = null; return m; } return this.action && this.action.getMessage(); } ); __readOnly( Cursor.prototype, "position", function() { return { start: this.PStart , end: this.PEnd }; } ); ns[ NS_EXPORT ]( EX_CLASS, "Cursor", Cursor ); })();