@@ -40,6 +40,15 @@ export interface CommitParams extends CommandParams {
4040 commandNames : string [ ] ;
4141}
4242
43+ export type ExecCommandFunction = < P extends JWPlugin , C extends Commands < P > = Commands < P > > (
44+ commandName : C | ( ( context : Context ) => Promise < void > | void ) ,
45+ params ?: CommandParamsType < P , C > ,
46+ ) => Promise < void > ;
47+
48+ export interface ExecutionContext {
49+ execCommand : ExecCommandFunction ;
50+ }
51+
4352export interface JWEditorConfig {
4453 /**
4554 * The modes that the editor will support.
@@ -322,6 +331,12 @@ export class JWEditor {
322331 }
323332 }
324333
334+ async nextEventMutex ( next : ( execCommand : ExecCommandFunction ) => void ) : Promise < void > {
335+ return ( this . _mutex = this . _mutex . then (
336+ next . bind ( undefined , this . _withOpenMemory . bind ( this ) ) ,
337+ ) ) ;
338+ }
339+
325340 /**
326341 * Execute arbitrary code in `callback`, then dispatch the commit event.
327342 * The callback receives an `execCommand` parameter which it can use to
@@ -355,103 +370,9 @@ export class JWEditor {
355370 commandName : C | ( ( context : Context ) => Promise < void > | void ) ,
356371 params ?: CommandParamsType < P , C > ,
357372 ) : Promise < void > {
358- this . _mutex = this . _mutex . then ( async ( ) => {
359- if ( ! this . memory . isFrozen ( ) ) {
360- console . error (
361- 'You are trying to call the external editor' +
362- ' execCommand method from within an execCommand. ' +
363- 'Use the `execCommand` method of your plugin instead.' ,
364- ) ;
365- return ;
366- }
367- const timeout = setTimeout ( ( ) => {
368- console . warn (
369- 'An execCommand call is taking more than 10 seconds to finish. It might be caused by a deadlock.\n' +
370- 'Verify that you do not call editor.execCommand inside another editor.execCommand.' ,
371- ) ;
372- } , 10000 ) ;
373-
374- // Switch to the next memory slice (unfreeze the memory).
375- const origin = this . memory . sliceKey ;
376- const memorySlice = this . _memoryID . toString ( ) ;
377- this . memory . switchTo ( memorySlice ) ;
378- this . memoryInfo . commandNames = new VersionableArray ( ) ;
379- this . memoryInfo . uiCommand = false ;
380- let commandNames = this . memoryInfo . commandNames ;
381-
382- try {
383- // Execute command.
384- if ( typeof commandName === 'function' ) {
385- const name = '@custom' + ( commandName . name ? ':' + commandName . name : '' ) ;
386- this . memoryInfo . commandNames . push ( name ) ;
387- await commandName ( this . contextManager . defaultContext ) ;
388- if ( this . memory . sliceKey !== memorySlice ) {
389- // Override by the current commandName if the slice changed.
390- commandNames = [ name ] ;
391- }
392- } else {
393- this . memoryInfo . commandNames . push ( commandName ) ;
394- await this . dispatcher . dispatch ( commandName , params ) ;
395- if ( this . memory . sliceKey !== memorySlice ) {
396- // Override by the current commandName if the slice changed.
397- commandNames = [ commandName ] ;
398- }
399- }
400-
401- // Prepare nex slice and freeze the memory.
402- this . _memoryID ++ ;
403- const nextMemorySlice = this . _memoryID . toString ( ) ;
404- this . memory . create ( nextMemorySlice ) ;
405-
406- // Send the commit message with a frozen memory.
407- const changesLocations = this . memory . getChangesLocations (
408- memorySlice ,
409- this . memory . sliceKey ,
410- ) ;
411- await this . dispatcher . dispatchHooks ( '@commit' , {
412- changesLocations : changesLocations ,
413- commandNames : commandNames ,
414- } ) ;
415- clearTimeout ( timeout ) ;
416- } catch ( error ) {
417- clearTimeout ( timeout ) ;
418- if ( this . _stage !== EditorStage . EDITION ) {
419- throw error ;
420- }
421- console . error ( error ) ;
422-
423- await this . dispatcher . dispatchHooks ( '@error' , {
424- message : error . message ,
425- stack : error . stack ,
426- } ) ;
427-
428- const failedSlice = this . memory . sliceKey ;
429-
430- // When an error occurs, we go back to part of the functional memory.
431- this . memory . switchTo ( origin ) ;
432-
433- try {
434- // Send the commit message with a frozen memory.
435- const changesLocations = this . memory . getChangesLocations ( failedSlice , origin ) ;
436- await this . dispatcher . dispatchHooks ( '@commit' , {
437- changesLocations : changesLocations ,
438- commandNames : commandNames ,
439- } ) ;
440- } catch ( revertError ) {
441- if ( this . _stage !== EditorStage . EDITION ) {
442- throw revertError ;
443- }
444-
445- await this . dispatcher . dispatchHooks ( '@error' , {
446- message : error . message ,
447- stack : error . stack ,
448- } ) ;
449-
450- console . error ( revertError ) ;
451- }
452- }
373+ return this . nextEventMutex ( async ( ) => {
374+ return this . _withOpenMemory ( commandName , params ) ;
453375 } ) ;
454- return this . _mutex ;
455376 }
456377
457378 /**
@@ -492,13 +413,13 @@ export class JWEditor {
492413 this . memoryInfo . commandNames . push ( '@withRange' ) ;
493414 let range : VRange ;
494415 if ( typeof commandName === 'function' ) {
495- range = new VRange ( this , bounds , params as Mode ) ;
416+ range = new VRange ( this , bounds , { mode : params as Mode } ) ;
496417 this . memoryInfo . commandNames . push (
497418 '@custom' + ( commandName . name ? ':' + commandName . name : '' ) ,
498419 ) ;
499420 await commandName ( { ...this . contextManager . defaultContext , range } ) ;
500421 } else {
501- range = new VRange ( this , bounds , mode ) ;
422+ range = new VRange ( this , bounds , { mode } ) ;
502423 this . memoryInfo . commandNames . push ( commandName ) ;
503424 const newParam = Object . assign ( { context : { } } , params as CommandParamsType < P , C > ) ;
504425 newParam . context . range = range ;
@@ -538,6 +459,105 @@ export class JWEditor {
538459 this . _stage = EditorStage . CONFIGURATION ;
539460 }
540461
462+ private async _withOpenMemory < P extends JWPlugin , C extends Commands < P > = Commands < P > > (
463+ commandName : C | ( ( context : Context ) => Promise < void > | void ) ,
464+ params ?: CommandParamsType < P , C > ,
465+ ) : Promise < void > {
466+ if ( ! this . memory . isFrozen ( ) ) {
467+ console . error (
468+ 'You are trying to call the external editor' +
469+ ' execCommand method from within an execCommand. ' +
470+ 'Use the `execCommand` method of your plugin instead.' ,
471+ ) ;
472+ return ;
473+ }
474+ const timeout = setTimeout ( ( ) => {
475+ console . warn (
476+ 'An execCommand call is taking more than 10 seconds to finish. It might be caused by a deadlock.\n' +
477+ 'Verify that you do not call editor.execCommand inside another editor.execCommand.' ,
478+ ) ;
479+ } , 10000 ) ;
480+
481+ // Switch to the next memory slice (unfreeze the memory).
482+ const origin = this . memory . sliceKey ;
483+ const memorySlice = this . _memoryID . toString ( ) ;
484+ this . memory . switchTo ( memorySlice ) ;
485+ this . memoryInfo . commandNames = new VersionableArray ( ) ;
486+ this . memoryInfo . uiCommand = false ;
487+ let commandNames = this . memoryInfo . commandNames ;
488+
489+ try {
490+ // Execute command.
491+ if ( typeof commandName === 'function' ) {
492+ const name = '@custom' + ( commandName . name ? ':' + commandName . name : '' ) ;
493+ this . memoryInfo . commandNames . push ( name ) ;
494+ await commandName ( this . contextManager . defaultContext ) ;
495+ if ( this . memory . sliceKey !== memorySlice ) {
496+ // Override by the current commandName if the slice changed.
497+ commandNames = [ name ] ;
498+ }
499+ } else {
500+ this . memoryInfo . commandNames . push ( commandName ) ;
501+ await this . dispatcher . dispatch ( commandName , params ) ;
502+ if ( this . memory . sliceKey !== memorySlice ) {
503+ // Override by the current commandName if the slice changed.
504+ commandNames = [ commandName ] ;
505+ }
506+ }
507+
508+ // Prepare nex slice and freeze the memory.
509+ this . _memoryID ++ ;
510+ const nextMemorySlice = this . _memoryID . toString ( ) ;
511+ this . memory . create ( nextMemorySlice ) ;
512+
513+ // Send the commit message with a frozen memory.
514+ const changesLocations = this . memory . getChangesLocations (
515+ memorySlice ,
516+ this . memory . sliceKey ,
517+ ) ;
518+ await this . dispatcher . dispatchHooks ( '@commit' , {
519+ changesLocations : changesLocations ,
520+ commandNames : commandNames ,
521+ } ) ;
522+ clearTimeout ( timeout ) ;
523+ } catch ( error ) {
524+ clearTimeout ( timeout ) ;
525+ if ( this . _stage !== EditorStage . EDITION ) {
526+ throw error ;
527+ }
528+ console . error ( error ) ;
529+
530+ await this . dispatcher . dispatchHooks ( '@error' , {
531+ message : error . message ,
532+ stack : error . stack ,
533+ } ) ;
534+
535+ const failedSlice = this . memory . sliceKey ;
536+
537+ // When an error occurs, we go back to part of the functional memory.
538+ this . memory . switchTo ( origin ) ;
539+
540+ try {
541+ // Send the commit message with a frozen memory.
542+ const changesLocations = this . memory . getChangesLocations ( failedSlice , origin ) ;
543+ await this . dispatcher . dispatchHooks ( '@commit' , {
544+ changesLocations : changesLocations ,
545+ commandNames : commandNames ,
546+ } ) ;
547+ } catch ( revertError ) {
548+ if ( this . _stage !== EditorStage . EDITION ) {
549+ throw revertError ;
550+ }
551+
552+ await this . dispatcher . dispatchHooks ( '@error' , {
553+ message : error . message ,
554+ stack : error . stack ,
555+ } ) ;
556+
557+ console . error ( revertError ) ;
558+ }
559+ }
560+ }
541561 /**
542562 * Execute the command or arbitrary code in `callback` in memory.
543563 *
0 commit comments