Skip to content

Commit c663f34

Browse files
authored
Merge pull request #193 from skogsbaer/sw/highlightLine
highlight line
2 parents 0d54192 + 02ba3e0 commit c663f34

File tree

9 files changed

+100
-47
lines changed

9 files changed

+100
-47
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Write Your Python Program - CHANGELOG
22

3+
* 2.1.1 (2025-11-26)
4+
* Fix a couple of glitches with highlighting in the visualization
35
* 2.1.0 (2025-11-07)
46
* Fix bug with recursive types #192
57
* Improve startup performance and other minor improvements #190

media/programflow-visualization/webview.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ window.addEventListener("message", (event) => {
3636
/**
3737
* Updates the Visualization in the Webview, with the given BackendTraceElem.
3838
*
39-
* @param traceElem A BackendTraceElem with 3 fields (line, stack, heap)
39+
* @param traceElem A FrontendTraceElem
4040
*/
4141
function updateVisualization(traceElem) {
4242
const data = `
@@ -56,10 +56,10 @@ function updateVisualization(traceElem) {
5656
</div>
5757
<div class="row">
5858
<div class="column floating-left floating-left-content" id="frames">
59-
${traceElem[1]}
59+
${traceElem.stackHTML}
6060
</div>
6161
<div class="column floating-right floating-right-content" id="objects">
62-
${traceElem[2]}
62+
${traceElem.heapHTML}
6363
</div>
6464
</div>
6565
`;
@@ -78,17 +78,17 @@ function updateVisualization(traceElem) {
7878
}
7979

8080
const stdoutLog = document.getElementById("stdout-log");
81-
stdoutLog.innerHTML = traceElem[4];
81+
stdoutLog.innerHTML = traceElem.outputState;
8282
stdoutLog.scrollTo(0, stdoutLog.scrollHeight);
8383
}
8484

8585
/**
8686
* Updates the indendation for heap elements, if a other heap element references it.
8787
*
88-
* @param traceElem A BackendTraceElem with 3 fields (line, stack, heap)
88+
* @param traceElem A FrontendTraceElem
8989
*/
9090
function updateIntend(traceElem) {
91-
const heapTags = traceElem[2].match(/(?<=startPointer)[0-9]+/g);
91+
const heapTags = traceElem.heapHTML.match(/(?<=startPointer)[0-9]+/g);
9292
if (heapTags) {
9393
heapTags.forEach((tag) => {
9494
const element = document.getElementById("objectItem" + tag);
@@ -117,7 +117,7 @@ function updateRefArrows(traceElem) {
117117
return new LinkerLine({
118118
parent: document.getElementById("viz"),
119119
start: tag.elem1,
120-
end: tag.elem2,
120+
end: tag.elem2,
121121
size: 2,
122122
path: "magnet",
123123
startSocket: "right",
@@ -139,9 +139,9 @@ function updateRefArrows(traceElem) {
139139
* @returns A list with all ids that have either a start or end pointer id in the html
140140
*/
141141
function getCurrentTags(traceElem) {
142-
const stackTags = traceElem[1].match(/(?<=id=")(.+)Pointer[0-9]+/g);
143-
const heapTags = traceElem[2].match(/(?<=startPointer)[0-9]+/g);
144-
const uniqueId = traceElem[2].match(/(?<=)\d+(?=startPointer)/g);
142+
const stackTags = traceElem.stackHTML.match(/(?<=id=")(.+)Pointer[0-9]+/g);
143+
const heapTags = traceElem.heapHTML.match(/(?<=startPointer)[0-9]+/g);
144+
const uniqueId = traceElem.heapHTML.match(/(?<=)\d+(?=startPointer)/g);
145145

146146
if (!stackTags) {
147147
return;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"displayName": "Write Your Python Program!",
44
"description": "A user friendly python environment for beginners",
55
"license": "See license in LICENSE",
6-
"version": "2.1.0",
6+
"version": "2.1.1",
77
"publisher": "StefanWehr",
88
"icon": "icon.png",
99
"engines": {

src/programflow-visualization/frontend/HTMLGenerator.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,13 @@ export class HTMLGenerator {
5050
if (traceElement.traceback !== undefined) {
5151
output += `<span class="traceback-text">${traceElement.traceback}</span>`;
5252
}
53-
return [traceElement.line, frameItems, objectItems, traceElement.filePath, output];
53+
return {
54+
lineNumber: traceElement.line,
55+
stackHTML: frameItems,
56+
heapHTML: objectItems,
57+
filename: traceElement.filePath,
58+
outputState: output
59+
};
5460
}
5561

5662
private objectItem(name: string, value: HeapValue): string {

src/programflow-visualization/frontend/frontend.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ExtensionContext } from 'vscode';
2+
import * as vscode from 'vscode';
23
import { VisualizationPanel } from './visualization_panel';
34
import { MessagePort } from 'worker_threads';
45
import * as TraceCache from '../trace_cache';
@@ -7,6 +8,7 @@ let panel: VisualizationPanel | undefined = undefined;
78

89
export async function startFrontend(
910
context: ExtensionContext,
11+
outChannel: vscode.OutputChannel,
1012
filePath: string,
1113
fileHash: string,
1214
tracePort: MessagePort | null): Promise<Failure | undefined> {
@@ -16,7 +18,7 @@ export async function startFrontend(
1618
}
1719

1820
panel?.dispose();
19-
panel = await VisualizationPanel.getVisualizationPanel(context, filePath, fileHash, trace, tracePort);
21+
panel = await VisualizationPanel.getVisualizationPanel(context, outChannel, filePath, fileHash, trace, tracePort);
2022
if (!panel) {
2123
return failure("Frontend couldn't be initialized!");
2224
}

src/programflow-visualization/frontend/visualization_panel.ts

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,17 @@ export class VisualizationPanel {
1818
private _trace: FrontendTrace;
1919
private _traceIndex: number;
2020
private _tracePortSelfClose: boolean;
21+
private _outChannel: vscode.OutputChannel;
2122

22-
private constructor(context: vscode.ExtensionContext, filePath: string, fileHash: string, trace: BackendTrace, tracePort: MessagePort | null) {
23+
private constructor(
24+
context: vscode.ExtensionContext,
25+
outChannel: vscode.OutputChannel,
26+
filePath: string,
27+
fileHash: string,
28+
trace: BackendTrace,
29+
tracePort: MessagePort | null
30+
) {
31+
this._outChannel = outChannel;
2332
this._fileHash = fileHash;
2433
this._tracePort = tracePort;
2534
this._backendTrace = { trace: trace, complete: trace.length > 0 };
@@ -74,8 +83,8 @@ export class VisualizationPanel {
7483
if (this._panel?.active) {
7584
this.updateLineHighlight();
7685
}
77-
}, undefined, context.subscriptions);
78-
86+
}, undefined, context.subscriptions);
87+
7988

8089
// Message Receivers
8190
this._panel.webview.onDidReceiveMessage(
@@ -100,7 +109,7 @@ export class VisualizationPanel {
100109
this._trace.push((new HTMLGenerator()).generateHTML(backendTraceElem));
101110
await this.postMessagesToWebview('updateButtons', 'updateContent');
102111
if (firstElement) {
103-
this.updateLineHighlight();
112+
await this.updateLineHighlight();
104113
}
105114
});
106115
this._tracePort?.on('close', async () => {
@@ -118,12 +127,13 @@ export class VisualizationPanel {
118127

119128
public static async getVisualizationPanel(
120129
context: vscode.ExtensionContext,
130+
outChannel: vscode.OutputChannel,
121131
filePath: string,
122132
fileHash: string,
123133
trace: BackendTrace,
124134
tracePort: MessagePort | null
125135
): Promise<VisualizationPanel | undefined> {
126-
return new VisualizationPanel(context, filePath, fileHash, trace, tracePort);
136+
return new VisualizationPanel(context, outChannel, filePath, fileHash, trace, tracePort);
127137
}
128138

129139
// TODO: Look if Typescript is possible OR do better documentation in all files
@@ -185,35 +195,59 @@ export class VisualizationPanel {
185195
}
186196

187197
private async updateLineHighlight(remove: boolean = false) {
188-
if (this._trace.length === 0) {
189-
return;
190-
}
191-
let editor: vscode.TextEditor | undefined = vscode.window.visibleTextEditors.filter(
192-
editor => path.basename(editor.document.uri.path) === path.basename(this._trace[this._traceIndex][3]!)
193-
)[0];
198+
try {
199+
if (this._trace.length === 0) {
200+
this._outChannel.appendLine("updateLineHighlight: no trace available, aborting");
201+
return;
202+
}
203+
const traceFile = this._trace[this._traceIndex].filename;
204+
this._outChannel.appendLine(
205+
`updateLineHighlight: traceFile=${traceFile}, traceIndex=${this._traceIndex}, remove=${remove}`);
194206

195-
const openPath = vscode.Uri.parse(this._trace[this._traceIndex][3]!);
196-
if (!editor || editor.document.uri.path !== openPath.path) {
197-
await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup');
198-
const document = await vscode.workspace.openTextDocument(openPath);
199-
editor = await vscode.window.showTextDocument(document);
200-
}
207+
// Use vscode.Uri.file() for proper file path to URI conversion
208+
const openPath = vscode.Uri.file(traceFile);
201209

202-
if (remove || editor.document.lineCount < this._trace[this._traceIndex][0]) {
203-
editor.setDecorations(nextLineExecuteHighlightType, []);
204-
} else {
205-
this.setNextLineHighlighting(editor);
206-
}
207-
}
210+
// Find editor by full normalized path, not just basename
211+
let editor: vscode.TextEditor | undefined = vscode.window.visibleTextEditors.find(
212+
editor => editor.document.uri.fsPath === openPath.fsPath
213+
);
208214

209-
private setNextLineHighlighting(editor: vscode.TextEditor) {
210-
if (this._trace.length === 0) {
211-
return;
212-
}
213-
const nextLine = this._trace[this._traceIndex][0] - 1;
215+
if (!editor && remove) {
216+
return;
217+
} else if (!editor){
218+
this._outChannel.appendLine(`updateLineHighlight: editor not found, opening document: ${openPath.fsPath}`);
219+
await vscode.commands.executeCommand('workbench.action.focusFirstEditorGroup');
220+
const document = await vscode.workspace.openTextDocument(openPath);
221+
editor = await vscode.window.showTextDocument(document, { preserveFocus: false });
222+
// Give the editor time to fully initialize
223+
await new Promise(resolve => setTimeout(resolve, 100));
224+
if (!editor) {
225+
this._outChannel.appendLine(`updateLineHighlight: failed to get editor after opening document`);
226+
return;
227+
}
228+
}
214229

215-
if (nextLine > -1) {
216-
this.setEditorDecorations(editor, nextLineExecuteHighlightType, nextLine);
230+
const traceLine = this._trace[this._traceIndex].lineNumber;
231+
const lineNo = traceLine - 1; // zero-based indexing in vscode
232+
if (remove) {
233+
this._outChannel.appendLine(
234+
"updateLineHighlight: removing highlighting in " + editor.document.fileName);
235+
editor.setDecorations(nextLineExecuteHighlightType, []);
236+
} else if (lineNo < 0 || lineNo >= editor.document.lineCount) {
237+
this._outChannel.appendLine(
238+
"updateLineHighlight: traceLine " + traceLine + " out of range (doc has " +
239+
editor.document.lineCount + " lines) in " + editor.document.fileName);
240+
editor.setDecorations(nextLineExecuteHighlightType, []);
241+
} else {
242+
this._outChannel.appendLine(
243+
"updateLineHighlight: highlighting line " + traceLine + " in " + editor.document.fileName);
244+
this.setEditorDecorations(editor, nextLineExecuteHighlightType, lineNo);
245+
}
246+
} catch (error) {
247+
this._outChannel.appendLine(`updateLineHighlight: ERROR - ${error}`);
248+
if (error instanceof Error) {
249+
this._outChannel.appendLine(`Stack: ${error.stack}`);
250+
}
217251
}
218252
}
219253

src/programflow-visualization/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export function getProgFlowVizCallback(context: vscode.ExtensionContext, outChan
2929
tracePort = startBackend(context, file, outChannel);
3030
}
3131

32-
const result = await startFrontend(context, file.fsPath, fileHash, tracePort);
32+
const result = await startFrontend(context, outChannel, file.fsPath, fileHash, tracePort);
3333
if (result) {
3434
await vscode.window.showErrorMessage("Error ProgramFlow-Visualization: " + result.errorMessage);
3535
return;

src/programflow-visualization/types.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/**
1+
/**
22
* For better readable code
33
*/
44
type Try = Success | Failure;
@@ -7,7 +7,15 @@ type Failure = { errorMessage: string };
77

88
// State Types for the Frontend
99
type FrontendTrace = Array<FrontendTraceElem>;
10-
type FrontendTraceElem = [number, string, string, string, string];
10+
11+
type FrontendTraceElem = {
12+
lineNumber: number, // 1-based
13+
stackHTML: string,
14+
heapHTML: string,
15+
filename: string,
16+
outputState: string,
17+
};
18+
1119
// ############################################################################################
1220
// State Types for the Backend
1321
type PartialBackendTrace = {

vscode-test/type-test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ class Point:
1212
x: int
1313
y: int
1414

15-
p = Point()
15+
p = Point(1, 2)
16+
print(p)

0 commit comments

Comments
 (0)