Skip to content

Commit 2ed9f7a

Browse files
refactored models handler to hold on to selections and model
1 parent da92113 commit 2ed9f7a

File tree

2 files changed

+80
-53
lines changed

2 files changed

+80
-53
lines changed

packages/app/src/app/overmind/effects/vscode/ModelsHandler.ts

Lines changed: 79 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { actions, dispatch } from 'codesandbox-api';
1010
import { css } from 'glamor';
1111
import { TextOperation } from 'ot';
1212

13-
import { getCurrentModelPath, getModel } from './utils';
13+
import { getCurrentModel, getCurrentModelPath } from './utils';
1414

1515
// @ts-ignore
1616
const fadeIn = css.keyframes('fadeIn', {
@@ -45,8 +45,16 @@ export type OnFileChangeCallback = (data: OnFileChangeData) => void;
4545

4646
export 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+
4855
export 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(/^\/sandbox/, ''),
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(/^\/sandbox/, ''),
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
}

packages/app/src/app/overmind/effects/vscode/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function getCode(editor) {
3434
return activeEditor.getValue();
3535
}
3636

37-
export function getModel(editor) {
37+
export function getCurrentModel(editor) {
3838
const activeEditor = editor.getActiveCodeEditor();
3939

4040
return activeEditor && activeEditor.getModel();

0 commit comments

Comments
 (0)