diff --git a/ChangeLog.md b/ChangeLog.md
index 1b448a98..0957de83 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,7 @@
# Write Your Python Program - CHANGELOG
+* 2.1.1 (2025-11-26)
+ * Fix a couple of glitches with highlighting in the visualization
* 2.1.0 (2025-11-07)
* Fix bug with recursive types #192
* Improve startup performance and other minor improvements #190
diff --git a/media/programflow-visualization/webview.js b/media/programflow-visualization/webview.js
index 6e621e50..56f326f3 100644
--- a/media/programflow-visualization/webview.js
+++ b/media/programflow-visualization/webview.js
@@ -36,7 +36,7 @@ window.addEventListener("message", (event) => {
/**
* Updates the Visualization in the Webview, with the given BackendTraceElem.
*
- * @param traceElem A BackendTraceElem with 3 fields (line, stack, heap)
+ * @param traceElem A FrontendTraceElem
*/
function updateVisualization(traceElem) {
const data = `
@@ -56,10 +56,10 @@ function updateVisualization(traceElem) {
- ${traceElem[1]}
+ ${traceElem.stackHTML}
- ${traceElem[2]}
+ ${traceElem.heapHTML}
`;
@@ -78,17 +78,17 @@ function updateVisualization(traceElem) {
}
const stdoutLog = document.getElementById("stdout-log");
- stdoutLog.innerHTML = traceElem[4];
+ stdoutLog.innerHTML = traceElem.outputState;
stdoutLog.scrollTo(0, stdoutLog.scrollHeight);
}
/**
* Updates the indendation for heap elements, if a other heap element references it.
*
- * @param traceElem A BackendTraceElem with 3 fields (line, stack, heap)
+ * @param traceElem A FrontendTraceElem
*/
function updateIntend(traceElem) {
- const heapTags = traceElem[2].match(/(?<=startPointer)[0-9]+/g);
+ const heapTags = traceElem.heapHTML.match(/(?<=startPointer)[0-9]+/g);
if (heapTags) {
heapTags.forEach((tag) => {
const element = document.getElementById("objectItem" + tag);
@@ -117,7 +117,7 @@ function updateRefArrows(traceElem) {
return new LinkerLine({
parent: document.getElementById("viz"),
start: tag.elem1,
- end: tag.elem2,
+ end: tag.elem2,
size: 2,
path: "magnet",
startSocket: "right",
@@ -139,9 +139,9 @@ function updateRefArrows(traceElem) {
* @returns A list with all ids that have either a start or end pointer id in the html
*/
function getCurrentTags(traceElem) {
- const stackTags = traceElem[1].match(/(?<=id=")(.+)Pointer[0-9]+/g);
- const heapTags = traceElem[2].match(/(?<=startPointer)[0-9]+/g);
- const uniqueId = traceElem[2].match(/(?<=)\d+(?=startPointer)/g);
+ const stackTags = traceElem.stackHTML.match(/(?<=id=")(.+)Pointer[0-9]+/g);
+ const heapTags = traceElem.heapHTML.match(/(?<=startPointer)[0-9]+/g);
+ const uniqueId = traceElem.heapHTML.match(/(?<=)\d+(?=startPointer)/g);
if (!stackTags) {
return;
diff --git a/package.json b/package.json
index 8bfb738e..e9944d8e 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"displayName": "Write Your Python Program!",
"description": "A user friendly python environment for beginners",
"license": "See license in LICENSE",
- "version": "2.1.0",
+ "version": "2.1.1",
"publisher": "StefanWehr",
"icon": "icon.png",
"engines": {
diff --git a/src/programflow-visualization/frontend/HTMLGenerator.ts b/src/programflow-visualization/frontend/HTMLGenerator.ts
index 1da5e8f4..bab8730f 100644
--- a/src/programflow-visualization/frontend/HTMLGenerator.ts
+++ b/src/programflow-visualization/frontend/HTMLGenerator.ts
@@ -50,7 +50,13 @@ export class HTMLGenerator {
if (traceElement.traceback !== undefined) {
output += `${traceElement.traceback}`;
}
- return [traceElement.line, frameItems, objectItems, traceElement.filePath, output];
+ return {
+ lineNumber: traceElement.line,
+ stackHTML: frameItems,
+ heapHTML: objectItems,
+ filename: traceElement.filePath,
+ outputState: output
+ };
}
private objectItem(name: string, value: HeapValue): string {
diff --git a/src/programflow-visualization/frontend/frontend.ts b/src/programflow-visualization/frontend/frontend.ts
index 08b50037..e362f852 100644
--- a/src/programflow-visualization/frontend/frontend.ts
+++ b/src/programflow-visualization/frontend/frontend.ts
@@ -1,4 +1,5 @@
import { ExtensionContext } from 'vscode';
+import * as vscode from 'vscode';
import { VisualizationPanel } from './visualization_panel';
import { MessagePort } from 'worker_threads';
import * as TraceCache from '../trace_cache';
@@ -7,6 +8,7 @@ let panel: VisualizationPanel | undefined = undefined;
export async function startFrontend(
context: ExtensionContext,
+ outChannel: vscode.OutputChannel,
filePath: string,
fileHash: string,
tracePort: MessagePort | null): Promise {
@@ -16,7 +18,7 @@ export async function startFrontend(
}
panel?.dispose();
- panel = await VisualizationPanel.getVisualizationPanel(context, filePath, fileHash, trace, tracePort);
+ panel = await VisualizationPanel.getVisualizationPanel(context, outChannel, filePath, fileHash, trace, tracePort);
if (!panel) {
return failure("Frontend couldn't be initialized!");
}
diff --git a/src/programflow-visualization/frontend/visualization_panel.ts b/src/programflow-visualization/frontend/visualization_panel.ts
index 4b7a7906..17a9fe8c 100644
--- a/src/programflow-visualization/frontend/visualization_panel.ts
+++ b/src/programflow-visualization/frontend/visualization_panel.ts
@@ -18,8 +18,17 @@ export class VisualizationPanel {
private _trace: FrontendTrace;
private _traceIndex: number;
private _tracePortSelfClose: boolean;
+ private _outChannel: vscode.OutputChannel;
- private constructor(context: vscode.ExtensionContext, filePath: string, fileHash: string, trace: BackendTrace, tracePort: MessagePort | null) {
+ private constructor(
+ context: vscode.ExtensionContext,
+ outChannel: vscode.OutputChannel,
+ filePath: string,
+ fileHash: string,
+ trace: BackendTrace,
+ tracePort: MessagePort | null
+ ) {
+ this._outChannel = outChannel;
this._fileHash = fileHash;
this._tracePort = tracePort;
this._backendTrace = { trace: trace, complete: trace.length > 0 };
@@ -74,8 +83,8 @@ export class VisualizationPanel {
if (this._panel?.active) {
this.updateLineHighlight();
}
- }, undefined, context.subscriptions);
-
+ }, undefined, context.subscriptions);
+
// Message Receivers
this._panel.webview.onDidReceiveMessage(
@@ -100,7 +109,7 @@ export class VisualizationPanel {
this._trace.push((new HTMLGenerator()).generateHTML(backendTraceElem));
await this.postMessagesToWebview('updateButtons', 'updateContent');
if (firstElement) {
- this.updateLineHighlight();
+ await this.updateLineHighlight();
}
});
this._tracePort?.on('close', async () => {
@@ -118,12 +127,13 @@ export class VisualizationPanel {
public static async getVisualizationPanel(
context: vscode.ExtensionContext,
+ outChannel: vscode.OutputChannel,
filePath: string,
fileHash: string,
trace: BackendTrace,
tracePort: MessagePort | null
): Promise {
- return new VisualizationPanel(context, filePath, fileHash, trace, tracePort);
+ return new VisualizationPanel(context, outChannel, filePath, fileHash, trace, tracePort);
}
// TODO: Look if Typescript is possible OR do better documentation in all files
@@ -185,35 +195,59 @@ export class VisualizationPanel {
}
private async updateLineHighlight(remove: boolean = false) {
- if (this._trace.length === 0) {
- return;
- }
- let editor: vscode.TextEditor | undefined = vscode.window.visibleTextEditors.filter(
- editor => path.basename(editor.document.uri.path) === path.basename(this._trace[this._traceIndex][3]!)
- )[0];
+ try {
+ if (this._trace.length === 0) {
+ this._outChannel.appendLine("updateLineHighlight: no trace available, aborting");
+ return;
+ }
+ const traceFile = this._trace[this._traceIndex].filename;
+ this._outChannel.appendLine(
+ `updateLineHighlight: traceFile=${traceFile}, traceIndex=${this._traceIndex}, remove=${remove}`);
- const openPath = vscode.Uri.parse(this._trace[this._traceIndex][3]!);
- if (!editor || editor.document.uri.path !== openPath.path) {
- await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup');
- const document = await vscode.workspace.openTextDocument(openPath);
- editor = await vscode.window.showTextDocument(document);
- }
+ // Use vscode.Uri.file() for proper file path to URI conversion
+ const openPath = vscode.Uri.file(traceFile);
- if (remove || editor.document.lineCount < this._trace[this._traceIndex][0]) {
- editor.setDecorations(nextLineExecuteHighlightType, []);
- } else {
- this.setNextLineHighlighting(editor);
- }
- }
+ // Find editor by full normalized path, not just basename
+ let editor: vscode.TextEditor | undefined = vscode.window.visibleTextEditors.find(
+ editor => editor.document.uri.fsPath === openPath.fsPath
+ );
- private setNextLineHighlighting(editor: vscode.TextEditor) {
- if (this._trace.length === 0) {
- return;
- }
- const nextLine = this._trace[this._traceIndex][0] - 1;
+ if (!editor && remove) {
+ return;
+ } else if (!editor){
+ this._outChannel.appendLine(`updateLineHighlight: editor not found, opening document: ${openPath.fsPath}`);
+ await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup');
+ const document = await vscode.workspace.openTextDocument(openPath);
+ editor = await vscode.window.showTextDocument(document, { preserveFocus: false });
+ // Give the editor time to fully initialize
+ await new Promise(resolve => setTimeout(resolve, 100));
+ if (!editor) {
+ this._outChannel.appendLine(`updateLineHighlight: failed to get editor after opening document`);
+ return;
+ }
+ }
- if (nextLine > -1) {
- this.setEditorDecorations(editor, nextLineExecuteHighlightType, nextLine);
+ const traceLine = this._trace[this._traceIndex].lineNumber;
+ const lineNo = traceLine - 1; // zero-based indexing in vscode
+ if (remove) {
+ this._outChannel.appendLine(
+ "updateLineHighlight: removing highlighting in " + editor.document.fileName);
+ editor.setDecorations(nextLineExecuteHighlightType, []);
+ } else if (lineNo < 0 || lineNo >= editor.document.lineCount) {
+ this._outChannel.appendLine(
+ "updateLineHighlight: traceLine " + traceLine + " out of range (doc has " +
+ editor.document.lineCount + " lines) in " + editor.document.fileName);
+ editor.setDecorations(nextLineExecuteHighlightType, []);
+ } else {
+ this._outChannel.appendLine(
+ "updateLineHighlight: highlighting line " + traceLine + " in " + editor.document.fileName);
+ this.setEditorDecorations(editor, nextLineExecuteHighlightType, lineNo);
+ }
+ } catch (error) {
+ this._outChannel.appendLine(`updateLineHighlight: ERROR - ${error}`);
+ if (error instanceof Error) {
+ this._outChannel.appendLine(`Stack: ${error.stack}`);
+ }
}
}
diff --git a/src/programflow-visualization/main.ts b/src/programflow-visualization/main.ts
index 87fbac1e..a56e0ab8 100644
--- a/src/programflow-visualization/main.ts
+++ b/src/programflow-visualization/main.ts
@@ -29,7 +29,7 @@ export function getProgFlowVizCallback(context: vscode.ExtensionContext, outChan
tracePort = startBackend(context, file, outChannel);
}
- const result = await startFrontend(context, file.fsPath, fileHash, tracePort);
+ const result = await startFrontend(context, outChannel, file.fsPath, fileHash, tracePort);
if (result) {
await vscode.window.showErrorMessage("Error ProgramFlow-Visualization: " + result.errorMessage);
return;
diff --git a/src/programflow-visualization/types.ts b/src/programflow-visualization/types.ts
index 7e4c4dc9..e2528ac4 100644
--- a/src/programflow-visualization/types.ts
+++ b/src/programflow-visualization/types.ts
@@ -1,4 +1,4 @@
-/**
+/**
* For better readable code
*/
type Try = Success | Failure;
@@ -7,7 +7,15 @@ type Failure = { errorMessage: string };
// State Types for the Frontend
type FrontendTrace = Array;
-type FrontendTraceElem = [number, string, string, string, string];
+
+type FrontendTraceElem = {
+ lineNumber: number, // 1-based
+ stackHTML: string,
+ heapHTML: string,
+ filename: string,
+ outputState: string,
+};
+
// ############################################################################################
// State Types for the Backend
type PartialBackendTrace = {
diff --git a/vscode-test/type-test.py b/vscode-test/type-test.py
index 83800177..091a1c81 100644
--- a/vscode-test/type-test.py
+++ b/vscode-test/type-test.py
@@ -12,4 +12,5 @@ class Point:
x: int
y: int
-p = Point()
+p = Point(1, 2)
+print(p)