From 13bc56348bd4dbeac106fb9176c20d2a2072f74b Mon Sep 17 00:00:00 2001 From: heyzec <61238538+heyzec@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:45:17 +0800 Subject: [PATCH 1/4] refactor: create prependUtils.ts --- src/commands/showPanel.tsx | 8 ++------ src/utils/editor.ts | 12 +++--------- src/utils/editorUtils.ts | 23 +++++++++++++++++++++++ 3 files changed, 28 insertions(+), 15 deletions(-) create mode 100644 src/utils/editorUtils.ts diff --git a/src/commands/showPanel.tsx b/src/commands/showPanel.tsx index 4e2d50e..56e7f7f 100644 --- a/src/commands/showPanel.tsx +++ b/src/commands/showPanel.tsx @@ -15,6 +15,7 @@ import { FRONTEND_ELEMENT_ID } from "../constants"; import { client, SOURCE_ACADEMY_ICON_URI } from "../extension"; import _ from "lodash"; import { treeDataProvider } from "../treeview"; +import { getNumPrependLines } from "../utils/editorUtils"; let panel: vscode.WebviewPanel | null = null; // This needs to be a reference to active @@ -55,13 +56,8 @@ async function handleMessage( const info = context.globalState.get("info") ?? {}; if (activeEditor.uri) { // TODO: Write our own wrapper to set nested keys easily, removing lodash - // @ts-ignore _.set(info, `["${activeEditor.uri}"].chapter`, message.chapter ?? 1); - // TODO: message.prepend can be undefined in runtime, investigate - const nPrependLines = - message.prepend && message.prepend !== "" - ? message.prepend.split("\n").length + 2 // account for start/end markers - : 0; + const nPrependLines = getNumPrependLines(message.prepend); _.set(info, `["${activeEditor.uri}"].prepend`, nPrependLines); context.globalState.update("info", info); client.sendRequest("source/publishInfo", info); diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 436bcb8..3327c11 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -1,3 +1,4 @@ +// TODO: Move this file to src/features/editor/editor.ts import * as vscode from "vscode"; import config from "../utils/config"; @@ -5,6 +6,7 @@ import Messages, { VscWorkspaceLocation } from "./messages"; import path from "path"; import { sendToFrontendWrapped } from "../commands/showPanel"; import { canonicaliseLocation } from "./misc"; +import { codeAddPrepend } from "./editorUtils"; export class Editor { editor?: vscode.TextEditor; @@ -60,15 +62,7 @@ export class Editor { const uri = vscode.Uri.file(filePath); self.uri = uri.toString(); - const contents = - prepend !== "" - ? [ - "// PREPEND -- DO NOT EDIT", - prepend, - "// END PREPEND", - initialCode, - ].join("\n") - : initialCode; + const contents = codeAddPrepend(initialCode, prepend); await vscode.workspace.fs.readFile(vscode.Uri.file(filePath)).then( (value) => { diff --git a/src/utils/editorUtils.ts b/src/utils/editorUtils.ts new file mode 100644 index 0000000..258f469 --- /dev/null +++ b/src/utils/editorUtils.ts @@ -0,0 +1,23 @@ +// TODO: Move this file to src/features/editor/utils.ts + +// ================================================================================ +// Prepend +// ================================================================================ + +// If this is edited, also change folding.markers.{start,end} in language-configuration.json +const PREPEND_MARKER_START = "// PREPEND -- DO NOT EDIT"; +const PREPEND_MARKER_END = "// END PREPEND"; + +export function codeAddPrepend(code: string, prepend: string) { + if (prepend === "") { + return code; + } + return [PREPEND_MARKER_START, prepend, PREPEND_MARKER_END, code].join("\n"); +} + +export function getNumPrependLines(prepend: string) { + if (prepend === "") { + return 0; + } + return prepend.split("\n").length + 2; // account for start/end markers +} From 29eb65efbb65b954ef92f894753da443124da2c4 Mon Sep 17 00:00:00 2001 From: heyzec <61238538+heyzec@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:52:46 +0800 Subject: [PATCH 2/4] fix: add newline after prepend Fixes https://github.com/source-academy/vscode/issues/35 --- src/utils/editorUtils.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/utils/editorUtils.ts b/src/utils/editorUtils.ts index 258f469..abe380f 100644 --- a/src/utils/editorUtils.ts +++ b/src/utils/editorUtils.ts @@ -12,12 +12,18 @@ export function codeAddPrepend(code: string, prepend: string) { if (prepend === "") { return code; } - return [PREPEND_MARKER_START, prepend, PREPEND_MARKER_END, code].join("\n"); + return [ + PREPEND_MARKER_START, + prepend, + PREPEND_MARKER_END, + "", // Visual separation between prepend and code + code, + ].join("\n"); } export function getNumPrependLines(prepend: string) { if (prepend === "") { return 0; } - return prepend.split("\n").length + 2; // account for start/end markers + return prepend.split("\n").length + 3; // account for start/end markers } From 57a06b1cf24b1d6e49173d52f744abe9a2e4e6c8 Mon Sep 17 00:00:00 2001 From: heyzec <61238538+heyzec@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:56:28 +0800 Subject: [PATCH 3/4] fix: clearer prepend markers Fixes https://github.com/source-academy/vscode/issues/31 --- language-configuration.json | 4 ++-- src/utils/editorUtils.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/language-configuration.json b/language-configuration.json index 35cd63a..15928d7 100644 --- a/language-configuration.json +++ b/language-configuration.json @@ -25,8 +25,8 @@ ], "folding": { "markers": { - "start": "// PREPEND -- DO NOT EDIT", - "end": "// END PREPEND" + "start": "// PREPEND", + "end": "// END PREPEND -- DO NOT EDIT PREPEND" } }, "wordPattern": "[_$a-zA-Z][_$a-zA-Z0-9]*", diff --git a/src/utils/editorUtils.ts b/src/utils/editorUtils.ts index abe380f..ba8d681 100644 --- a/src/utils/editorUtils.ts +++ b/src/utils/editorUtils.ts @@ -5,8 +5,8 @@ // ================================================================================ // If this is edited, also change folding.markers.{start,end} in language-configuration.json -const PREPEND_MARKER_START = "// PREPEND -- DO NOT EDIT"; -const PREPEND_MARKER_END = "// END PREPEND"; +const PREPEND_MARKER_START = "// PREPEND"; +const PREPEND_MARKER_END = "// END PREPEND -- DO NOT EDIT PREPEND"; export function codeAddPrepend(code: string, prepend: string) { if (prepend === "") { From 76dcf5ed7ba4dbbe801ce62259294e7a6e2f30b1 Mon Sep 17 00:00:00 2001 From: heyzec <61238538+heyzec@users.noreply.github.com> Date: Thu, 26 Jun 2025 13:25:59 +0800 Subject: [PATCH 4/4] feat: remove prepend when sending to frontend --- src/commands/showPanel.tsx | 8 ++-- src/utils/editor.ts | 87 ++++++++++++++++++++------------------ src/utils/editorUtils.ts | 15 +++++++ 3 files changed, 65 insertions(+), 45 deletions(-) diff --git a/src/commands/showPanel.tsx b/src/commands/showPanel.tsx index 56e7f7f..0a618db 100644 --- a/src/commands/showPanel.tsx +++ b/src/commands/showPanel.tsx @@ -15,7 +15,7 @@ import { FRONTEND_ELEMENT_ID } from "../constants"; import { client, SOURCE_ACADEMY_ICON_URI } from "../extension"; import _ from "lodash"; import { treeDataProvider } from "../treeview"; -import { getNumPrependLines } from "../utils/editorUtils"; +import { codeRemovePrepend, getNumPrependLines } from "../utils/editorUtils"; let panel: vscode.WebviewPanel | null = null; // This needs to be a reference to active @@ -78,8 +78,10 @@ async function handleMessage( `EXTENSION: Editor ${editor.assessmentName}_${editor.questionId} is no longer active, skipping onChange`, ); } - const message = Messages.Text(workspaceLocation, code); - console.log(`Sending message: ${JSON.stringify(message)}`); + const message = Messages.Text( + workspaceLocation, + codeRemovePrepend(code), + ); sendToFrontend(panel, message); }); break; diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 3327c11..a6bd48c 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -6,7 +6,7 @@ import Messages, { VscWorkspaceLocation } from "./messages"; import path from "path"; import { sendToFrontendWrapped } from "../commands/showPanel"; import { canonicaliseLocation } from "./misc"; -import { codeAddPrepend } from "./editorUtils"; +import { codeAddPrepend, codeRemovePrepend } from "./editorUtils"; export class Editor { editor?: vscode.TextEditor; @@ -64,48 +64,51 @@ export class Editor { const contents = codeAddPrepend(initialCode, prepend); - await vscode.workspace.fs.readFile(vscode.Uri.file(filePath)).then( - (value) => { - if (value.toString() !== contents) { - self.log( - "EXTENSION: Conflict detected between local and remote, prompting user to choose one", + await vscode.workspace.fs + .readFile(vscode.Uri.file(filePath)) + .then((value) => value.toString()) + .then( + (localCode) => { + if (localCode !== contents) { + self.log( + "EXTENSION: Conflict detected between local and remote, prompting user to choose one", + ); + vscode.window + .showInformationMessage( + [ + "The local file differs from the version on the Source Academy servers.", + "Discard the local file and use the one on the server?", + ].join(" "), + { modal: true }, + "Yes", + ) + .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), + ); + } else if (answer === undefined) { + // Modal cancelled + const message = Messages.Text( + self.workspaceLocation, + codeRemovePrepend(localCode), + ); + sendToFrontendWrapped(message); + } + }); + } + }, + async () => { + self.log(`Opening file failed, creating at ${filePath}`); + await vscode.workspace.fs.writeFile( + uri, + new TextEncoder().encode(contents), ); - vscode.window - .showInformationMessage( - [ - "The local file differs from the version on the Source Academy servers.", - "Discard the local file and use the one on the server?", - ].join(" "), - { modal: true }, - "Yes", - ) - .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), - ); - } else if (answer === undefined) { - // Modal cancelled - const message = Messages.Text( - self.workspaceLocation, - value.toString(), - ); - sendToFrontendWrapped(message); - } - }); - } - }, - async () => { - self.log(`Opening file failed, creating at ${filePath}`); - await vscode.workspace.fs.writeFile( - uri, - new TextEncoder().encode(contents), - ); - }, - ); + }, + ); const editor = await vscode.window.showTextDocument(uri, { preview: false, diff --git a/src/utils/editorUtils.ts b/src/utils/editorUtils.ts index ba8d681..f7df1f3 100644 --- a/src/utils/editorUtils.ts +++ b/src/utils/editorUtils.ts @@ -27,3 +27,18 @@ export function getNumPrependLines(prepend: string) { } return prepend.split("\n").length + 3; // account for start/end markers } + +export function codeRemovePrepend(code: string) { + const lines = code.split("\n"); + const start = lines.indexOf(PREPEND_MARKER_START); + const end = lines.indexOf(PREPEND_MARKER_END); + + if (start === -1 || end === -1 || end < start) { + return code; + } + + // If line spacing between prepend and code accidentally removed, do not delete code! + const skip = end + (lines[end + 1] === "" ? 1 : 0); + + return lines.slice(skip + 1).join("\n"); +}