From 675a53e5de944b84c1dc422b1a9dbe693fb58c66 Mon Sep 17 00:00:00 2001 From: heyzec <61238538+heyzec@users.noreply.github.com> Date: Sun, 29 Jun 2025 15:15:53 +0800 Subject: [PATCH 1/5] chore: remove old code --- src/utils/editor.ts | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 62a05d0..6ff3f1e 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -15,13 +15,8 @@ export class Editor { questionId: number; assessmentType: string | null = null; onChangeCallback?: (editor: Editor) => void; - code: string | null = null; uri: string | null = null; - // For debugging purposes - replaceTime: number = 0; - nBursty: number = 0; - constructor( workspaceLocation: VscWorkspaceLocation, assessmentName: string, @@ -129,20 +124,11 @@ export class Editor { if (!self.onChangeCallback) { return; } - const text = editor.document.getText(); if (e.contentChanges.length === 0) { self.log(`EXTENSION: Editor's code did not change, ignoring`); return; } - if (Date.now() - self.replaceTime < 1000) { - self.log( - `EXTENSION: Ignoring change event, ${Date.now() - self.replaceTime}ms since last replace`, - ); - return; - } - self.log(`EXTENSION: Editor's code changed!`); self.onChangeCallback(self); - self.code = text; }, ); return self; @@ -152,18 +138,6 @@ export class Editor { if (!this.editor) { return; } - this.log(`EXTENSION: Editor's replace called by ${tag}: <<${code}>>`); - if (this.nBursty > 5) { - if (Date.now() - this.replaceTime < 5000) { - this.log(`EXTENSION: TOO BURSTY`); - return; - } - this.nBursty = 0; - } - if (Date.now() - this.replaceTime < 1000) { - this.nBursty++; - } - // this.disableCallback = true; const editor = this.editor; // Don't replace if the code is the same editor.edit((editBuilder) => { @@ -175,20 +149,6 @@ export class Editor { code, ); }); - let retry = 0; - while (editor.document.getText() !== code) { - await new Promise((r) => setTimeout(r, 100)); - this.log( - `EXTENSION: Editor's not replace yet, lets wait: ${editor.document.getText()}`, - ); - retry++; - if (retry > 11) { - this.log(`EXTENSION: Editor's replace wait limit reached`); - break; - } - } - this.code = code; - this.replaceTime = Date.now(); } onChange( From c8fe1a5044496b5f5fe9270c1fc3e1b1b80072b9 Mon Sep 17 00:00:00 2001 From: heyzec <61238538+heyzec@users.noreply.github.com> Date: Sun, 29 Jun 2025 17:16:28 +0800 Subject: [PATCH 2/5] refactor: cleanup code --- src/utils/editor.ts | 23 +++++++++++++---------- src/utils/messageHandler.tsx | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 6ff3f1e..5eb0c51 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -10,12 +10,15 @@ import { codeAddPrepend, codeRemovePrepend } from "./editorUtils"; export class Editor { editor?: vscode.TextEditor; + onChangeCallback?: (editor: Editor) => void; + + // Data associated with TextEditor + uri: string | null = null; + + // Metadata relating to this question workspaceLocation: VscWorkspaceLocation; assessmentName: string; questionId: number; - assessmentType: string | null = null; - onChangeCallback?: (editor: Editor) => void; - uri: string | null = null; constructor( workspaceLocation: VscWorkspaceLocation, @@ -24,7 +27,6 @@ export class Editor { ) { this.workspaceLocation = workspaceLocation; this.assessmentName = assessmentName; - this.assessmentType = this.assessmentType; this.questionId = questionId; } @@ -46,19 +48,15 @@ export class Editor { initialCode: string = "", ): Promise { const self = new Editor(workspaceLocation, assessmentName, questionId); - self.assessmentName = assessmentName; - self.questionId = questionId; const workspaceFolder = canonicaliseLocation(config.workspaceFolder); - const filePath = path.join( workspaceFolder, `${assessmentName}_${questionId}.js`, ); - const uri = vscode.Uri.file(filePath); - self.uri = uri.toString(); + self.uri = uri.toString(); const contents = codeAddPrepend(initialCode, prepend); await vscode.workspace.fs @@ -111,14 +109,19 @@ export class Editor { preview: false, viewColumn: vscode.ViewColumn.One, }); + self.editor = editor; + + // Programmatically set the language vscode.languages.setTextDocumentLanguage(editor.document, "source"); + + // Collapse the prepend section editor.selection = new vscode.Selection( editor.document.positionAt(0), editor.document.positionAt(1), ); vscode.commands.executeCommand("editor.fold"); - self.editor = editor; + // Register callback when contents changed vscode.workspace.onDidChangeTextDocument( (e: vscode.TextDocumentChangeEvent) => { if (!self.onChangeCallback) { diff --git a/src/utils/messageHandler.tsx b/src/utils/messageHandler.tsx index c17415d..81aa4c8 100644 --- a/src/utils/messageHandler.tsx +++ b/src/utils/messageHandler.tsx @@ -147,7 +147,7 @@ export class MessageHandler { } if (editor !== this.activeEditor) { console.log( - `EXTENSION: Editor ${editor.assessmentName}_${editor.questionId}_${editor.assessmentType} is no longer active, skipping onChange`, + `EXTENSION: Editor ${editor.assessmentName}_${editor.questionId} is no longer active, skipping onChange`, ); } const message = Messages.Text( From 9530cdf3306f4095dc34372868343c93db343c19 Mon Sep 17 00:00:00 2001 From: heyzec <61238538+heyzec@users.noreply.github.com> Date: Sun, 29 Jun 2025 17:24:57 +0800 Subject: [PATCH 3/5] refactor: avoid nullable in Editor --- src/utils/editor.ts | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 5eb0c51..69ab0e8 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -9,11 +9,11 @@ import { canonicaliseLocation } from "./misc"; import { codeAddPrepend, codeRemovePrepend } from "./editorUtils"; export class Editor { - editor?: vscode.TextEditor; + editor: vscode.TextEditor; onChangeCallback?: (editor: Editor) => void; // Data associated with TextEditor - uri: string | null = null; + uri: string; // Metadata relating to this question workspaceLocation: VscWorkspaceLocation; @@ -21,10 +21,14 @@ export class Editor { questionId: number; constructor( + editor: vscode.TextEditor, + uri: string, workspaceLocation: VscWorkspaceLocation, assessmentName: string, questionId: number, ) { + this.editor = editor; + this.uri = uri; this.workspaceLocation = workspaceLocation; this.assessmentName = assessmentName; this.questionId = questionId; @@ -32,11 +36,11 @@ export class Editor { /** For debugging purposes */ log(text: string) { - console.log(`${this.editor?.document.fileName.split("/").at(-1)} ${text}`); + console.log(`${this.editor.document.fileName.split("/").at(-1)} ${text}`); } getText() { - return this.editor?.document.getText(); + return this.editor.document.getText(); } // TODO: This method is too loaded, it's not obvious it also shows the editor @@ -47,8 +51,6 @@ export class Editor { prepend: string = "", initialCode: string = "", ): Promise { - const self = new Editor(workspaceLocation, assessmentName, questionId); - const workspaceFolder = canonicaliseLocation(config.workspaceFolder); const filePath = path.join( workspaceFolder, @@ -56,7 +58,6 @@ export class Editor { ); const uri = vscode.Uri.file(filePath); - self.uri = uri.toString(); const contents = codeAddPrepend(initialCode, prepend); await vscode.workspace.fs @@ -65,9 +66,6 @@ export class Editor { .then( (localCode) => { if (localCode !== contents) { - self.log( - "EXTENSION: Conflict detected between local and remote, prompting user to choose one", - ); vscode.window .showInformationMessage( [ @@ -80,7 +78,6 @@ export class Editor { .then(async (answer) => { // By default the code displayed is the local one if (answer === "Yes") { - self.log("EXTENSION: Saving program from server to file"); await vscode.workspace.fs.writeFile( uri, new TextEncoder().encode(contents), @@ -88,7 +85,7 @@ export class Editor { } else if (answer === undefined) { // Modal cancelled const message = Messages.Text( - self.workspaceLocation, + workspaceLocation, codeRemovePrepend(localCode), ); sendToFrontendWrapped(message); @@ -97,7 +94,6 @@ export class Editor { } }, async () => { - self.log(`Opening file failed, creating at ${filePath}`); await vscode.workspace.fs.writeFile( uri, new TextEncoder().encode(contents), @@ -109,7 +105,6 @@ export class Editor { preview: false, viewColumn: vscode.ViewColumn.One, }); - self.editor = editor; // Programmatically set the language vscode.languages.setTextDocumentLanguage(editor.document, "source"); @@ -121,6 +116,15 @@ export class Editor { ); vscode.commands.executeCommand("editor.fold"); + // Create wrapper + const self = new Editor( + editor, + uri.toString(), + workspaceLocation, + assessmentName, + questionId, + ); + // Register callback when contents changed vscode.workspace.onDidChangeTextDocument( (e: vscode.TextDocumentChangeEvent) => { @@ -134,13 +138,11 @@ export class Editor { self.onChangeCallback(self); }, ); + return self; } async replace(code: string, tag: string = "") { - if (!this.editor) { - return; - } const editor = this.editor; // Don't replace if the code is the same editor.edit((editBuilder) => { From 1481d68cea6f6f96c916746cc8a8bfa5bfcd48f3 Mon Sep 17 00:00:00 2001 From: heyzec <61238538+heyzec@users.noreply.github.com> Date: Sun, 29 Jun 2025 17:26:05 +0800 Subject: [PATCH 4/5] refactor: make private --- src/utils/editor.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 69ab0e8..439c7b5 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -8,9 +8,13 @@ import { sendToFrontendWrapped } from "../commands/showPanel"; import { canonicaliseLocation } from "./misc"; import { codeAddPrepend, codeRemovePrepend } from "./editorUtils"; +/** + * Represents a VS Code editor associated with a Source Academy question. + * Abstracts low level calling of VS Code APIs. + */ export class Editor { - editor: vscode.TextEditor; - onChangeCallback?: (editor: Editor) => void; + private editor: vscode.TextEditor; + private onChangeCallback?: (editor: Editor) => void; // Data associated with TextEditor uri: string; @@ -20,7 +24,7 @@ export class Editor { assessmentName: string; questionId: number; - constructor( + private constructor( editor: vscode.TextEditor, uri: string, workspaceLocation: VscWorkspaceLocation, @@ -35,7 +39,7 @@ export class Editor { } /** For debugging purposes */ - log(text: string) { + private log(text: string) { console.log(`${this.editor.document.fileName.split("/").at(-1)} ${text}`); } From ce4d25657dcf928739e07cf5346fcf65c710348e Mon Sep 17 00:00:00 2001 From: heyzec <61238538+heyzec@users.noreply.github.com> Date: Sun, 29 Jun 2025 17:45:26 +0800 Subject: [PATCH 5/5] feat: update code on reset --- src/utils/editor.ts | 12 +++++++++--- src/utils/messageHandler.tsx | 6 ++++++ src/utils/messages.ts | 7 +++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 439c7b5..73ffbf4 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -17,6 +17,7 @@ export class Editor { private onChangeCallback?: (editor: Editor) => void; // Data associated with TextEditor + prepend: string; uri: string; // Metadata relating to this question @@ -26,12 +27,14 @@ export class Editor { private constructor( editor: vscode.TextEditor, + prepend: string, uri: string, workspaceLocation: VscWorkspaceLocation, assessmentName: string, questionId: number, ) { this.editor = editor; + this.prepend = prepend; this.uri = uri; this.workspaceLocation = workspaceLocation; this.assessmentName = assessmentName; @@ -123,6 +126,7 @@ export class Editor { // Create wrapper const self = new Editor( editor, + prepend, uri.toString(), workspaceLocation, assessmentName, @@ -146,16 +150,18 @@ export class Editor { return self; } - async replace(code: string, tag: string = "") { + async replace(code: string) { const editor = this.editor; - // Don't replace if the code is the same + const contents = codeAddPrepend(code, this.prepend); + + // In some sense, simulate a select all and paste editor.edit((editBuilder) => { editBuilder.replace( new vscode.Range( editor.document.positionAt(0), editor.document.positionAt(editor.document.getText().length), ), - code, + contents, ); }); } diff --git a/src/utils/messageHandler.tsx b/src/utils/messageHandler.tsx index 81aa4c8..ca696cb 100644 --- a/src/utils/messageHandler.tsx +++ b/src/utils/messageHandler.tsx @@ -167,6 +167,12 @@ export class MessageHandler { context.globalState.update("courseId", courseId); treeDataProvider.refresh(); break; + case MessageTypeNames.ResetEditor: + if (this.activeEditor) { + this.activeEditor.replace(message.initialCode); + this.panel?.reveal(vscode.ViewColumn.Two); + } + break; } console.log(`${Date.now()} Finish handleMessage: ${message.type}`); } diff --git a/src/utils/messages.ts b/src/utils/messages.ts index b35c498..0ce9367 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -48,6 +48,13 @@ const Messages = createMessages({ EvalEditor: (workspaceLocation: VscWorkspaceLocation) => ({ workspaceLocation: workspaceLocation, }), + ResetEditor: ( + workspaceLocation: VscWorkspaceLocation, + initialCode: string, + ) => ({ + workspaceLocation, + initialCode, + }), NotifyAssessmentsOverview: ( assessmentOverviews: VscAssessmentOverview[], courseId: number,