AstroJS/botanjs/src/Components/Vim/Cursor.js

447 lines
8.1 KiB
JavaScript
Raw Normal View History

2016-03-13 00:35:29 +00:00
(function(){
var ns = __namespace( "Components.Vim" );
/** @type {System.Debug} */
2016-03-14 19:38:05 +00:00
var debug = __import( "System.Debug" );
2016-03-13 00:35:29 +00:00
/** @type {Components.Vim.State.Recorder} */
var Recorder = __import( "Components.Vim.State.Recorder" );
2016-03-14 19:06:16 +00:00
var Actions = __import( "Components.Vim.Actions.*" );
2016-03-13 00:35:29 +00:00
var LineOffset = function( buffs, l )
{
/** @type {Components.Vim.LineBuffer} */
var offset = 0;
2016-03-13 12:26:26 +00:00
LineLoop:
2016-03-13 00:35:29 +00:00
for( var i = 0, line = buffs[0];
line && i < l; i ++ )
{
while( line )
{
2016-03-13 12:26:26 +00:00
if( line.next && line.next.placeholder )
break LineLoop;
2016-03-13 00:35:29 +00:00
2016-03-14 15:28:30 +00:00
// Using toString because tab is 1 byte
// but variable width
offset += line.toString().length + 1;
2016-03-13 00:35:29 +00:00
line = line.next;
2016-03-14 15:28:30 +00:00
if( line && line.br ) break;
2016-03-13 00:35:29 +00:00
}
}
return offset;
};
2016-03-18 00:34:36 +00:00
// 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;
};
2016-03-13 12:26:26 +00:00
var Cursor = function( feeder )
2016-03-13 00:35:29 +00:00
{
2016-03-13 12:26:26 +00:00
/** @type {Components.Vim.LineFeeder} */
this.feeder = feeder;
2016-03-13 18:15:24 +00:00
this.cols = feeder.firstBuffer.cols;
2016-03-13 00:35:29 +00:00
// 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;
2016-03-14 19:06:16 +00:00
// State recorder
this.rec = new Recorder();
2016-03-14 19:06:16 +00:00
this.action = null;
this.blink = true;
this.pSpace = false;
this.__suppEvt = 0;
2016-03-13 00:35:29 +00:00
};
2016-03-22 09:35:49 +00:00
// Set by VimArea
Cursor.prototype.Vim;
2016-03-29 20:32:36 +00:00
// 0 will be treated as default ( 1 )
Cursor.prototype.moveX = function( d, penetrate, phantomSpace )
2016-03-13 00:35:29 +00:00
{
var x = this.pX;
var updatePx = Boolean( d );
if( updatePx ) x = this.X + d;
if( !d ) d = 1;
2016-03-13 12:26:26 +00:00
var buffs = this.feeder.lineBuffers;
2016-03-13 00:35:29 +00:00
2016-03-29 20:32:36 +00:00
if( penetrate )
{
2016-03-19 11:05:07 +00:00
if( x < 0 && ( 0 < this.feeder.panY || 0 < this.Y ) )
{
this.moveY( -1 );
this.lineEnd( phantomSpace );
return;
}
}
2016-03-13 00:35:29 +00:00
/** @type {Components.Vim.LineBuffer} */
2016-03-19 23:52:44 +00:00
var line = this.getLine();
2016-03-13 00:35:29 +00:00
var content = line.visualLines.join( "\n" );
var cLen = content.length;
2016-03-13 00:35:29 +00:00
var c = content[ x ];
2016-03-29 20:32:36 +00:00
// Motion includes empty lines before cursor end
if( ( phantomSpace && cLen - 1 <= x ) || ( cLen == 1 && c == undefined ) )
{
2016-03-29 20:32:36 +00:00
if( 0 < d )
{
x = cLen - 1;
if( penetrate )
{
this.X = 0;
this.moveY( 1 );
return;
}
}
else
{
x = 0;
}
}
2016-03-29 20:32:36 +00:00
// ( 2 < cLen ) motion excludes empty lines at cursor end
else if( ( 2 <= cLen && x == cLen - 1 && c == " " ) || c == undefined )
2016-03-13 00:35:29 +00:00
{
2016-03-29 20:32:36 +00:00
if( 0 < d )
{
x = cLen - 2;
if( penetrate )
{
this.X = 0;
this.moveY( 1 );
return;
}
}
else
{
x = 0;
}
2016-03-13 00:35:29 +00:00
}
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 )
2016-03-13 00:35:29 +00:00
{
this.moveX( Number.MAX_VALUE, false, phantomSpace );
2016-03-13 00:35:29 +00:00
};
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;
}
2016-03-13 18:15:24 +00:00
this.feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) );
2016-03-13 00:35:29 +00:00
};
2016-03-29 20:32:36 +00:00
Cursor.prototype.moveY = function( d, penetrate )
2016-03-13 00:35:29 +00:00
{
2016-03-18 00:34:36 +00:00
var i;
2016-03-14 15:57:17 +00:00
var Y = this.Y + d;
2016-03-18 00:34:36 +00:00
var feeder = this.feeder;
2016-03-14 15:28:30 +00:00
var line;
2016-03-13 12:26:26 +00:00
2016-03-13 18:15:24 +00:00
if( Y < 0 )
{
2016-03-18 00:34:36 +00:00
feeder.pan( undefined, d );
2016-03-14 15:57:17 +00:00
this.Y = 0;
this.moveX();
this.updatePosition();
2016-03-18 00:34:36 +00:00
feeder.softReset();
2016-03-14 15:57:17 +00:00
return;
2016-03-13 18:15:24 +00:00
}
2016-03-18 00:34:36 +00:00
// More at bottom, start panning
else if( !feeder.EOF && feeder.moreAt < Y )
2016-03-13 12:26:26 +00:00
{
2016-03-13 18:15:24 +00:00
var feeder = this.feeder;
2016-03-29 20:32:36 +00:00
if( penetrate )
2016-03-17 21:55:04 +00:00
{
feeder.pan( undefined, Y - moreAt );
}
else if( feeder.linesTotal < Y )
{
while( !feeder.EOF )
2016-03-13 18:15:24 +00:00
{
2016-03-17 21:55:04 +00:00
feeder.pan( undefined, 1 );
2016-03-13 18:15:24 +00:00
}
2016-03-18 00:34:36 +00:00
i = GetRushPos( this, d );
2016-03-13 18:15:24 +00:00
}
2016-03-17 21:55:04 +00:00
else
{
var lastLine = feeder.lastBuffer.lineNum;
var lineShift = Y - feeder.moreAt;
2016-03-19 23:52:44 +00:00
var thisLine = this.getLine().lineNum;
2016-03-13 18:15:24 +00:00
2016-03-19 23:52:44 +00:00
if( !feeder.EOF )
feeder.pan( undefined, lineShift );
// if it turns out to be the same line
// before after panning
// we keep scrolling it ( panning )
// until the entire line cosumes the screen
while( !feeder.EOF && feeder.lastBuffer.lineNum == lastLine )
2016-03-17 21:55:04 +00:00
{
2016-03-19 23:52:44 +00:00
feeder.pan( undefined, 1 );
2016-03-17 21:55:04 +00:00
}
// The line number cursor need to be in
2016-03-19 23:52:44 +00:00
Y = thisLine + d;
2016-03-14 15:28:30 +00:00
2016-03-19 23:52:44 +00:00
i = this.Y;
this.Y = 0;
2016-03-18 00:34:36 +00:00
// Calculate the visual line position "i"
2016-03-19 23:52:44 +00:00
for( var line = this.getLine();
line && line.lineNum != Y && !line.placeholder;
this.Y ++, line = this.getLine() )
2016-03-18 00:34:36 +00:00
{
}
2016-03-19 23:52:44 +00:00
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 --;
}
2016-03-13 18:15:24 +00:00
}
2016-03-13 12:26:26 +00:00
2016-03-13 18:15:24 +00:00
this.Y = i;
2016-03-13 12:26:26 +00:00
// Keep original position after panning
this.moveX();
this.updatePosition();
2016-03-13 18:15:24 +00:00
2016-03-18 00:34:36 +00:00
// Because it is panned, soft reset is needed
2016-03-13 19:33:36 +00:00
feeder.softReset();
2016-03-13 12:26:26 +00:00
return;
}
2016-03-14 15:57:17 +00:00
else if ( 0 < d )
2016-03-14 15:28:30 +00:00
{
2016-03-18 00:34:36 +00:00
var line = this.getLine();
// If already at bottom
if( line.nextLine.placeholder ) return;
Y = GetRushPos( this, d );
2016-03-14 15:28:30 +00:00
}
2016-03-13 12:26:26 +00:00
this.Y = Y;
2016-03-13 00:35:29 +00:00
this.moveX();
this.updatePosition();
};
2016-03-22 09:35:49 +00:00
// Open an action handler
// i.e. YANK, VISUAL, INSERT, UNDO, etc.
2016-03-14 21:22:37 +00:00
Cursor.prototype.openAction = function( name )
2016-03-14 19:06:16 +00:00
{
if( this.action ) this.action.dispose();
2016-03-14 21:22:37 +00:00
this.action = new (Actions[ name ])( this );
this.__pulseMsg = null;
2016-03-14 21:22:37 +00:00
this.__fireUpdate();
2016-03-14 21:22:37 +00:00
};
2016-03-14 19:06:16 +00:00
2016-03-14 21:22:37 +00:00
Cursor.prototype.closeAction = function()
{
if( !this.action ) return;
this.action.dispose();
2016-03-22 09:35:49 +00:00
this.__pulseMsg = this.action.getMessage();
2016-03-14 21:22:37 +00:00
this.action = null;
this.__fireUpdate();
};
2016-03-22 09:35:49 +00:00
// 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();
2016-03-14 21:22:37 +00:00
this.__fireUpdate();
2016-03-14 19:06:16 +00:00
};
Cursor.prototype.suppressEvent = function() { ++ this.__suppEvt; };
Cursor.prototype.unsuppressEvent = function() { -- this.__suppEvt; };
2016-03-13 18:15:24 +00:00
Cursor.prototype.getLine = function()
{
var feeder = this.feeder;
var line = feeder.firstBuffer;
2016-03-19 23:52:44 +00:00
var eBuffer = feeder.lastBuffer.next;
2016-03-13 18:15:24 +00:00
for( var i = 0;
2016-03-19 23:52:44 +00:00
line != eBuffer;
2016-03-13 18:15:24 +00:00
line = line.next )
{
if( line.br ) i ++;
2016-03-19 23:52:44 +00:00
if( this.Y == i ) return line;
2016-03-13 18:15:24 +00:00
}
2016-03-19 23:52:44 +00:00
return null;
2016-03-13 18:15:24 +00:00
};
2016-03-15 16:11:39 +00:00
// The absX for current Line
__readOnly( Cursor.prototype, "aX", function()
{
var X = this.X;
var f = this.feeder;
var w = 1;
2016-03-15 16:11:39 +00:00
// Calculate wordwrap offset
if( f.wrap )
{
var lines = this.getLine().visualLines;
2016-03-15 16:11:39 +00:00
for( var i in lines )
2016-03-15 16:11:39 +00:00
{
/** @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;
}
2016-03-15 16:11:39 +00:00
}
}
return w;
2016-03-15 16:11:39 +00:00
} );
2016-03-17 19:35:45 +00:00
// 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;
} );
2016-03-14 19:06:16 +00:00
__readOnly( Cursor.prototype, "message", function()
{
if( this.__pulseMsg )
{
var m = this.__pulseMsg;
this.__pulseMsg = null;
return m;
}
2016-03-14 19:06:16 +00:00
return this.action && this.action.getMessage();
} );
2016-03-13 00:35:29 +00:00
__readOnly( Cursor.prototype, "position", function()
{
return {
start: this.PStart
, end: this.PEnd
2016-03-13 00:35:29 +00:00
};
} );
ns[ NS_EXPORT ]( EX_CLASS, "Cursor", Cursor );
})();