1- import { Dispatcher } from './Dispatcher' ;
1+ import { Dispatcher , CommandParams } from './Dispatcher' ;
22import { JWPlugin , JWPluginConfig } from './JWPlugin' ;
33import { Core } from './Core' ;
44import { ContextManager } from './ContextManager' ;
55import { VSelection } from './VSelection' ;
6+ import { VRange } from './VRange' ;
67import { isConstructor } from '../../utils/src/utils' ;
78import { Keymap } from '../../plugin-keymap/src/Keymap' ;
89import { StageError } from '../../utils/src/errors' ;
910import { ContainerNode } from './VNodes/ContainerNode' ;
1011import { AtomicNode } from './VNodes/AtomicNode' ;
1112import { SeparatorNode } from './VNodes/SeparatorNode' ;
1213import { ModeIdentifier , ModeDefinition , Mode } from './Mode' ;
14+ import { Memory , ChangesLocations } from './Memory/Memory' ;
15+ import { makeVersionable } from './Memory/Versionable' ;
16+ import { VersionableArray } from './Memory/VersionableArray' ;
17+ import { Point } from './VNodes/VNode' ;
1318
1419export enum EditorStage {
1520 CONFIGURATION = 'configuration' ,
@@ -26,9 +31,13 @@ export type Loadables<T extends JWPlugin = JWPlugin> = {
2631} ;
2732
2833type Commands < T extends JWPlugin > = Extract < keyof T [ 'commands' ] , string > ;
29- type CommandParams < T extends JWPlugin , K extends string > = K extends Commands < T >
34+ type CommandParamsType < T extends JWPlugin , K extends string > = K extends Commands < T >
3035 ? Parameters < T [ 'commands' ] [ K ] [ 'handler' ] > [ 0 ]
3136 : never ;
37+ export interface CommitParams extends CommandParams {
38+ changesLocations : ChangesLocations ;
39+ commandNames : string [ ] ;
40+ }
3241
3342export interface JWEditorConfig {
3443 /**
@@ -68,7 +77,10 @@ export class JWEditor {
6877 plugins : [ ] ,
6978 loadables : { } ,
7079 } ;
71- selection = new VSelection ( this ) ;
80+ memory : Memory ;
81+ memoryInfo : { commandNames : string [ ] } ;
82+ private _memoryID = 0 ;
83+ selection : VSelection ;
7284 loaders : Record < string , Loader > = { } ;
7385 private mutex = Promise . resolve ( ) ;
7486 // Use a set so that when asynchronous functions are called we ensure that
@@ -86,6 +98,7 @@ export class JWEditor {
8698 constructor ( ) {
8799 this . dispatcher = new Dispatcher ( this ) ;
88100 this . plugins = new Map ( ) ;
101+ this . selection = new VSelection ( this ) ;
89102 this . contextManager = new ContextManager ( this ) ;
90103
91104 this . nextEventMutex = this . nextEventMutex . bind ( this ) ;
@@ -130,9 +143,18 @@ export class JWEditor {
130143 this . setMode ( this . configuration . mode ) ;
131144 }
132145
133- for ( const plugin of this . plugins . values ( ) ) {
134- await plugin . start ( ) ;
135- }
146+ // create memory
147+ this . memoryInfo = makeVersionable ( { commandNames : [ ] } ) ;
148+ this . memory = new Memory ( ) ;
149+ this . memory . attach ( this . memoryInfo ) ;
150+ this . memory . create ( this . _memoryID . toString ( ) ) ;
151+
152+ // Start all plugins in the first memory slice.
153+ return this . execCommand ( async ( ) => {
154+ for ( const plugin of this . plugins . values ( ) ) {
155+ await plugin . start ( ) ;
156+ }
157+ } ) ;
136158 }
137159
138160 //--------------------------------------------------------------------------
@@ -303,13 +325,12 @@ export class JWEditor {
303325 }
304326 }
305327
306- async execBatch ( callback : ( ) => Promise < void > ) : Promise < void > {
307- this . preventRenders . add ( callback ) ;
308- await callback ( ) ;
309- this . preventRenders . delete ( callback ) ;
310- await this . dispatcher . dispatchHooks ( '@batch' ) ;
311- }
312-
328+ /**
329+ * Execute arbitrary code in `callback`, then dispatch the commit event.
330+ *
331+ * @param callback
332+ */
333+ async execCommand ( callback : ( ) => Promise < void > | void ) : Promise < void > ;
313334 /**
314335 * Execute the given command.
315336 *
@@ -318,28 +339,101 @@ export class JWEditor {
318339 */
319340 async execCommand < P extends JWPlugin , C extends Commands < P > = Commands < P > > (
320341 commandName : C ,
321- params ?: CommandParams < P , C > ,
342+ params ?: CommandParamsType < P , C > ,
343+ ) : Promise < void > ;
344+ /**
345+ * Execute the command or arbitrary code in `callback` in memory.
346+ *
347+ * TODO: create memory for each plugin who use the command then use
348+ * squashInto(winnerSliceKey, winnerSliceKey, newMasterSliceKey)
349+ *
350+ * @param commandName name identifier of the command to execute or callback
351+ * @param params arguments object of the command to execute
352+ */
353+ async execCommand < P extends JWPlugin , C extends Commands < P > = Commands < P > > (
354+ commandName : C | ( ( ) => Promise < void > | void ) ,
355+ params ?: CommandParamsType < P , C > ,
322356 ) : Promise < void > {
323- return await this . dispatcher . dispatch ( commandName , params ) ;
357+ const isFrozen = this . memory . isFrozen ( ) ;
358+
359+ let memorySlice : string ;
360+ if ( isFrozen ) {
361+ // Switch to the next memory slice (unfreeze the memory).
362+ memorySlice = this . _memoryID . toString ( ) ;
363+ this . memory . switchTo ( memorySlice ) ;
364+ this . memoryInfo . commandNames = new VersionableArray ( ) ;
365+ }
366+
367+ // Execute command.
368+ if ( typeof commandName === 'function' ) {
369+ this . memoryInfo . commandNames . push ( '@custom' ) ;
370+ await commandName ( ) ;
371+ } else {
372+ this . memoryInfo . commandNames . push ( commandName ) ;
373+ await this . dispatcher . dispatch ( commandName , params ) ;
374+ }
375+
376+ if ( isFrozen ) {
377+ // Check if it's frozen for calling execCommand inside a call of
378+ // execCommand Create the next memory slice (and freeze the
379+ // current memory).
380+ this . _memoryID ++ ;
381+ const nextMemorySlice = this . _memoryID . toString ( ) ;
382+ this . memory . create ( nextMemorySlice ) ;
383+
384+ // Send the commit message with a froozen memory.
385+ const changesLocations = this . memory . getChangesLocations (
386+ memorySlice ,
387+ this . memory . sliceKey ,
388+ ) ;
389+ await this . dispatcher . dispatchHooks ( '@commit' , {
390+ changesLocations : changesLocations ,
391+ commandNames : this . memoryInfo . commandNames ,
392+ } ) ;
393+ }
324394 }
325395
326396 /**
327- * Execute arbitrary code in `callback`, then dispatch the event.
397+ * Create a temporary range corresponding to the given boundary points and
398+ * call the given callback with the newly created range as argument. The
399+ * range is automatically destroyed after calling the callback.
400+ *
401+ * @param bounds The points corresponding to the range boundaries.
402+ * @param callback The callback to call with the newly created range.
403+ * @param mode
328404 */
329- async execCustomCommand < P extends JWPlugin , C extends Commands < P > = Commands < P > > (
330- callback : ( ) => Promise < void > ,
405+ async withRange (
406+ bounds : [ Point , Point ] ,
407+ callback : ( range : VRange ) => Promise < void > | void ,
408+ mode ?: Mode ,
331409 ) : Promise < void > {
332- await callback ( ) ;
333- await this . dispatcher . dispatchHooks ( '@custom' ) ;
410+ return this . execCommand ( async ( ) => {
411+ this . memoryInfo . commandNames . push ( '@withRange' ) ;
412+ const range = new VRange ( this , bounds , mode ) ;
413+ await callback ( range ) ;
414+ range . remove ( ) ;
415+ } ) ;
334416 }
335417
336418 /**
337419 * Stop this editor instance.
338420 */
339421 async stop ( ) : Promise < void > {
422+ if ( this . memory ) {
423+ this . memory . create ( 'stop' ) ;
424+ this . memory . switchTo ( 'stop' ) ; // Unfreeze the memory.
425+ }
340426 for ( const plugin of this . plugins . values ( ) ) {
341427 await plugin . stop ( ) ;
342428 }
429+ if ( this . memory ) {
430+ this . memory . create ( 'stopped' ) ; // Freeze the memory.
431+ this . memory = null ;
432+ }
433+ this . plugins . clear ( ) ;
434+ this . dispatcher = new Dispatcher ( this ) ;
435+ this . selection = new VSelection ( this ) ;
436+ this . contextManager = new ContextManager ( this ) ;
343437 // Clear loaders.
344438 this . loaders = { } ;
345439 this . _stage = EditorStage . CONFIGURATION ;
0 commit comments