penguin/AstroJS

Javascript framework for my blog

botanjs/src/Components/Vim/Cursor.js

raw ยท 8715 bytes

(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 );
})();