|
2 | 2 | import { onMount, onDestroy } from 'svelte' |
3 | 3 | import { SvelteSet } from 'svelte/reactivity' |
4 | 4 | import type { editor as meditor, IDisposable } from 'monaco-editor' |
5 | | - import { debugState, getDAPClient, type DAPClient } from './dapClient' |
| 5 | + import { debugState, getDAPClient, resetDAPClient, type DAPClient } from './dapClient' |
6 | 6 | import DebugToolbar from './DebugToolbar.svelte' |
7 | 7 | import DebugPanel from './DebugPanel.svelte' |
| 8 | + import { DAP_SERVER_URLS, getDebugFileExtension, type DebugLanguage } from './index' |
8 | 9 |
|
9 | 10 | interface Props { |
10 | 11 | editor: meditor.IStandaloneCodeEditor | null |
11 | 12 | code: string |
| 13 | + language?: DebugLanguage |
12 | 14 | filePath?: string |
13 | 15 | dapServerUrl?: string |
14 | 16 | } |
15 | 17 |
|
16 | | - let { editor, code, filePath = '/tmp/script.py', dapServerUrl = 'ws://localhost:5679' }: Props = $props() |
| 18 | + let { editor, code, language = 'bun', filePath, dapServerUrl }: Props = $props() |
| 19 | +
|
| 20 | + // Derive the server URL from language if not explicitly provided |
| 21 | + const effectiveServerUrl = $derived(dapServerUrl ?? DAP_SERVER_URLS[language]) |
| 22 | + // Derive file path from language if not explicitly provided |
| 23 | + const effectiveFilePath = $derived(filePath ?? `/tmp/script${getDebugFileExtension(language)}`) |
17 | 24 |
|
18 | 25 | let client: DAPClient | null = $state(null) |
19 | 26 | let breakpointDecorations: string[] = $state([]) |
|
37 | 44 | // Track breakpoints by line number |
38 | 45 | let breakpoints = new SvelteSet<number>() |
39 | 46 |
|
| 47 | + // Track the last used server URL to detect changes |
| 48 | + let lastServerUrl: string | undefined = undefined |
| 49 | +
|
| 50 | + // React to language/server URL changes - fully exit debug mode and reset client |
| 51 | + $effect(() => { |
| 52 | + const newUrl = effectiveServerUrl |
| 53 | + if (lastServerUrl !== undefined && lastServerUrl !== newUrl) { |
| 54 | + // Server URL changed (language switched), fully exit debug mode |
| 55 | + console.log('[DAP] Language changed, switching from', lastServerUrl, 'to', newUrl) |
| 56 | +
|
| 57 | + // Terminate and disconnect if we have an active session |
| 58 | + if (client) { |
| 59 | + if (client.isConnected()) { |
| 60 | + // Try to terminate gracefully, then disconnect |
| 61 | + client.terminate().catch(() => {}).finally(() => { |
| 62 | + client?.disconnect() |
| 63 | + }) |
| 64 | + } |
| 65 | + } |
| 66 | +
|
| 67 | + // Reset the singleton and clear local client reference |
| 68 | + resetDAPClient() |
| 69 | + client = null |
| 70 | +
|
| 71 | + // Clear current line decoration since we're exiting debug mode |
| 72 | + if (editor) { |
| 73 | + currentLineDecoration = editor.deltaDecorations(currentLineDecoration, []) |
| 74 | + } |
| 75 | + } |
| 76 | + lastServerUrl = newUrl |
| 77 | + }) |
| 78 | +
|
| 79 | + // Export function to refresh breakpoint positions - called by parent when code changes |
| 80 | + export function refreshBreakpoints(): void { |
| 81 | + updateBreakpointPositionsFromDecorations() |
| 82 | + } |
| 83 | +
|
40 | 84 | onMount(() => { |
41 | 85 | if (!editor) return |
42 | 86 |
|
43 | | - client = getDAPClient(dapServerUrl) |
| 87 | + client = getDAPClient(effectiveServerUrl) |
| 88 | + lastServerUrl = effectiveServerUrl |
44 | 89 |
|
45 | 90 | // Add click handler for glyph margin (breakpoint toggle) |
46 | 91 | const mouseDownDisposable = editor.onMouseDown((e) => { |
|
105 | 150 | breakpointDecorations = editor.deltaDecorations(breakpointDecorations, decorations) |
106 | 151 | } |
107 | 152 |
|
| 153 | + // Update breakpoint line numbers from decoration positions after code edits |
| 154 | + function updateBreakpointPositionsFromDecorations(): void { |
| 155 | + console.log('[DAP] updateBreakpointPositionsFromDecorations called, decorations:', breakpointDecorations.length) |
| 156 | + if (!editor || breakpointDecorations.length === 0) return |
| 157 | +
|
| 158 | + const model = editor.getModel() |
| 159 | + if (!model) return |
| 160 | +
|
| 161 | + // Get current line numbers from decorations (using plain Set as this is not reactive state) |
| 162 | + const newLines: Set<number> = new Set() |
| 163 | + for (const decorationId of breakpointDecorations) { |
| 164 | + const range = model.getDecorationRange(decorationId) |
| 165 | + if (range) { |
| 166 | + newLines.add(range.startLineNumber) |
| 167 | + } |
| 168 | + } |
| 169 | +
|
| 170 | + // Check if positions changed |
| 171 | + const oldLines = Array.from(breakpoints).sort((a, b) => a - b) |
| 172 | + const updatedLines = Array.from(newLines).sort((a, b) => a - b) |
| 173 | +
|
| 174 | + console.log('[DAP] Old breakpoint lines:', oldLines, 'New lines from decorations:', updatedLines) |
| 175 | +
|
| 176 | + const positionsChanged = |
| 177 | + oldLines.length !== updatedLines.length || |
| 178 | + oldLines.some((line, i) => line !== updatedLines[i]) |
| 179 | +
|
| 180 | + if (positionsChanged) { |
| 181 | + console.log('[DAP] Breakpoint positions changed, syncing with server') |
| 182 | + // Update breakpoints set with new positions |
| 183 | + breakpoints.clear() |
| 184 | + for (const line of newLines) { |
| 185 | + breakpoints.add(line) |
| 186 | + } |
| 187 | + // Sync updated positions with server |
| 188 | + syncBreakpointsWithServer() |
| 189 | + } |
| 190 | + } |
| 191 | +
|
108 | 192 | function updateCurrentLineDecoration(line: number | undefined): void { |
109 | 193 | if (!editor) return |
110 | 194 |
|
|
127 | 211 | } |
128 | 212 |
|
129 | 213 | async function syncBreakpointsWithServer(): Promise<void> { |
130 | | - if (!client || !client.isConnected()) return |
| 214 | + console.log('[DAP] syncBreakpointsWithServer called, connected:', client?.isConnected(), 'breakpoints:', Array.from(breakpoints)) |
| 215 | + if (!client || !client.isConnected()) { |
| 216 | + console.log('[DAP] Not syncing - client not connected') |
| 217 | + return |
| 218 | + } |
131 | 219 |
|
132 | 220 | try { |
133 | | - await client.setBreakpoints(filePath, Array.from(breakpoints)) |
| 221 | + console.log('[DAP] Sending setBreakpoints to server:', effectiveFilePath, Array.from(breakpoints)) |
| 222 | + await client.setBreakpoints(effectiveFilePath, Array.from(breakpoints)) |
| 223 | + console.log('[DAP] setBreakpoints completed') |
134 | 224 | } catch (error) { |
135 | 225 | console.error('Failed to sync breakpoints:', error) |
136 | 226 | } |
137 | 227 | } |
138 | 228 |
|
139 | 229 | async function startDebugging(): Promise<void> { |
140 | | - if (!client) { |
141 | | - client = getDAPClient(dapServerUrl) |
142 | | - } |
| 230 | + // Always reset and create a fresh client with the current server URL |
| 231 | + // This ensures we connect to the correct endpoint for the current language |
| 232 | + resetDAPClient() |
| 233 | + client = getDAPClient(effectiveServerUrl) |
143 | 234 |
|
144 | 235 | try { |
145 | 236 | await client.connect() |
146 | 237 | await client.initialize() |
147 | | - await client.setBreakpoints(filePath, Array.from(breakpoints)) |
| 238 | + await client.setBreakpoints(effectiveFilePath, Array.from(breakpoints)) |
148 | 239 | await client.configurationDone() |
149 | 240 | await client.launch({ code, cwd: '/tmp' }) |
150 | 241 | } catch (error) { |
|
0 commit comments