|
4 | 4 | *--------------------------------------------------------*/ |
5 | 5 |
|
6 | 6 | import * as vscode from 'vscode'; |
7 | | -import { TextDecoder } from 'util'; |
8 | | - |
9 | | -// Track sessions since vscode doesn't provide a list of them. |
10 | | -const sessions = new Map<string, vscode.DebugSession>(); |
11 | | -vscode.debug.onDidStartDebugSession((s) => sessions.set(s.id, s)); |
12 | | -vscode.debug.onDidTerminateDebugSession((s) => sessions.delete(s.id)); |
13 | 7 |
|
14 | 8 | /** |
15 | 9 | * Registers commands to improve the debugging experience for Go. |
16 | 10 | * |
17 | 11 | * Currently, it adds a command to open a variable in a new text document. |
18 | 12 | */ |
19 | 13 | export function registerGoDebugCommands(ctx: vscode.ExtensionContext) { |
20 | | - class VariableContentProvider implements vscode.TextDocumentContentProvider { |
21 | | - static uriForRef(ref: VariableRef) { |
22 | | - return vscode.Uri.from({ |
23 | | - scheme: 'go-debug-variable', |
24 | | - authority: `${ref.container.variablesReference}@${ref.sessionId}`, |
25 | | - path: `/${ref.variable.name}` |
26 | | - }); |
27 | | - } |
| 14 | + // Track sessions since vscode doesn't provide a list of them. |
| 15 | + const sessions = new Map<string, vscode.DebugSession>(); |
| 16 | + |
| 17 | + ctx.subscriptions.push( |
| 18 | + vscode.debug.onDidStartDebugSession((s) => sessions.set(s.id, s)), |
| 19 | + vscode.debug.onDidTerminateDebugSession((s) => sessions.delete(s.id)), |
| 20 | + vscode.workspace.registerTextDocumentContentProvider('go-debug-variable', new VariableContentProvider(sessions)), |
| 21 | + vscode.commands.registerCommand('go.debug.openVariableAsDoc', async (ref: VariableRef) => { |
| 22 | + const uri = VariableContentProvider.uriForRef(ref); |
| 23 | + const doc = await vscode.workspace.openTextDocument(uri); |
| 24 | + await vscode.window.showTextDocument(doc); |
| 25 | + }) |
| 26 | + ); |
| 27 | +} |
28 | 28 |
|
29 | | - async provideTextDocumentContent(uri: vscode.Uri): Promise<string> { |
30 | | - const name = uri.path.replace(/^\//, ''); |
31 | | - const [container, sessionId] = uri.authority.split('@', 2); |
32 | | - if (!container || !sessionId) { |
33 | | - throw new Error('Invalid URI'); |
34 | | - } |
| 29 | +class VariableContentProvider implements vscode.TextDocumentContentProvider { |
| 30 | + sessions: Map<string, vscode.DebugSession> |
35 | 31 |
|
36 | | - const session = sessions.get(sessionId); |
37 | | - if (!session) return 'Debug session has been terminated'; |
| 32 | + constructor(sessionsSet: Map<string, vscode.DebugSession>) { |
| 33 | + this.sessions = sessionsSet; |
| 34 | + } |
| 35 | + |
| 36 | + static uriForRef(ref: VariableRef) { |
| 37 | + return vscode.Uri.from({ |
| 38 | + scheme: 'go-debug-variable', |
| 39 | + authority: `${ref.container.variablesReference}@${ref.sessionId}`, |
| 40 | + path: `/${ref.variable.name}` |
| 41 | + }); |
| 42 | + } |
38 | 43 |
|
39 | | - const { variables } = await session.customRequest('variables', { |
40 | | - variablesReference: parseInt(container, 10) |
41 | | - }) as { variables: Variable[] }; |
| 44 | + async provideTextDocumentContent(uri: vscode.Uri): Promise<string> { |
| 45 | + const name = uri.path.replace(/^\//, ''); |
| 46 | + const [container, sessionId] = uri.authority.split('@', 2); |
| 47 | + if (!container || !sessionId) { |
| 48 | + throw new Error('Invalid URI'); |
| 49 | + } |
42 | 50 |
|
43 | | - const v = variables.find(v => v.name === name); |
44 | | - if (!v) return `Cannot resolve variable ${name}`; |
| 51 | + const session = this.sessions.get(sessionId); |
| 52 | + if (!session) return 'Debug session has been terminated'; |
45 | 53 |
|
46 | | - if (!v.memoryReference) { |
47 | | - const { result } = await session.customRequest('evaluate', { |
48 | | - expression: v.evaluateName, |
49 | | - context: 'clipboard' |
50 | | - }) as { result: string }; |
| 54 | + const { variables } = await session.customRequest('variables', { |
| 55 | + variablesReference: parseInt(container, 10) |
| 56 | + }) as { variables: Variable[] }; |
51 | 57 |
|
52 | | - v.value = result ?? v.value; |
| 58 | + const v = variables.find(v => v.name === name); |
| 59 | + if (!v) return `Cannot resolve variable ${name}`; |
53 | 60 |
|
54 | | - return parseVariable(v); |
55 | | - } |
| 61 | + if (!v.memoryReference) { |
| 62 | + const { result } = await session.customRequest('evaluate', { |
| 63 | + expression: v.evaluateName, |
| 64 | + context: 'clipboard' |
| 65 | + }) as { result: string }; |
56 | 66 |
|
57 | | - const chunk = 1 << 14; |
58 | | - let offset = 0; |
59 | | - let full: Uint8Array[] = []; |
| 67 | + v.value = result ?? v.value; |
60 | 68 |
|
61 | | - while (true) { |
62 | | - const resp = await session.customRequest('readMemory', { |
63 | | - memoryReference: v.memoryReference, |
64 | | - offset, |
65 | | - count: chunk |
66 | | - }) as { address: string; data: string; unreadableBytes: number }; |
| 69 | + return parseVariable(v); |
| 70 | + } |
67 | 71 |
|
68 | | - if (!resp.data) break; |
69 | | - full.push(Buffer.from(resp.data, 'base64')); |
| 72 | + const chunk = 1 << 14; |
| 73 | + let offset = 0; |
| 74 | + let full: Uint8Array[] = []; |
70 | 75 |
|
71 | | - if (resp.unreadableBytes === 0) break; |
72 | | - offset += chunk; |
73 | | - } |
| 76 | + while (true) { |
| 77 | + const resp = await session.customRequest('readMemory', { |
| 78 | + memoryReference: v.memoryReference, |
| 79 | + offset, |
| 80 | + count: chunk |
| 81 | + }) as { address: string; data: string; unreadableBytes: number }; |
74 | 82 |
|
75 | | - const allBytes = Buffer.concat(full); |
| 83 | + if (!resp.data) break; |
| 84 | + full.push(Buffer.from(resp.data, 'base64')); |
76 | 85 |
|
77 | | - return new TextDecoder('utf-8').decode(allBytes); |
| 86 | + if (resp.unreadableBytes === 0) break; |
| 87 | + offset += chunk; |
78 | 88 | } |
| 89 | + |
| 90 | + return Buffer.concat(full).toString('utf-8'); |
79 | 91 | } |
| 92 | +} |
80 | 93 |
|
81 | | - ctx.subscriptions.push( |
82 | | - vscode.workspace.registerTextDocumentContentProvider('go-debug-variable', new VariableContentProvider()) |
83 | | - ); |
| 94 | +/** |
| 95 | + * A reference to a variable, used to pass data between commands. |
| 96 | + */ |
| 97 | +interface VariableRef { |
| 98 | + sessionId: string; |
| 99 | + container: Container; |
| 100 | + variable: Variable; |
| 101 | +} |
84 | 102 |
|
85 | | - ctx.subscriptions.push( |
86 | | - vscode.commands.registerCommand('go.debug.openVariableAsDoc', async (ref: VariableRef) => { |
87 | | - const uri = VariableContentProvider.uriForRef(ref); |
88 | | - const doc = await vscode.workspace.openTextDocument(uri); |
89 | | - await vscode.window.showTextDocument(doc); |
90 | | - }) |
91 | | - ); |
| 103 | +/** |
| 104 | + * A container for variables, used to pass data between commands. |
| 105 | + */ |
| 106 | +interface Container { |
| 107 | + name: string; |
| 108 | + variablesReference: number; |
| 109 | + expensive: boolean; |
| 110 | +} |
92 | 111 |
|
93 | | - /** |
94 | | - * A reference to a variable, used to pass data between commands. |
95 | | - */ |
96 | | - interface VariableRef { |
97 | | - sessionId: string; |
98 | | - container: Container; |
99 | | - variable: Variable; |
100 | | - } |
| 112 | +/** |
| 113 | + * A variable, used to pass data between commands. |
| 114 | + */ |
| 115 | +interface Variable { |
| 116 | + name: string; |
| 117 | + value: string; |
| 118 | + evaluateName: string; |
| 119 | + variablesReference: number; |
| 120 | + memoryReference?: string; |
| 121 | +} |
101 | 122 |
|
102 | | - /** |
103 | | - * A container for variables, used to pass data between commands. |
104 | | - */ |
105 | | - interface Container { |
106 | | - name: string; |
107 | | - variablesReference: number; |
108 | | - expensive: boolean; |
109 | | - } |
110 | 123 |
|
111 | | - /** |
112 | | - * A variable, used to pass data between commands. |
113 | | - */ |
114 | | - interface Variable { |
115 | | - name: string; |
116 | | - value: string; |
117 | | - evaluateName: string; |
118 | | - variablesReference: number; |
119 | | - memoryReference?: string; |
120 | | - } |
121 | 124 |
|
122 | | - const escapeCodes: Record<string, string> = { |
123 | | - r: '\r', |
124 | | - n: '\n', |
125 | | - t: '\t' |
126 | | - }; |
127 | | - |
128 | | - /** |
129 | | - * Parses a variable value, unescaping special characters. |
130 | | - */ |
131 | | - function parseVariable(variable: Variable) { |
132 | | - let raw = variable.value.trim(); |
133 | | - try { |
134 | | - return JSON.parse(raw); |
135 | | - } catch (_) { |
136 | | - return raw.replace(/\\[nrt\\"'`]/, (_, s) => (s in escapeCodes ? escapeCodes[s] : s)); |
137 | | - } |
| 125 | +const escapeCodes: Record<string, string> = { |
| 126 | + r: '\r', |
| 127 | + n: '\n', |
| 128 | + t: '\t' |
| 129 | +}; |
| 130 | + |
| 131 | +/** |
| 132 | + * Parses a variable value, unescaping special characters. |
| 133 | + */ |
| 134 | +function parseVariable(variable: Variable) { |
| 135 | + let raw = variable.value.trim(); |
| 136 | + try { |
| 137 | + return JSON.parse(raw); |
| 138 | + } catch (_) { |
| 139 | + return raw.replace(/\\[nrt\\"'`]/, (_, s) => (s in escapeCodes ? escapeCodes[s] : s)); |
138 | 140 | } |
139 | 141 | } |
0 commit comments