From f1653727f2c458dfaeb168566611d9b06fe27965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9F=E9=85=8C=20=E9=B5=AC=E5=85=84?= Date: Thu, 17 Mar 2016 04:57:43 +0800 Subject: [PATCH] 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 + .../src/externs/Components.Vim.DateTime.js | 7 + .../src/externs/Components.Vim.State.Stack.js | 2 + 11 files changed, 472 insertions(+), 47 deletions(-) create mode 100644 botanjs/src/Components/Vim/Actions/REDO.js create mode 100644 botanjs/src/Components/Vim/DateTime/String.js create mode 100644 botanjs/src/Components/Vim/DateTime/_this.js create mode 100644 botanjs/src/externs/Components.Vim.DateTime.js 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 = "<>; 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 = "<>; 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 1065: // A, append at the line end + case X: // Del break; - case 1088: // X, delete before + case SHIFT + A: // Append at the line end break; - case 1085: // U, undo previous changes in oneline + case SHIFT + X: // Delete before break; - case 1073: // I, append before the line start, after spaces + case SHIFT + U: // Undo previous changes in oneline + break; + 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 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;