diff --git a/botanjs/src/Components/Vim/Cursor.js b/botanjs/src/Components/Vim/Cursor.js new file mode 100644 index 0000000..b72ea1b --- /dev/null +++ b/botanjs/src/Components/Vim/Cursor.js @@ -0,0 +1,162 @@ +(function(){ + var ns = __namespace( "Components.Vim" ); + + /** @type {Dandelion} */ + var Dand = __import( "Dandelion" ); + /** @type {Dandelion.IDOMElement} */ + var IDOMElement = __import( "Dandelion.IDOMElement" ); + /** @type {Dandelion.IDOMObject} */ + var IDOMObject = __import( "Dandelion.IDOMObject" ); + /** @type {System.Cycle} */ + var Cycle = __import( "System.Cycle" ); + /** @type {System.Debug} */ + var debug = __import( "System.Debug" ); + + var GetLine = function( buffs, l ) + { + /** @type {Components.Vim.LineBuffer} */ + var LineHead = buffs[0]; + l ++; + + for( var i = 0, line = LineHead; + line && i < l; i ++ ) + { + LineHead = line; + while( line ) + { + line = line.next; + if( line.br ) break; + } + } + + return LineHead; + }; + + var LineOffset = function( buffs, l ) + { + /** @type {Components.Vim.LineBuffer} */ + var offset = 0; + + for( var i = 0, line = buffs[0]; + line && i < l; i ++ ) + { + while( line ) + { + if( line.br ) + { + offset += line.prev.toString().length + 1; + + // Empty line has a special space + if( line.content == "" ) offset ++; + + line = line.next; + break; + } + else + { + if( offset == 0 ) offset -= 1; + + if( line.next && !line.next.br ) + offset += line.cols + 1; + } + + line = line.next; + } + } + + debug.Info( offset ); + + return offset; + }; + + var Cursor = function( buffs ) + { + this.buffers = buffs; + this.cols = buffs[0].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.P = 0; + }; + + // Can only be 1, -1 + // 0 will be treated as undefined + Cursor.prototype.moveX = function( d ) + { + var x = this.pX; + + var updatePx = Boolean( d ); + if( updatePx ) x = this.X + d; + + if( !d ) d = 1; + + var buffs = this.buffers; + + /** @type {Components.Vim.LineBuffer} */ + var line = GetLine( buffs, this.Y ); + var content = line.visualLines.join( "\n" ); + + var c = content[ x ]; + + if( c == undefined ) + { + x = d > 0 ? content.length - 1 : 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() + { + /** @type {Components.Vim.LineBuffer} */ + this.moveX( Number.MAX_VALUE ); + }; + + Cursor.prototype.updatePosition = function() + { + this.P = this.X + LineOffset( this.buffers, this.Y ); + }; + + Cursor.prototype.moveY = function( d ) + { + this.Y += d; + this.moveX(); + this.updatePosition(); + }; + + __readOnly( Cursor.prototype, "position", function() + { + return { + start: this.P + , end: this.P + 1 + }; + } ); + + + ns[ NS_EXPORT ]( EX_CLASS, "Cursor", Cursor ); +})(); diff --git a/botanjs/src/Components/Vim/LineBuffer.js b/botanjs/src/Components/Vim/LineBuffer.js new file mode 100644 index 0000000..aa0360a --- /dev/null +++ b/botanjs/src/Components/Vim/LineBuffer.js @@ -0,0 +1,111 @@ +(function(){ + var ns = __namespace( "Components.Vim" ); + + /** @type {Dandelion} */ + var Dand = __import( "Dandelion" ); + /** @type {Dandelion.IDOMElement} */ + var IDOMElement = __import( "Dandelion.IDOMElement" ); + /** @type {Dandelion.IDOMObject} */ + var IDOMObject = __import( "Dandelion.IDOMObject" ); + /** @type {System.Cycle} */ + var Cycle = __import( "System.Cycle" ); + /** @type {System.Debug} */ + var debug = __import( "System.Debug" ); + + var LineBuffer = function( cols, nextLineBuffer ) + { + this.prev = null; + this.cols = cols; + this.next = nextLineBuffer; + this.br = false; + this.placeholder = true; + + if( nextLineBuffer ) + { + nextLineBuffer.prev = this; + } + + }; + + LineBuffer.prototype.Push = function( content, wrap ) + { + if( content == undefined || content === "" ) + { + this.content = "~"; + this.placeholder = true; + if( this.next ) this.next.Push( content, wrap ); + return; + } + + this.placeholder = false; + + var line = ""; + var br = false; + + if( wrap ) + { + for( var i = 0; i < this.cols; i ++ ) + { + var c = content[i]; + if( c === undefined ) break; + + if( c == "\n" ) + { + br = true; + i ++; + break; + } + + line += c; + } + } + else + { + br = true; + for( var i = 0; true; i ++ ) + { + var c = content[i]; + if( c === undefined ) break; + + if( c == "\n" ) + { + i ++; + break; + } + + if( i < this.cols ) + { + line += c; + } + } + } + + if( this.next ) + { + this.next.br = br; + this.next.Push( content.substr( i ), wrap ); + } + + this.content = line; + }; + + LineBuffer.prototype.toString = function() + { + return this.content || " "; + }; + + __readOnly( LineBuffer.prototype, "visualLines", function() + { + var lines = [ this ]; + var line = this; + while( ( line = line.next ) && !line.br ) + { + lines.push( line ); + } + + return lines; + } ); + + + ns[ NS_EXPORT ]( EX_CLASS, "LineBuffer", LineBuffer ); +})(); diff --git a/botanjs/src/Components/Vim/LineFeeder.js b/botanjs/src/Components/Vim/LineFeeder.js index ae8af80..2aba1f8 100644 --- a/botanjs/src/Components/Vim/LineFeeder.js +++ b/botanjs/src/Components/Vim/LineFeeder.js @@ -12,111 +12,38 @@ /** @type {System.Debug} */ var debug = __import( "System.Debug" ); - var Line = function( cols, nextLine ) - { - this.cols = cols; - this.next = nextLine; - this.br = false; - this.placeholder = true; - }; - - Line.prototype.Push = function( content, wrap ) - { - if( content == undefined || content === "" ) - { - this.content = "~"; - this.placeholder = true; - if( this.next ) this.next.Push( content, wrap ); - return; - } - - this.placeholder = false; - - var line = ""; - var br = false; - - if( wrap ) - { - for( var i = 0; i < this.cols; i ++ ) - { - var c = content[i]; - if( c === undefined ) break; - - if( c == "\n" ) - { - br = true; - i ++; - break; - } - - line += c; - } - } - else - { - br = true; - for( var i = 0; true; i ++ ) - { - var c = content[i]; - if( c === undefined ) break; - - if( c == "\n" ) - { - i ++; - break; - } - - if( i < this.cols ) - { - line += c; - } - } - } - - if( this.next ) - { - this.next.br = br; - this.next.Push( content.substr( i ), wrap ); - } - - this.content = line; - }; - - Line.prototype.toString = function() - { - return this.content; - }; + /** @type {Components.Vim.LineBuffer} */ + var LineBuffer = ns[ NS_INVOKE ]( "LineBuffer" ); + /** @type {Components.Vim.Cursor} */ + var Cursor = ns[ NS_INVOKE ]( "Cursor" ); var Feeder = function( rows, cols ) { - var lines = []; + var lineBuffers = []; // Last line - lines[ rows - 1 ] = new Line( cols ); + lineBuffers[ rows - 1 ] = new LineBuffer( cols ); for( var i = rows - 2; 0 <= i; i -- ) { - lines[i] = new Line( cols, lines[ i + 1 ] ); + lineBuffers[i] = new LineBuffer( cols, lineBuffers[ i + 1 ] ); } - this.lines = lines; + this.cursor = new Cursor( lineBuffers ); + + this.lineBuffers = lineBuffers; this.setRender(); }; Feeder.prototype.init = function( content, wrap ) { if( wrap == undefined ) wrap = true; - if( this.lines.length ) + if( this.lineBuffers.length ) { - this.lines[0].Push( content, wrap ); + this.lineBuffers[0].Push( content, wrap ); } }; - // Advance the text to number of lines - Feeder.prototype.feed = function( num ) - { - }; - Feeder.prototype.wrap = function( setwrap ) { }; @@ -159,30 +86,23 @@ } } - Feeder.prototype.cursor = function( direction ) - { - switch( direction ) - { - case 0: - return { start: 0, end: 1 }; - } - }; - Feeder.prototype.render = function( start, length ) { - if( start == undefined ) start = 0; - else if( this.lines.length < start ) return ""; + var buffs = this.lineBuffers; - if( length == undefined || ( this.lines.length - start ) < length ) - length = this.lines.length - start; + if( start == undefined ) start = 0; + else if( buffs.length < start ) return ""; + + if( length == undefined || ( buffs.length - start ) < length ) + length = buffs.length - start; if( length == 0 ) return ""; - return this.__render( this.lines[ start ], length - 1 ); + return this.__render( buffs[ start ], length - 1 ); }; __readOnly( Feeder.prototype, "linesOccupied", function() { - var line = this.lines[0]; + var line = this.lineBuffers[0]; if( line.placeholder ) return 0; var i = 0; diff --git a/botanjs/src/Components/Vim/VimArea.js b/botanjs/src/Components/Vim/VimArea.js index 791f511..159a34f 100644 --- a/botanjs/src/Components/Vim/VimArea.js +++ b/botanjs/src/Components/Vim/VimArea.js @@ -14,8 +14,9 @@ /** @type {System.Debug} */ var debug = __import( "System.Debug" ); - /** @type {Components.VimArea.LineFeeder} */ + /** @type {Components.Vim.LineFeeder} */ var LineFeeder = ns[ NS_INVOKE ]( "LineFeeder" ); + /** @type {Components.Vim.StatusBar} */ var StatusBar = ns[ NS_INVOKE ]( "StatusBar" ); var KeyHandler = function( sender, handler ) @@ -32,6 +33,11 @@ var VimControls = function( sender, e ) { + if( e.altKey + // F2 - F12 + || ( 112 < e.keyCode && e.keyCode < 124 ) + ) return; + e.preventDefault(); if( e.ctrlKey ) { @@ -39,28 +45,49 @@ return; } - var kCode = e.KeyCode + ( e.shiftKey ? 1000 : 0 ); - switch( e.KeyCode ) + var kCode = e.keyCode + ( e.shiftKey ? 1000 : 0 ); + + var cfeeder = sender.contentFeeder; + switch( kCode ) { + // Cursor movements + case 72: // h + cfeeder.cursor.moveX( -1 ); + break; + case 74: // j + cfeeder.cursor.moveY( 1 ); + break; + case 75: // k + cfeeder.cursor.moveY( -1 ); + break; + case 76: // l + cfeeder.cursor.moveX( 1 ); + break; + case 65: // a case 1065: // A break; - case 72: // h - case 1072: // H + case 1072: // H, First line buffer break; - case 74: // j - case 1074: // J + case 1076: // L, Last line buffer break; - case 75: // k - case 1075: // K - break; - case 76: // l - case 1076: // L + case 1052: // $ + cfeeder.cursor.lineEnd(); break; case 1053: // % - case 1054: // ^ break; + case 1054: // ^ + cfeeder.cursor.lineStart(); + break; + case 1074: // J, Join lines + break; + case 1075: // K, manual entry + break; + case 112: // F1, help } + + sender.__blink = false; + sender.select( cfeeder.cursor.position ); }; /* stage @param {Dandelion.IDOMElement} */ @@ -94,31 +121,23 @@ VimArea.prototype.startInput = function( mode ) { - }; - VimArea.prototype.cursor = function( x, y ) + VimArea.prototype.select = function( sel ) { - return this.__cursor(); - }; - - VimArea.prototype.flashCursor = function() - { - var _self = this; var textarea = this.stage.element; - Cycle.perma( "VimCursorFlashCycle", function() + + if( sel ) { - var cursor = _self.cursor(); - if( cursor ) - { - textarea.selectionStart = cursor.start; - textarea.selectionEnd = cursor.end; - } - }, 600 ); + textarea.selectionStart = sel.start; + textarea.selectionEnd = sel.end; + } }; VimArea.prototype.VisualizeVimFrame = function() { + var _self = this; + var element = this.stage.element; var r = this.rows; var c = this.cols; @@ -132,6 +151,7 @@ sfeeder = new LineFeeder( r, c ); sfeeder.setRender( false ); + // XXX: Placeholder var statusBar = new StatusBar( c ); statusBar.stamp( -18, function(){ return "1,1-1"; @@ -143,20 +163,24 @@ sfeeder.init( statusBar.statusText ); - element.value = cfeeder.render( 0, r - sfeeder.linesOccupied ) + "\n" + sfeeder.render(); + element.value = + cfeeder.render( 0, r - sfeeder.linesOccupied ) + + "\n" + sfeeder.render(); this.contentFeeder = cfeeder; this.statusFeeder = sfeeder; - var f = true; - this.__cursor = function() - { - if( f = !f ) - return this.contentFeeder.cursor( 0 ); - else return { start: 0, end: 0 }; - } + this.__cursor = cfeeder.cursor; - this.flashCursor(); + this.__blink = true; + Cycle.perma( "VimCursorBlinkCycle" + element.id, function() + { + _self.select( + ( _self.__blink = !_self.__blink ) + ? _self.__cursor.position + : { start: 0, end: 0 } + ); + }, 600 ); }; ns[ NS_EXPORT ]( EX_CLASS, "VimArea", VimArea ); diff --git a/botanjs/src/Components/Vim/_this.js b/botanjs/src/Components/Vim/_this.js index 706cf48..5de456f 100644 --- a/botanjs/src/Components/Vim/_this.js +++ b/botanjs/src/Components/Vim/_this.js @@ -14,6 +14,7 @@ var messages = { "INSERT": "-- INSERT --" + , "REPLACE": "-- REPLACE --" , "MORE": "-- MORE --" , "WRITE": "\"%1\" %2L, %3C written" , "CONTINUE": "Press ENTER or type command to continue"