penguin/AstroJS

Javascript framework for my blog

botanjs/src/Components/Vim/VimArea.js

raw ยท 4426 bytes

(function(){
	var ns = __namespace( "Components.Vim" );

	/** @type {Dandelion.IDOMElement} */
	var IDOMElement							 = __import( "Dandelion.IDOMElement" );
	/** @type {System.utils.DataKey} */
	var DataKey								 = __import( "System.utils.DataKey" );
	/** @type {System.Cycle} */
	var Cycle								   = __import( "System.Cycle" );
	/** @type {System.Debug} */
	var debug								   = __import( "System.Debug" );

	/** @type {Components.Vim.State.Registers} */
	var Registers								 = __import( "Components.Vim.State.Registers" );
	/** @type {Components.Vim.Syntax.Analyzer} */
	var SyntaxAnalyzer							= __import( "Components.Vim.Syntax.Analyzer" );

	/** @type {Components.Vim.LineFeeder} */
	var LineFeeder = ns[ NS_INVOKE ]( "LineFeeder" );
	/** @type {Components.Vim.StatusBar} */
	var StatusBar = ns[ NS_INVOKE ]( "StatusBar" );

	var VimControls = ns[ NS_INVOKE ]( "Controls" );
	var InputEvent = ns[ NS_INVOKE ]( "InputEvent" );
	var mesg = ns[ NS_INVOKE ]( "Message" );

	var Insts = [];

	var KeyHandler = function( sender, handler )
	{
		return function( e )
		{
			e = e || window.event;
			if ( e.keyCode ) code = e.keyCode;
			else if ( e.which ) code = e.which;

			handler( sender, new InputEvent( sender, e ) );
		};
	};

	/* stage @param {Dandelion.IDOMElement} */
	var VimArea = function( stage )
	{
		if( !stage ) return;

		var element = stage.element;

		if( element.nodeName != "TEXTAREA" )
		{
			debug.Error( "Element is not compatible for VimArea" );
			return;
		}

		stage.setAttribute( new DataKey( "vimarea", 1 ) );

		this.stage = stage;
		this.rows = element.rows;
		this.cols = element.cols;

		this.__active = false;

		var _self = this;

		stage.addEventListener( "Focus", function() { _self.__active = true; } );
		stage.addEventListener( "Blur", function() { _self.__active = false; } );

		// Init
		this.VisualizeVimFrame( element.value );

		// Push this instance
		Insts.push( this );
	};

	VimArea.prototype.select = function( sel )
	{
		if( !this.__active ) return;
		var textarea = this.stage.element;

		if( sel )
		{
			textarea.selectionStart = sel.start;
			textarea.selectionEnd = sel.end;
		}
	};

	VimArea.prototype.VisualizeVimFrame = function( content )
	{
		var _self = this;

		var element = this.stage.element;
		var r = this.rows;
		var c = this.cols;

		// StatusFeeder always consumes at least 1 line
		var cRange = r - 1;

		// Content feeder
		var cfeeder = new LineFeeder( cRange, c );
		var contentAnalyzer = new SyntaxAnalyzer( cfeeder );

		// Feed the contents to content feeder
		// This "\n" fixes the last line "\n" not displaying
		// it will be trimmed after saving
		cfeeder.init( content + "\n" );

		// Status can consumes up to full screen, I think
		var sfeeder = new LineFeeder( r, c );
		sfeeder.setRender( false );

		// Set the Vim instance
		cfeeder.cursor.Vim = this;
		sfeeder.cursor.Vim = this;

		// Set the stamps
		var statusBar = new StatusBar( c );
		statusBar.stamp( -18, function(){ return cfeeder.lineStat; } );
		statusBar.stamp( -3, function(){ return mesg( cfeeder.docPos ); } );
		statusBar.stamp( 0, function(){ return cfeeder.cursor.message; } );

		sfeeder.init( statusBar.statusText );

		var Update = function()
		{
			sfeeder.init( statusBar.statusText );

			var sLine = sfeeder.linesOccupied;
			element.value =
				cfeeder.render( 0, r - sLine )
				+ "\n" + sfeeder.render( 0, sLine < r ? sLine : r );

			_self.__blink = false;
			_self.select( cfeeder.cursor.position );
		};

		cfeeder.dispatcher.addEventListener( "VisualUpdate", Update );
		Update();

		this.contentFeeder = cfeeder;
		this.contentAnalyzer = contentAnalyzer;
		this.statusFeeder = sfeeder;
		this.statusBar = statusBar;
		this.registers = new Registers();

		this.__cursor = cfeeder.cursor;

		this.__blink = true;
		Cycle.perma( "VimCursorBlinkCycle" + element.id, function()
		{
			_self.select(
				!_self.__cursor.blink || ( _self.__blink = !_self.__blink )
					? _self.__cursor.position
					: { start: 0, end: 0 }
			);
		}, 600 );

		var controls = new VimControls( this );
		this.stage.addEventListener(
			"KeyDown"
			, KeyHandler( this, controls.handler.bind( controls ) )
		);
	};

	__readOnly( VimArea, "Instances", function() {
		return Insts.slice();
	} );

	__readOnly( VimArea.prototype, "content", function() {
		return this.contentFeeder.content.slice( 0, -1 );
	} );

	ns[ NS_EXPORT ]( EX_CLASS, "VimArea", VimArea );
})();