penguin/AstroJS

Javascript framework for my blog

commit f1653727f2c458dfaeb168566611d9b06fe27965

author斟酌 鵬兄 <tgckpg@gmail.com>
date2016-03-16T20:57:43Z
subjectSome sort of UNDO / REDO ( not functional yet )
commit f1653727f2c458dfaeb168566611d9b06fe27965
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2016-03-16T20:57:43Z

    Some sort of UNDO / REDO ( not functional yet )
---
 botanjs/src/Components/Vim/Actions/INSERT.js      |  96 ++++++++--
 botanjs/src/Components/Vim/Actions/REDO.js        |  42 +++++
 botanjs/src/Components/Vim/Actions/UNDO.js        |   1 +
 botanjs/src/Components/Vim/Controls.js            |  80 ++++----
 botanjs/src/Components/Vim/DateTime/String.js     |  59 ++++++
 botanjs/src/Components/Vim/DateTime/_this.js      | 213 ++++++++++++++++++++++
 botanjs/src/Components/Vim/State/Recorder.js      |   7 +-
 botanjs/src/Components/Vim/State/Stack.js         |  11 ++
 botanjs/src/Components/Vim/_this.js               |   1 +
 botanjs/src/externs/Components.Vim.DateTime.js    |   7 +
 botanjs/src/externs/Components.Vim.State.Stack.js |   2 +
 11 files changed, 472 insertions(+), 47 deletions(-)

