|
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