@@ -10,7 +10,7 @@ import { actions, dispatch } from 'codesandbox-api';
1010import { css } from 'glamor' ;
1111import { TextOperation } from 'ot' ;
1212
13- import { getCurrentModelPath , getModel } from './utils' ;
13+ import { getCurrentModel , getCurrentModelPath } from './utils' ;
1414
1515// @ts -ignore
1616const fadeIn = css . keyframes ( 'fadeIn' , {
@@ -45,8 +45,16 @@ export type OnFileChangeCallback = (data: OnFileChangeData) => void;
4545
4646export type OnOperationAppliedCallback = ( data : OnOperationAppliedData ) => void ;
4747
48+ export type ModuleModel = {
49+ changeListener : { dispose : Function } ;
50+ selections : any [ ] ;
51+ path : string ;
52+ model : Promise < any > ;
53+ } ;
54+
4855export class ModelsHandler {
4956 public isApplyingOperation : boolean = false ;
57+ private moduleModels : { [ path : string ] : ModuleModel } = { } ;
5058 private modelAddedListener : { dispose : Function } ;
5159 private modelRemovedListener : { dispose : Function } ;
5260 private onChangeCallback : OnFileChangeCallback ;
@@ -56,15 +64,6 @@ export class ModelsHandler {
5664 private monaco ;
5765 private userClassesGenerated = { } ;
5866 private userSelectionDecorations = { } ;
59- private modelListeners : {
60- [ path : string ] : {
61- moduleShortid : string ;
62- model : any ;
63- listener : {
64- dispose : Function ;
65- } ;
66- } ;
67- } = { } ;
6867
6968 constructor (
7069 editorApi ,
@@ -84,21 +83,33 @@ export class ModelsHandler {
8483 public dispose ( ) : null {
8584 this . modelAddedListener . dispose ( ) ;
8685 this . modelRemovedListener . dispose ( ) ;
87- Object . keys ( this . modelListeners ) . forEach ( p => {
88- this . modelListeners [ p ] . listener . dispose ( ) ;
86+ Object . keys ( this . moduleModels ) . forEach ( path => {
87+ if ( this . moduleModels [ path ] . changeListener ) {
88+ this . moduleModels [ path ] . changeListener . dispose ( ) ;
89+ }
8990 } ) ;
90- this . modelListeners = { } ;
91+ this . moduleModels = { } ;
9192
9293 return null ;
9394 }
9495
9596 public changeModule = async ( module : Module ) => {
97+ const moduleModel = this . getModuleModel ( module ) ;
98+
9699 if ( getCurrentModelPath ( this . editorApi ) !== module . path ) {
97100 const file = await this . editorApi . openFile ( module . path ) ;
98- return file . getModel ( ) ;
101+ const model = file . getModel ( ) ;
102+
103+ this . updateUserSelections ( module , moduleModel . selections ) ;
104+
105+ moduleModel . model = Promise . resolve ( model ) ;
106+ } else {
107+ const model = getCurrentModel ( this . editorApi ) ;
108+
109+ moduleModel . model = Promise . resolve ( model ) ;
99110 }
100111
101- return Promise . resolve ( getModel ( this . editorApi ) ) ;
112+ return moduleModel . model ;
102113 } ;
103114
104115 public async applyOperation ( moduleShortid : string , operation : any ) {
@@ -108,43 +119,53 @@ export class ModelsHandler {
108119 return ;
109120 }
110121
111- const modulePath = '/sandbox' + module . path ;
122+ const moduleModel = this . getModuleModel ( module ) ;
112123
113124 const modelEditor = this . editorApi . editorService . editors . find (
114- editor => editor . resource && editor . resource . path === modulePath
125+ editor => editor . resource && editor . resource . path === moduleModel . path
115126 ) ;
116127
117- let model ;
118-
119- if ( modelEditor ) {
120- model = ( await modelEditor . textModelReference ) . object ;
121- } else {
122- model = await this . editorApi . textFileService . models . loadOrCreate (
123- this . monaco . Uri . file ( modulePath )
124- ) ;
128+ // We keep a reference to the model on our own. We keep it as a
129+ // promise, because there might be multiple operations fired before
130+ // the model is actually resolved. This creates a "natural" queue
131+ if ( ! moduleModel . model ) {
132+ if ( modelEditor ) {
133+ moduleModel . model = modelEditor . textModelReference . then (
134+ ref => ref . object . textEditorModel
135+ ) ;
136+ } else {
137+ moduleModel . model = this . editorApi . textFileService . models
138+ . loadOrCreate ( this . monaco . Uri . file ( moduleModel . path ) )
139+ . then ( model => model . textEditorModel ) ;
140+ }
125141 }
126142
143+ const model = await moduleModel . model ;
144+
127145 this . isApplyingOperation = true ;
128- this . applyOperationToModel ( operation , false , model . textEditorModel ) ;
146+ this . applyOperationToModel ( operation , false , model ) ;
129147 this . isApplyingOperation = false ;
130148 this . onOperationAppliedCallback ( {
131- code : model . textEditorModel . getValue ( ) ,
149+ code : model . getValue ( ) ,
132150 moduleShortid : module . shortid ,
133151 title : module . title ,
134- model : model . textEditorModel ,
152+ model,
135153 } ) ;
136154 }
137155
138- public updateUserSelections (
156+ public async updateUserSelections (
139157 module ,
140158 userSelections : Array < UserSelection | EditorSelection >
141159 ) {
142- const model = getModel ( this . editorApi ) ;
160+ const moduleModel = this . getModuleModel ( module ) ;
161+
162+ moduleModel . selections = userSelections ;
143163
144- if ( ! model ) {
164+ if ( ! moduleModel . model ) {
145165 return ;
146166 }
147167
168+ const model = await moduleModel . model ;
148169 const lines = model . getLinesContent ( ) || [ ] ;
149170 const activeEditor = this . editorApi . getActiveCodeEditor ( ) ;
150171
@@ -394,41 +415,35 @@ export class ModelsHandler {
394415 private listenForChanges ( ) {
395416 this . modelAddedListener = this . editorApi . textFileService . modelService . onModelAdded (
396417 model => {
397- if ( this . modelListeners [ model . uri . path ] === undefined ) {
398- let module : Module ;
399- try {
400- module = resolveModule (
401- model . uri . path . replace ( / ^ \/ s a n d b o x / , '' ) ,
402- this . sandbox . modules ,
403- this . sandbox . directories
404- ) ;
405- } catch ( e ) {
406- return ;
407- }
418+ try {
419+ const module = resolveModule (
420+ model . uri . path . replace ( / ^ \/ s a n d b o x / , '' ) ,
421+ this . sandbox . modules ,
422+ this . sandbox . directories
423+ ) ;
424+
425+ const moduleModel = this . getModuleModel ( module ) ;
408426
409- const listener = this . getModelContentChangeListener (
427+ moduleModel . model = model ;
428+ moduleModel . changeListener = this . getModelContentChangeListener (
410429 this . sandbox ,
411430 model
412431 ) ;
413-
414- this . modelListeners [ model . uri . path ] = {
415- moduleShortid : module . shortid ,
416- model,
417- listener,
418- } ;
432+ } catch ( e ) {
433+ return ;
419434 }
420435 }
421436 ) ;
422437
423438 this . modelRemovedListener = this . editorApi . textFileService . modelService . onModelRemoved (
424439 model => {
425- if ( this . modelListeners [ model . uri . path ] ) {
426- this . modelListeners [ model . uri . path ] . listener . dispose ( ) ;
440+ if ( this . moduleModels [ model . uri . path ] ) {
441+ this . moduleModels [ model . uri . path ] . changeListener . dispose ( ) ;
427442
428443 const csbPath = model . uri . path . replace ( '/sandbox' , '' ) ;
429444 dispatch ( actions . correction . clear ( csbPath , 'eslint' ) ) ;
430445
431- delete this . modelListeners [ model . uri . path ] ;
446+ delete this . moduleModels [ model . uri . path ] ;
432447 }
433448 }
434449 ) ;
@@ -462,4 +477,16 @@ export class ModelsHandler {
462477 }
463478 } ) ;
464479 }
480+
481+ private getModuleModel ( module : Module ) {
482+ const path = '/sandbox' + module . path ;
483+ this . moduleModels [ path ] = this . moduleModels [ path ] || {
484+ changeListener : null ,
485+ model : null ,
486+ path,
487+ selections : [ ] ,
488+ } ;
489+
490+ return this . moduleModels [ path ] ;
491+ }
465492}
0 commit comments