diff --git a/botanjs/src/Components/Vim/Actions/INSERT.js b/botanjs/src/Components/Vim/Actions/INSERT.js
index e082baf..367e6b0 100644
--- a/botanjs/src/Components/Vim/Actions/INSERT.js
+++ b/botanjs/src/Components/Vim/Actions/INSERT.js
@@ -1,10 +1,12 @@
 (function(){
 	var ns = __namespace( "Components.Vim.Actions" );
 
-	var Mesg = __import( "Components.Vim.Message" );
-
 	/** @type {Components.Vim.State.Stack} */
-	var Stack = __import( "Components.Vim.State.Stack" );
+	var Stack                                 = __import( "Components.Vim.State.Stack" );
+	/** @type {System.Debug} */
+	var debug                                 = __import( "System.Debug" );
+
+	var Mesg = __import( "Components.Vim.Message" );
 
 	var Translate = function( c )
 	{
@@ -55,13 +57,33 @@
 
 	INSERT.prototype.dispose = function()
 	{
-
+		this.__rec( "", true );
 	};
 
-	INSERT.prototype.__storeState = function( c, pos )
+	INSERT.prototype.__storeState = function()
 	{
+		var cur = this.__cursor;
+		var feeder = cur.feeder;
+		var insertLength = this.__insertLength;
+		var contentUndo = this.__contentUndo;
+		var startPos = this.__startPosition;
+
+		if( insertLength < 0 )
+		{
+			startPos += insertLength;
+			insertLength = 0;
+		}
+
 		return function() {
-			debug.Inf( pos, c );
+			var contentRedo = feeder.content.substr( startPos, insertLength );
+			feeder.content =
+				feeder.content.substring( 0, startPos )
+				+ contentUndo
+				+ feeder.content.substring( startPos + insertLength );
+			insertLength = contentUndo.length;
+			contentUndo = contentRedo;
+
+			feeder.pan();
 		};
 	};
 
@@ -71,21 +93,67 @@
 		{
 			if( this.__stack )
 			{
-				var c = this.__content;
+				// If nothings changed
+				if( this.__insertLength == 0
+					&& this.__contentUndo === ""
+				) return;
 
 				this.__stack.store(
-					this.__storeState( c, this.__startPosition )
+					this.__storeState()
 				);
 
-				this.__cursor.rec.store( this.__stack );
+				this.__cursor.rec.record( this.__stack );
 			}
 
-			this.__content = "";
+			this.__insertLength = 0;
+			this.__contentUndo = "";
 			this.__stack = new Stack();
 			this.__startPosition = ContentPosition( this.__cursor.feeder );
 		}
 
-		this.__content += c;
+		this.__insertLength += c.length;
+	};
+
+	INSERT.prototype.__specialKey = function( e, inputChar )
+	{
+		var cur = this.__cursor;
+		var feeder = cur.feeder;
+
+		switch( e.keyCode )
+		{
+			case 8: // Backspace
+				if( cur.X == 0 ) return;
+
+				cur.moveX( -1 );
+
+				var f = ContentPosition( feeder );
+
+				this.__contentUndo = feeder.content.substr( f, 1 ) + this.__contentUndo;
+				this.__insertLength --;
+
+				feeder.content =
+					feeder.content.substring( 0, f )
+					+ feeder.content.substring( f + 1 );
+
+				break;
+			case 46: // Delete
+				var f = ContentPosition( feeder );
+
+				this.__contentUndo += feeder.content.substr( f, 1 );
+				this.__insertLength ++;
+
+				feeder.content =
+					feeder.content.substring( 0, f )
+					+ feeder.content.substring( f + 1 );
+
+				break;
+			default:
+				// Do nothing
+				return;
+		}
+
+		feeder.pan();
+		feeder.dispatcher.dispatchEvent( new BotanEvent( "VisualUpdate" ) );
 	};
 
 	INSERT.prototype.handler = function( e )
@@ -93,7 +161,11 @@
 		e.preventDefault();
 		var inputChar = Translate( e.key );
 
-		if( inputChar.length != 1 ) return;
+		if( inputChar.length != 1 )
+		{
+			this.__specialKey( e, inputChar );
+			return;
+		}
 
 		var cur = this.__cursor;
 		var feeder = cur.feeder;
diff --git a/botanjs/src/Components/Vim/Actions/REDO.js b/botanjs/src/Components/Vim/Actions/REDO.js
new file mode 100644
index 0000000..da241d9
--- /dev/null
+++ b/botanjs/src/Components/Vim/Actions/REDO.js
@@ -0,0 +1,42 @@
+(function(){
+	var ns = __namespace( "Components.Vim.Actions" );
+
+	var Mesg = __import( "Components.Vim.Message" );
+
+	/** @type {Components.Vim.Cursor.IAction} */
+	var REDO = function( Cursor )
+	{
+		/** @type {Components.Vim.Cursor} */
+		this.__cursor = Cursor;
+		this.__message = "REDO COMMAND";
+	};
+
+	REDO.prototype.dispose = function()
+	{
+
+	};
+
+	REDO.prototype.handler = function( e )
+	{
+		e.preventDefault();
+
+		/** @type {Components.Vim.State.Stack} */
+		var stack = this.__cursor.rec.redo();
+		if( stack )
+		{
+			stack.play();
+			this.__message = "<<TODO>>; before #" + stack.id + "  " + stack.time;
+		}
+		else
+		{
+			this.__message = Mesg( "REDO_LIMIT" );
+		}
+	};
+
+	REDO.prototype.getMessage = function()
+	{
+		return this.__message;
+	};
+
+	ns[ NS_EXPORT ]( EX_CLASS, "REDO", REDO );
+})();
diff --git a/botanjs/src/Components/Vim/Actions/UNDO.js b/botanjs/src/Components/Vim/Actions/UNDO.js
index d7a76f8..0e47a00 100644
--- a/botanjs/src/Components/Vim/Actions/UNDO.js
+++ b/botanjs/src/Components/Vim/Actions/UNDO.js
@@ -25,6 +25,7 @@
 		if( stack )
 		{
 			stack.play();
+			this.__message = "<<TODO>>; before #" + stack.id + "  " + stack.time;
 		}
 		else
 		{
diff --git a/botanjs/src/Components/Vim/Controls.js b/botanjs/src/Components/Vim/Controls.js
index 7e13dc6..eacb150 100644
--- a/botanjs/src/Components/Vim/Controls.js
+++ b/botanjs/src/Components/Vim/Controls.js
@@ -3,6 +3,21 @@
 
 	var debug = __import( "System.Debug" );
 
+	var SHIFT = 1 << 9;
+	var CTRL = 1 << 10;
+
+	var BACKSPACE = 8;
+
+	var _0 = 48; var _1 = 49; var _2 = 50; var _3 = 51; var _4 = 52;
+	var _5 = 53; var _6 = 54; var _7 = 55; var _8 = 56; var _9 = 57;
+
+	var A = 65; var B = 66; var C = 67; var D = 68; var E = 69;
+	var F = 70; var G = 71; var H = 72; var I = 73; var J = 74;
+	var K = 75; var L = 76; var M = 77; var N = 78; var O = 79;
+	var P = 80; var Q = 81; var R = 82; var S = 83; var T = 84;
+	var U = 85; var V = 86; var W = 87; var X = 88; var Y = 89;
+	var Z = 90;
+
 	var Controls = function( sender, e )
 	{
 		// Neve capture these keys
@@ -15,7 +30,7 @@
 		var cfeeder = sender.contentFeeder;
 		if( cfeeder.cursor.action )
 		{
-			// Esc OR Ctrl+c
+			// Esc OR Ctrl + c
 			if( e.keyCode == 27 || ( e.ctrlKey && e.keyCode == 67 ) )
 			{
 				e.preventDefault();
@@ -29,76 +44,75 @@
 		}
 
 		e.preventDefault();
-
-		if( e.ctrlKey )
-		{
-			VimComboFunc( sender, e );
-			return;
-		}
-
-		var kCode = e.keyCode + ( e.shiftKey ? 1000 : 0 );
+		var kCode = e.keyCode
+			+ ( e.shiftKey ? SHIFT : 0 )
+			+ ( e.ctrlKey ? CTRL : 0 );
 
 		var cfeeder = sender.contentFeeder;
 		var sfeeder = sender.statusFeeder;
 		switch( kCode )
 		{
 			// Cursor movements
-			case 8: // Backspace, go back 1 char, regardless of line
+			case BACKSPACE: // Backspace, go back 1 char, regardless of line
 				break;
-			case 72: // h
+			case H: // Left
 				cfeeder.cursor.moveX( -1 );
 				break;
-			case 74: // j
-				cfeeder.cursor.moveY( 1 );
+			case L: // Right
+				cfeeder.cursor.moveX( 1 );
 				break;
-			case 75: // k
+			case K: // Up
 				cfeeder.cursor.moveY( -1 );
 				break;
-			case 76: // l
-				cfeeder.cursor.moveX( 1 );
+			case J: // Down
+				cfeeder.cursor.moveY( 1 );
 				break;
 
 			// Insert
-			case 65: // a
+			case A: // Append
+				cfeeder.cursor.moveX( 1 );
 				cfeeder.cursor.openAction( "INSERT" );
 				break;
-			case 73: // i
+			case I: // Insert
 				break;
-			case 85: // u, undo
+			case U: // Undo
 				cfeeder.cursor.openRunAction( "UNDO", e );
 				break;
-			case 88: // x, del
+			case CTRL + R: // Redo
+				cfeeder.cursor.openRunAction( "REDO", e );
+				break;
+			case X: // Del
 				break;
-			case 1065: // A, append at the line end
+			case SHIFT + A: // Append at the line end
 				break;
-			case 1088: // X, delete before
+			case SHIFT + X: // Delete before
 				break;
-			case 1085: // U, undo previous changes in oneline
+			case SHIFT + U: // Undo previous changes in oneline
 				break;
-			case 1073: // I, append before the line start, after spaces
+			case SHIFT + I: // Append before the line start, after spaces
 				break;
 
 			// remove characters
-			case 88: // x, remove in cursor
+			case X: // Remove in cursor
 				break;
-			case 1088: // X, remove before cursor
+			case SHIFT + X: // Remove before cursor
 				break;
 
-			case 1072: // H, First line buffer
+			case SHIFT + H: // First line buffer
 				break;
-			case 1076: // L, Last line buffer
+			case SHIFT + L: // Last line buffer
 				break;
-			case 1052: // $
+			case SHIFT + _4: // $, End
 				cfeeder.cursor.lineEnd();
 				break;
-			case 1053: // %
+			case SHIFT + _5: // %, Find next item
 				break;
-			case 1054: // ^
+			case SHIFT + _6: // ^, Start
 				cfeeder.cursor.lineStart();
 				break;
-			case 1074: // J, Join lines
+			case SHIFT + J: // Join lines
 				break;
-			case 1075: // K, manual entry
+			case SHIFT + K: // manual entry
 				break;
 			case 112: // F1, help
 		}
diff --git a/botanjs/src/Components/Vim/DateTime/String.js b/botanjs/src/Components/Vim/DateTime/String.js
new file mode 100644
index 0000000..d17e847
--- /dev/null
+++ b/botanjs/src/Components/Vim/DateTime/String.js
@@ -0,0 +1,59 @@
+(function(){
+	var ns = __namespace( "Components.Vim.DateTime" );
+
+	var messages = {
+		  "AboutAMinuteAgo"                 : "about a minute ago"
+		, "AboutAMonthAgo"                  : "about a month ago"
+		, "AboutAnHourAgo"                  : "about an hour ago"
+		, "AboutAWeekAgo"                   : "about a week ago"
+		, "last Friday"                     : "last Friday"
+		, "last Monday"                     : "last Monday"
+		, "last Saturday"                   : "last Saturday"
+		, "last Sunday"                     : "last Sunday"
+		, "last Thursday"                   : "last Thursday"
+		, "last Tuesday"                    : "last Tuesday"
+		, "last Wednesday"                  : "last Wednesday"
+		, "on Friday"                       : "on Friday"
+		, "on Monday"                       : "on Monday"
+		, "on Saturday"                     : "on Saturday"
+		, "on Sunday"                       : "on Sunday"
+		, "on Thursday"                     : "on Thursday"
+		, "on Tuesday"                      : "on Tuesday"
+		, "on Wednesday"                    : "on Wednesday"
+		, "OverAYearAgo"                    : "over a year ago"
+		, "XHoursAgo_2To4"                  : "%1 hours ago"
+		, "XHoursAgo_EndsIn1Not11"          : "%1 hours ago"
+		, "XHoursAgo_EndsIn2To4Not12To14"   : "%1 hours ago"
+		, "XHoursAgo_Other"                 : "%1 hours ago"
+		, "XMinutesAgo_2To4"                : "%1 minutes ago"
+		, "XMinutesAgo_EndsIn1Not11"        : "%1 minutes ago"
+		, "XMinutesAgo_EndsIn2To4Not12To14" : "%1 minutes ago"
+		, "XMinutesAgo_Other"               : "%1 minutes ago"
+		, "XMonthsAgo_2To4"                 : "%1 months ago"
+		, "XMonthsAgo_5To12"                : "%1 months ago"
+		, "XSecondsAgo_2To4"                : "%1 seconds ago"
+		, "XSecondsAgo_EndsIn1Not11"        : "%1 seconds ago"
+		, "XSecondsAgo_EndsIn2To4Not12To14" : "%1 seconds ago"
+		, "XSecondsAgo_Other"               : "%1 seconds ago"
+		, "XWeeksAgo_2To4"                  : "%1 weeks ago"
+	};
+
+	var GetString = function( arr, key, restArgs )
+	{
+		if( arr[ key ] == undefined ) return key;
+
+		var i = 0;
+		return arr[ key ].replace( /%\d+/g, function( e )
+		{
+			return restArgs[ i ++ ];
+		} );
+	};
+
+	var DateTimeString = function( key )
+	{
+		var restArgs = Array.prototype.slice.call( arguments, 1 );
+		return GetString( messages, key, restArgs );
+	};
+
+	ns[ NS_EXPORT ]( EX_FUNC, "String", DateTimeString );
+})();
diff --git a/botanjs/src/Components/Vim/DateTime/_this.js b/botanjs/src/Components/Vim/DateTime/_this.js
new file mode 100644
index 0000000..1a9312c
--- /dev/null
+++ b/botanjs/src/Components/Vim/DateTime/_this.js
@@ -0,0 +1,213 @@
+(function(){
+	var ns = __namespace( "Components.Vim.DateTime" );
+
+	var Minute = 60;
+	var Hour = 60 * Minute;
+	var Day = 24 * Hour;
+	var Week = 7 * Day;
+	var Month = 30.5 * Day;
+	var Year = 365 * Day;
+
+	var Mesg = ns[ NS_INVOKE ]( "String" );
+
+	var PluralHourStrings = [
+		"XHoursAgo_2To4",
+		"XHoursAgo_EndsIn1Not11",
+		"XHoursAgo_EndsIn2To4Not12To14",
+		"XHoursAgo_Other"
+	];
+
+	var PluralMinuteStrings = [
+		"XMinutesAgo_2To4",
+		"XMinutesAgo_EndsIn1Not11",
+		"XMinutesAgo_EndsIn2To4Not12To14",
+		"XMinutesAgo_Other"
+	];
+
+	var PluralSecondStrings = [
+		"XSecondsAgo_2To4",
+		"XSecondsAgo_EndsIn1Not11",
+		"XSecondsAgo_EndsIn2To4Not12To14",
+		"XSecondsAgo_Other"
+	];
+
+	var DayOfWeek = { Sunday: 0, Monday: 1, Tuesday: 2, Wednesday: 3, Thursday: 4, Friday: 5, Saturday: 6 };
+
+	var GetPluralMonth = function( month )
+	{
+		if ( month >= 2 && month <= 4 )
+		{
+			return Mesg( "XMonthsAgo_2To4", month );
+		}
+		else if ( month >= 5 && month <= 12 )
+		{
+			return Mesg( "XMonthsAgo_5To12", month );
+		}
+		else
+		{
+			throw new Error( "Invalid number of Months" );
+		}
+	};
+
+	var GetLastDayOfWeek = function( dow )
+	{
+		var result;
+		switch ( dow )
+		{
+			case DayOfWeek.Monday:
+				result = Mesg( "last Monday" );
+				break;
+			case DayOfWeek.Tuesday:
+				result = Mesg( "last Tuesday" );
+				break;
+			case DayOfWeek.Wednesday:
+				result = Mesg( "last Wednesday" );
+				break;
+			case DayOfWeek.Thursday:
+				result = Mesg( "last Thursday" );
+				break;
+			case DayOfWeek.Friday:
+				result = Mesg( "last Friday" );
+				break;
+			case DayOfWeek.Saturday:
+				result = Mesg( "last Saturday" );
+				break;
+			case DayOfWeek.Sunday:
+				result = Mesg( "last Sunday" );
+				break;
+			default:
+				result = Mesg( "last Sunday" );
+				break;
+		}
+
+		return result;
+	};
+
+	var GetOnDayOfWeek = function( dow )
+	{
+		var result;
+
+		switch( dow )
+		{
+			case DayOfWeek.Monday:
+				result = Mesg( "on Monday" );
+				break;
+			case DayOfWeek.Tuesday:
+				result = Mesg( "on Tuesday" );
+				break;
+			case DayOfWeek.Wednesday:
+				result = Mesg( "on Wednesday" );
+				break;
+			case DayOfWeek.Thursday:
+				result = Mesg( "on Thursday" );
+				break;
+			case DayOfWeek.Friday:
+				result = Mesg( "on Friday" );
+				break;
+			case DayOfWeek.Saturday:
+				result = Mesg( "on Saturday" );
+				break;
+			case DayOfWeek.Sunday:
+				result = Mesg( "on Sunday" );
+				break;
+			default:
+				result = Mesg( "on Sunday" );
+				break;
+		}
+
+		return result;
+	};
+
+	var GetPluralTimeUnits = function( units, resources )
+	{
+		var modTen = units % 10;
+		var modHundred = units % 100;
+
+		if ( units <= 1 )
+		{
+			throw new Error( "Invalid number of Time units" );
+		}
+		else if ( 2 <= units && units <= 4 )
+		{
+			return Mesg( resources[ 0 ], units );
+		}
+		else if ( modTen == 1 && modHundred != 11 )
+		{
+			return Mesg( resources[ 1 ], units );
+		}
+		else if ( ( 2 <= modTen && modTen <= 4 ) && !( 12 <= modHundred && modHundred <= 14 ) )
+		{
+			return Mesg( resources[ 2 ], units );
+		}
+		else
+		{
+			return Mesg( resources[ 3 ], units );
+		}
+	};
+
+	var RelativeTime = function( given )
+	{
+		var diffSecs = Math.round( 0.001 * ( new Date().getTime() - given.getTime() ) );
+
+		if( Year < diffSecs )
+		{
+			result = Mesg( "OverAYearAgo" );
+		}
+		else if( ( 1.5 * Month ) < diffSecs )
+		{
+			var nMonths = Math.round( ( diffSecs + Month / 2 ) / Month );
+			result = GetPluralMonth( nMonths );
+		}
+		else if( ( 3.5 * Week ) <= diffSecs )
+		{
+			result = Mesg( "AboutAMonthAgo" );
+		}
+		else if( Week <= diffSecs )
+		{
+			var nWeeks = Math.round( diffSecs / Week );
+			if ( 1 < nWeeks )
+			{
+				result = Mesg( "XWeeksAgo_2To4", nWeeks );
+			}
+			else
+			{
+				result = Mesg( "AboutAWeekAgo" );
+			}
+		}
+		else if ( ( 5 * Day ) <= diffSecs )
+		{
+			result = GetLastDayOfWeek( given.getDay() );
+		}
+		else if ( Day <= diffSecs )
+		{
+			result = GetOnDayOfWeek( given.getDay() );
+		}
+		else if ( ( 2 * Hour ) <= diffSecs )
+		{
+			var nHours = Math.round( diffSecs / Hour );
+			result = GetPluralTimeUnits( nHours, PluralHourStrings );
+		}
+		else if ( Hour <= diffSecs )
+		{
+			result = Mesg( "AboutAnHourAgo" );
+		}
+		else if ( ( 2 * Minute ) <= diffSecs )
+		{
+			var nMinutes = Math.round( diffSecs / Minute );
+			result = GetPluralTimeUnits( nMinutes, PluralMinuteStrings );
+		}
+		else if ( Minute <= diffSecs )
+		{
+			result = Mesg( "AboutAMinuteAgo" );
+		}
+		else
+		{
+			var nSeconds =  1 < diffSecs ? diffSecs : 2;
+			result = GetPluralTimeUnits( nSeconds, PluralSecondStrings );
+		}
+
+		return result;
+	};
+
+	ns[ NS_EXPORT ]( EX_FUNC, "RelativeTime", RelativeTime );
+})();
diff --git a/botanjs/src/Components/Vim/State/Recorder.js b/botanjs/src/Components/Vim/State/Recorder.js
index e6cfe6b..2fe9081 100644
--- a/botanjs/src/Components/Vim/State/Recorder.js
+++ b/botanjs/src/Components/Vim/State/Recorder.js
@@ -13,7 +13,8 @@
 		if( i == -1 || !this.__steps.length ) return null;
 
 
-		return this.__steps[ this.__i = i ];
+		this.__i -= 2;
+		return this.__steps[ i ];
 	};
 
 	Recorder.prototype.redo = function()
@@ -24,7 +25,7 @@
 		var State = this.__steps[ i ];
 		if( State )
 		{
-			this.__i = i;
+			this.__i += 2;
 			return State;
 		}
 
@@ -34,6 +35,8 @@
 	Recorder.prototype.record = function( StateObj )
 	{
 		this.__steps[ this.__i ] = StateObj;
+		StateObj.id = this.__i;
+
 		delete this.__steps[ ++ this.__i ];
 	};
 
diff --git a/botanjs/src/Components/Vim/State/Stack.js b/botanjs/src/Components/Vim/State/Stack.js
index a58e99d..0e7d561 100644
--- a/botanjs/src/Components/Vim/State/Stack.js
+++ b/botanjs/src/Components/Vim/State/Stack.js
@@ -1,13 +1,19 @@
 (function(){
 	var ns = __namespace( "Components.Vim.State" );
 
+	/** @type {Components.Vim.DateTime} */
+	var RelativeTime = __import( "Components.Vim.DateTime.RelativeTime" );
+
 	var Stack = function()
 	{
+
 	};
 
 	Stack.prototype.store = function( handler )
 	{
 		this.__handler = handler;
+		this.__time = new Date();
+		this.id = 0;
 	};
 
 	Stack.prototype.play = function()
@@ -15,5 +21,10 @@
 		if( this.__handler ) this.__handler();
 	};
 
+	__readOnly( Stack.prototype, "time", function()
+	{
+		return RelativeTime( this.__time );
+	} );
+
 	ns[ NS_EXPORT ]( EX_CLASS, "Stack", Stack );
 })();
diff --git a/botanjs/src/Components/Vim/_this.js b/botanjs/src/Components/Vim/_this.js
index b568ced..4c51246 100644
--- a/botanjs/src/Components/Vim/_this.js
+++ b/botanjs/src/Components/Vim/_this.js
@@ -15,6 +15,7 @@
 		, "EXIT": "Type :quit<Enter>  to exit Vim"
 
 		, "UNDO_LIMIT": "Already at oldest change"
+		, "REDO_LIMIT": "Already at newest change"
 	};
 
 	var errors = {
diff --git a/botanjs/src/externs/Components.Vim.DateTime.js b/botanjs/src/externs/Components.Vim.DateTime.js
new file mode 100644
index 0000000..9498299
--- /dev/null
+++ b/botanjs/src/externs/Components.Vim.DateTime.js
@@ -0,0 +1,7 @@
+/** @constructor */
+Components.Vim.DateTime = function(){};
+
+/** @type Function */
+Components.Vim.DateTime.RelativeTime;
+/** @type Function */
+Components.Vim.DateTime.String;
diff --git a/botanjs/src/externs/Components.Vim.State.Stack.js b/botanjs/src/externs/Components.Vim.State.Stack.js
index c572081..ea74dc2 100644
--- a/botanjs/src/externs/Components.Vim.State.Stack.js
+++ b/botanjs/src/externs/Components.Vim.State.Stack.js
@@ -5,3 +5,5 @@ Components.Vim.State.Stack = function(){};
 Components.Vim.State.Stack.play;
 /** @type Function */
 Components.Vim.State.Stack.store;
+/** @type Function */
+Components.Vim.State.Stack.time;