Skip to content

Commit 1aca6b6

Browse files
christianalfoniCompuIves
authored andcommitted
Live fixes and improvements (codesandbox#3107)
* also write module state changes * ask for module state on live when ready to receive it * Update packages/app/src/app/overmind/effects/vscode/ModelsHandler.ts Co-Authored-By: Ives van Hoorne <[email protected]> * Update packages/app/src/app/overmind/namespaces/live/internalActions.ts Co-Authored-By: Christian Alfoni <[email protected]> * use new saved_code from server * QOL improvements (codesandbox#3108) * QOL improvements * Change request * Make writeFile use the saved code when writing to fs * fix syncing module state on owned live sandbox * Fix discarding module code in live
1 parent fa0ae73 commit 1aca6b6

File tree

9 files changed

+194
-109
lines changed

9 files changed

+194
-109
lines changed

packages/app/src/app/overmind/effects/live/index.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -259,9 +259,6 @@ export default {
259259
message,
260260
});
261261
},
262-
sendModuleState() {
263-
return this.send('live:module_state', {});
264-
},
265262
sendModuleSaved(module: Module) {
266263
return this.send('module:saved', {
267264
type: 'module',
@@ -272,7 +269,7 @@ export default {
272269
sendChatEnabled(enabled: boolean) {
273270
return this.send('live:chat_enabled', { enabled });
274271
},
275-
sendModuleUpdateRequest() {
272+
sendModuleStateSyncRequest() {
276273
return this.send('live:module_state', {});
277274
},
278275
sendUserSelection(moduleShortid: string, liveUserId: string, selection: any) {

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,19 @@ export class ModelsHandler {
185185
});
186186
}
187187

188+
public async setModuleCode(module: Module) {
189+
const moduleModel = this.getModuleModel(module);
190+
const model = await moduleModel.model;
191+
192+
if (!model) {
193+
return;
194+
}
195+
196+
this.isApplyingOperation = true;
197+
await model.setValue(module.code);
198+
this.isApplyingOperation = false;
199+
}
200+
188201
public async updateUserSelections(
189202
module,
190203
userSelections: Array<UserSelection | EditorSelection>
@@ -384,6 +397,14 @@ export class ModelsHandler {
384397
let index = 0;
385398
const currentEOLLength = model.getEOL().length;
386399
let eolChanged = false;
400+
const modelCode = model.getValue();
401+
402+
if (operation.baseLength !== modelCode.length) {
403+
throw new Error(
404+
"The base length of the operation doesn't match the length of the code"
405+
);
406+
}
407+
387408
for (let i = 0; i < operation.ops.length; i++) {
388409
const op = operation.ops[i];
389410
if (TextOperation.isRetain(op)) {

packages/app/src/app/overmind/effects/vscode/SandboxFsSync/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getGlobal } from '@codesandbox/common/lib/utils/global';
1616
import { protocolAndHost } from '@codesandbox/common/lib/utils/url-generator';
1717
import { json } from 'overmind';
1818

19+
import { getSavedCode } from 'app/overmind/utils/sandbox';
1920
import { WAIT_INITIAL_TYPINGS_MS } from '../constants';
2021
import { appendFile, mkdir, rename, rmdir, unlink, writeFile } from './utils';
2122

@@ -111,15 +112,19 @@ class SandboxFsSync {
111112

112113
appendFile(fs, copy);
113114
this.send('append-file', copy);
114-
browserFs.appendFile(join('/sandbox', module.path), module.code, () => {});
115+
116+
const savedCode = getSavedCode(module.code, module.savedCode);
117+
browserFs.appendFile(join('/sandbox', module.path), savedCode, () => {});
115118
}
116119

117120
public writeFile(fs: SandboxFs, module: Module) {
118121
const copy = json(module);
119122

120123
writeFile(fs, copy);
121124
this.send('write-file', copy);
122-
browserFs.writeFile(join('/sandbox', module.path), module.code, () => {});
125+
126+
const savedCode = getSavedCode(module.code, module.savedCode);
127+
browserFs.writeFile(join('/sandbox', module.path), savedCode, () => {});
123128

124129
if (module.title === 'package.json') {
125130
this.syncDependencyTypings();

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

Lines changed: 96 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { listen } from 'codesandbox-api';
2121
import FontFaceObserver from 'fontfaceobserver';
2222
import * as childProcess from 'node-services/lib/child_process';
2323

24+
import { debounce } from 'lodash-es';
2425
import { EXTENSIONS_LOCATION, VIM_EXTENSION_ID } from './constants';
2526
import {
2627
initializeCustomTheme,
@@ -77,7 +78,7 @@ const context: any = window;
7778
* parts.
7879
*/
7980
export class VSCodeEffect {
80-
public initialized: Promise<void>;
81+
public initialized: Promise<unknown>;
8182
public sandboxFsSync: SandboxFsSync;
8283

8384
private monaco: any;
@@ -104,12 +105,17 @@ export class VSCodeEffect {
104105
getCustomEditor: () => null,
105106
};
106107

108+
onSelectionChangeDebounced: VsCodeOptions['onSelectionChange'] & {
109+
cancel(): void;
110+
};
111+
107112
public initialize(options: VsCodeOptions) {
108113
this.options = options;
109114
this.controller = {
110115
getState: options.getState,
111116
getSignal: options.getSignal,
112117
};
118+
this.onSelectionChangeDebounced = debounce(options.onSelectionChange, 500);
113119

114120
this.prepareElements();
115121

@@ -216,7 +222,11 @@ export class VSCodeEffect {
216222
moduleShortid: string,
217223
operation: (string | number)[]
218224
) {
219-
return this.modelsHandler.applyOperation(moduleShortid, operation);
225+
if (!this.modelsHandler) {
226+
return;
227+
}
228+
229+
await this.modelsHandler.applyOperation(moduleShortid, operation);
220230
}
221231

222232
public updateOptions(options: { readOnly: boolean }) {
@@ -305,6 +315,14 @@ export class VSCodeEffect {
305315
}
306316
}
307317

318+
public async setModuleCode(module: Module) {
319+
if (!this.modelsHandler) {
320+
return;
321+
}
322+
323+
await this.modelsHandler.setModuleCode(module);
324+
}
325+
308326
public async closeAllTabs() {
309327
if (this.editorApi) {
310328
const groupsToClose = this.editorApi.editorService.editorGroupService.getGroups();
@@ -588,79 +606,82 @@ export class VSCodeEffect {
588606
);
589607
});
590608

591-
// It has to run the accessor within the callback
592-
serviceCollection.get(IInstantiationService).invokeFunction(accessor => {
593-
// Initialize these services
594-
accessor.get(CodeSandboxConfigurationUIService);
595-
accessor.get(ICodeSandboxEditorConnectorService);
596-
597-
const statusbarPart = accessor.get(IStatusbarService);
598-
const menubarPart = accessor.get('menubar');
599-
const commandService = accessor.get(ICommandService);
600-
const extensionService = accessor.get(IExtensionService);
601-
const extensionEnablementService = accessor.get(
602-
IExtensionEnablementService
603-
);
609+
return new Promise(resolve => {
610+
// It has to run the accessor within the callback
611+
serviceCollection.get(IInstantiationService).invokeFunction(accessor => {
612+
// Initialize these services
613+
accessor.get(CodeSandboxConfigurationUIService);
614+
accessor.get(ICodeSandboxEditorConnectorService);
615+
616+
const statusbarPart = accessor.get(IStatusbarService);
617+
const menubarPart = accessor.get('menubar');
618+
const commandService = accessor.get(ICommandService);
619+
const extensionService = accessor.get(IExtensionService);
620+
const extensionEnablementService = accessor.get(
621+
IExtensionEnablementService
622+
);
604623

605-
this.commandService.resolve(commandService);
606-
this.extensionService.resolve(extensionService);
624+
this.commandService.resolve(commandService);
625+
this.extensionService.resolve(extensionService);
607626

608-
this.extensionEnablementService.resolve(extensionEnablementService);
627+
this.extensionEnablementService.resolve(extensionEnablementService);
609628

610-
const editorPart = accessor.get(IEditorGroupsService);
629+
const editorPart = accessor.get(IEditorGroupsService);
611630

612-
const codeEditorService = accessor.get(ICodeEditorService);
613-
const textFileService = accessor.get(ITextFileService);
614-
const editorService = accessor.get(IEditorService);
615-
const contextViewService = accessor.get(IContextViewService);
631+
const codeEditorService = accessor.get(ICodeEditorService);
632+
const textFileService = accessor.get(ITextFileService);
633+
const editorService = accessor.get(IEditorService);
634+
const contextViewService = accessor.get(IContextViewService);
616635

617-
contextViewService.setContainer(container);
636+
contextViewService.setContainer(container);
618637

619-
this.editorApi = {
620-
openFile(path) {
621-
return codeEditorService.openCodeEditor({
622-
resource: monaco.Uri.file('/sandbox' + path),
623-
});
624-
},
625-
getActiveCodeEditor() {
626-
return codeEditorService.getActiveCodeEditor();
627-
},
628-
textFileService,
629-
editorPart,
630-
editorService,
631-
codeEditorService,
632-
extensionService,
633-
};
634-
635-
window.CSEditor = {
636-
editor: this.editorApi,
637-
monaco,
638-
};
639-
640-
if (process.env.NODE_ENV === 'development') {
641-
// eslint-disable-next-line
642-
console.log(accessor);
643-
}
638+
this.editorApi = {
639+
openFile(path) {
640+
return codeEditorService.openCodeEditor({
641+
resource: monaco.Uri.file('/sandbox' + path),
642+
});
643+
},
644+
getActiveCodeEditor() {
645+
return codeEditorService.getActiveCodeEditor();
646+
},
647+
textFileService,
648+
editorPart,
649+
editorService,
650+
codeEditorService,
651+
extensionService,
652+
};
653+
654+
window.CSEditor = {
655+
editor: this.editorApi,
656+
monaco,
657+
};
658+
659+
if (process.env.NODE_ENV === 'development') {
660+
// eslint-disable-next-line
661+
console.log(accessor);
662+
}
644663

645-
statusbarPart.create(this.elements.statusbar);
646-
menubarPart.create(this.elements.menubar);
647-
editorPart.create(this.elements.editorPart);
648-
editorPart.layout(container.offsetWidth, container.offsetHeight);
664+
statusbarPart.create(this.elements.statusbar);
665+
menubarPart.create(this.elements.menubar);
666+
editorPart.create(this.elements.editorPart);
667+
editorPart.layout(container.offsetWidth, container.offsetHeight);
649668

650-
editorPart.parent = container;
669+
editorPart.parent = container;
651670

652-
container.appendChild(this.elements.editorPart);
671+
container.appendChild(this.elements.editorPart);
653672

654-
this.initializeReactions();
673+
this.initializeReactions();
655674

656-
this.configureMonacoLanguages(monaco);
675+
this.configureMonacoLanguages(monaco);
657676

658-
editorService.onDidActiveEditorChange(this.onActiveEditorChange);
659-
this.initializeCodeSandboxAPIListener();
677+
editorService.onDidActiveEditorChange(this.onActiveEditorChange);
678+
this.initializeCodeSandboxAPIListener();
660679

661-
if (this.settings.lintEnabled) {
662-
this.createLinter();
663-
}
680+
if (this.settings.lintEnabled) {
681+
this.createLinter();
682+
}
683+
resolve();
684+
});
664685
});
665686
}
666687

@@ -814,7 +835,19 @@ export class VSCodeEffect {
814835
),
815836
};
816837

817-
this.options.onSelectionChange(data);
838+
if (
839+
selectionChange.reason === 3 ||
840+
/* alt + shift + arrow keys */ selectionChange.source ===
841+
'moveWordCommand' ||
842+
/* click inside a selection */ selectionChange.source === 'api'
843+
) {
844+
this.onSelectionChangeDebounced.cancel();
845+
this.options.onSelectionChange(data);
846+
} else {
847+
// This is just on typing, we send a debounced selection update as a
848+
// safeguard to make sure we are in sync
849+
this.onSelectionChangeDebounced(data);
850+
}
818851
}
819852
);
820853
}

packages/app/src/app/overmind/namespaces/editor/actions.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ export const sandboxChanged: AsyncAction<{ id: string }> = withLoadApp<{
116116

117117
if (sandbox.owned && !state.live.isLive) {
118118
actions.files.internal.recoverFiles();
119+
} else if (state.live.isLive) {
120+
effects.live.sendModuleStateSyncRequest();
119121
}
120122

121123
effects.vscode.openModule(state.editor.currentModule);
@@ -167,7 +169,7 @@ export const codeSaved: AsyncAction<{
167169
export const onOperationApplied: Action<{
168170
moduleShortid: string;
169171
code: string;
170-
}> = ({ state, actions }, { code, moduleShortid }) => {
172+
}> = ({ state, effects, actions }, { code, moduleShortid }) => {
171173
const module = state.editor.currentSandbox.modules.find(
172174
m => m.shortid === moduleShortid
173175
);
@@ -182,6 +184,10 @@ export const onOperationApplied: Action<{
182184
});
183185

184186
actions.editor.internal.updatePreviewCode();
187+
188+
if (module.savedCode !== null && module.code === module.savedCode) {
189+
effects.vscode.revertModule(module);
190+
}
185191
};
186192

187193
export const codeChanged: Action<{
@@ -217,6 +223,10 @@ export const codeChanged: Action<{
217223
if (!isServer && state.preferences.settings.livePreviewEnabled) {
218224
actions.editor.internal.updatePreviewCode();
219225
}
226+
227+
if (module.savedCode !== null && module.code === module.savedCode) {
228+
effects.vscode.revertModule(module);
229+
}
220230
};
221231

222232
export const saveClicked: AsyncAction = withOwnedSandbox(
@@ -496,14 +506,7 @@ export const discardModuleChanges: Action<{
496506
return;
497507
}
498508

499-
const code = module.savedCode === null ? module.code || '' : module.savedCode;
500-
actions.editor.codeChanged({
501-
code,
502-
moduleShortid,
503-
});
504-
505509
module.updatedAt = new Date().toString();
506-
507510
effects.vscode.revertModule(module);
508511

509512
state.editor.changedModuleShortids.splice(

0 commit comments

Comments
 (0)