From ceab6a5e84a517aebca9cbe2c324438422d62e73 Mon Sep 17 00:00:00 2001 From: mug1wara26 Date: Tue, 15 Jul 2025 04:36:07 +0800 Subject: [PATCH 1/2] Breakpoint implementation --- src/extension.ts | 43 ++++++++++++++++++++++++++++++++++++ src/utils/editor.ts | 9 ++++++++ src/utils/messageHandler.tsx | 23 +++++++++++++++++++ src/utils/messages.ts | 6 +++++ 4 files changed, 81 insertions(+) diff --git a/src/extension.ts b/src/extension.ts index 2d94297..6c1d09e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,6 +8,9 @@ import { activateLspClient, deactivateLspClient } from "./lsp/client"; import { LanguageClient } from "vscode-languageclient/node"; import { canonicaliseLocation } from "./utils/misc"; import config from "./utils/config"; +import { MessageHandler } from "./utils/messageHandler"; +import { sendToFrontendWrapped } from "./commands/showPanel"; +import Messages from "./utils/messages"; // TODO: Don't expose this object directly, create an interface via a wrapper class export let client: LanguageClient; @@ -77,6 +80,46 @@ export function activate(context: vscode.ExtensionContext) { vscode.commands.executeCommand("source-academy.show-panel", route, url); }, }); + + + let messageHandler = MessageHandler.getInstance() + // we use this map to track breakpoints + // breakpoint instances are the same even when they are moved + const m = new Map() + const handle_breakpoints = (breakpoint: vscode.Breakpoint, add: boolean) => { + if (!(breakpoint instanceof vscode.SourceBreakpoint)) { + return + } + const line = breakpoint.location.range.start.line + const editor = messageHandler.activeEditor + if (editor && editor.uri.toString() === breakpoint.location.uri.toString()) { + // Check for change + if (m.has(breakpoint) && add) { + delete editor.breakpoints[m.get(breakpoint)!] + editor.breakpoints[line] = "ace_breakpoint" + m.set(breakpoint, line) + } + else { + if (add) { + m.set(breakpoint, line); + editor.breakpoints[line] = "ace_breakpoint"; + } + else { + m.delete(breakpoint) + delete editor.breakpoints[line]; + } + } + } + } + + vscode.debug.onDidChangeBreakpoints((e: vscode.BreakpointsChangeEvent) => { + e.added.map(b => handle_breakpoints(b, true)) + e.removed.map(b => handle_breakpoints(b, false)) + e.changed.map(b => handle_breakpoints(b, true)) + const editor = messageHandler.activeEditor; + if (editor) + sendToFrontendWrapped(Messages.SetEditorBreakpoints(editor.workspaceLocation, editor.breakpoints)) + }) } // This method is called when your extension is deactivated diff --git a/src/utils/editor.ts b/src/utils/editor.ts index 73ffbf4..d1a2db6 100644 --- a/src/utils/editor.ts +++ b/src/utils/editor.ts @@ -25,6 +25,9 @@ export class Editor { assessmentName: string; questionId: number; + // Data for breakpoints + breakpoints: string[]; + private constructor( editor: vscode.TextEditor, prepend: string, @@ -32,6 +35,7 @@ export class Editor { workspaceLocation: VscWorkspaceLocation, assessmentName: string, questionId: number, + breakpoints: string[], ) { this.editor = editor; this.prepend = prepend; @@ -39,6 +43,7 @@ export class Editor { this.workspaceLocation = workspaceLocation; this.assessmentName = assessmentName; this.questionId = questionId; + this.breakpoints = breakpoints; } /** For debugging purposes */ @@ -57,6 +62,7 @@ export class Editor { questionId: number, prepend: string = "", initialCode: string = "", + breakpoints: string[] ): Promise { const workspaceFolder = canonicaliseLocation(config.workspaceFolder); const filePath = path.join( @@ -113,6 +119,7 @@ export class Editor { viewColumn: vscode.ViewColumn.One, }); + // Programmatically set the language vscode.languages.setTextDocumentLanguage(editor.document, "source"); @@ -131,6 +138,7 @@ export class Editor { workspaceLocation, assessmentName, questionId, + breakpoints ); // Register callback when contents changed @@ -147,6 +155,7 @@ export class Editor { }, ); + return self; } diff --git a/src/utils/messageHandler.tsx b/src/utils/messageHandler.tsx index 57b8834..0a7f242 100644 --- a/src/utils/messageHandler.tsx +++ b/src/utils/messageHandler.tsx @@ -119,7 +119,28 @@ export class MessageHandler { message.questionId, message.prepend, message.initialCode, + message.breakpoints ); + + + // Remove all breakpoints, then add the one included in the message + // Need this to pass typechecks + const editor = this.activeEditor + if (editor !== null) { + vscode.debug.removeBreakpoints( + vscode.debug.breakpoints.filter(bp => + bp instanceof vscode.SourceBreakpoint + && bp.location.uri.toString() === editor.uri.toString() + ) + ); + vscode.debug.addBreakpoints(this.activeEditor.breakpoints.map((s, index) => { + if (s === "ace_breakpoint") { + return new vscode.SourceBreakpoint(new vscode.Location(vscode.Uri.parse(editor.uri), new vscode.Position(index, 0)), true) + } + }).filter(x => x !== undefined)); + } + + this.activeEditor.uri; const info = context.globalState.get("info") ?? {}; if (this.activeEditor.uri) { @@ -130,6 +151,7 @@ export class MessageHandler { ); const nPrependLines = getNumPrependLines(message.prepend); _.set(info, `["${this.activeEditor.uri}"].prepend`, nPrependLines); + context.globalState.update("info", info); client.sendRequest("source/publishInfo", info); } @@ -157,6 +179,7 @@ export class MessageHandler { console.log(`Sending message: ${JSON.stringify(message)}`); sendToFrontend(this.panel, message); }); + break; case MessageTypeNames.NotifyAssessmentsOverview: const { assessmentOverviews, courseId } = message; diff --git a/src/utils/messages.ts b/src/utils/messages.ts index eb6f912..793c3cd 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -33,6 +33,7 @@ const Messages = createMessages({ chapter: number, prepend: string, initialCode: string, + breakpoints: string[], ) => ({ workspaceLocation, assessmentName, @@ -40,6 +41,7 @@ const Messages = createMessages({ chapter, prepend, initialCode, + breakpoints }), Text: (workspaceLocation: VscWorkspaceLocation, code: string) => ({ workspaceLocation, @@ -92,6 +94,10 @@ const Messages = createMessages({ LoginWithBrowser: (route: string) => ({ route, }), + SetEditorBreakpoints: (workspaceLocation: VscWorkspaceLocation, newBreakpoints: string[]) => ({ + workspaceLocation, + newBreakpoints + }) }); export default Messages; From b7b9e4b5cc9a10c3fcb9ff809e2c3230c24b15a8 Mon Sep 17 00:00:00 2001 From: mug1wara26 Date: Tue, 15 Jul 2025 05:33:50 +0800 Subject: [PATCH 2/2] automatically set debug.allowBreakpointsEverywhere --- src/extension.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/extension.ts b/src/extension.ts index 6c1d09e..ed56343 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -81,6 +81,22 @@ export function activate(context: vscode.ExtensionContext) { }, }); + const debugConfig = vscode.workspace.getConfiguration('debug'); + try { + if (debugConfig.get("allowBreakpointsEverywhere") === false) { + debugConfig.update("allowBreakpointsEverywhere", true, vscode.ConfigurationTarget.Global); + vscode.window.showInformationMessage('Successfully set "debug.allowBreakpointsEverywhere" to true in your user settings.'); + } + +} catch (error) { + // Handle potential errors, e.g., if settings can't be written. + console.error(error); + vscode.window.showErrorMessage([ + 'Failed to set "debug.allowBreakpointsEverywhere" to true in your user settings.', + 'Please manually set this to true to be able to use the CSE Machine' + ].join(" ")); +} + let messageHandler = MessageHandler.getInstance() // we use this map to track breakpoints