Skip to content

Commit eba0066

Browse files
committed
all
1 parent 0e140d2 commit eba0066

File tree

7 files changed

+2838
-61
lines changed

7 files changed

+2838
-61
lines changed

frontend/src/lib/components/ScriptEditor.svelte

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,8 @@
241241
const hasDebugResult = $derived(debugMode && $debugState.result !== undefined)
242242
243243
// Breakpoint decoration options
244-
const breakpointDecorationType = {
244+
// stickiness: 1 = NeverGrowsWhenTypingAtEdges - decorations track their position when code changes
245+
const breakpointDecorationType: meditor.IModelDecorationOptions = {
245246
glyphMarginClassName: 'debug-breakpoint-glyph',
246247
glyphMarginHoverMessage: { value: 'Breakpoint (click to remove)' },
247248
stickiness: 1
@@ -421,6 +422,54 @@
421422
breakpointDecorations = monacoEditor.deltaDecorations(oldDecorations, decorations)
422423
}
423424
425+
// Refresh breakpoint line numbers from decoration positions after code edits
426+
function refreshBreakpointPositions(): void {
427+
const monacoEditor = editor?.getEditor?.()
428+
if (!monacoEditor || breakpointDecorations.length === 0) return
429+
430+
const model = monacoEditor.getModel()
431+
if (!model) return
432+
433+
// Get current line numbers from decorations (Monaco tracks positions when code changes)
434+
const newLines = new Set<number>()
435+
for (const decorationId of breakpointDecorations) {
436+
const range = model.getDecorationRange(decorationId)
437+
if (range) {
438+
newLines.add(range.startLineNumber)
439+
}
440+
}
441+
442+
// Check if positions changed
443+
const oldLines = Array.from(debugBreakpoints).sort((a, b) => a - b)
444+
const updatedLines = Array.from(newLines).sort((a, b) => a - b)
445+
446+
const positionsChanged =
447+
oldLines.length !== updatedLines.length ||
448+
oldLines.some((line, i) => line !== updatedLines[i])
449+
450+
if (positionsChanged) {
451+
console.log('[DAP] Breakpoint positions changed:', oldLines, '->', updatedLines)
452+
// Update breakpoints set with new positions
453+
debugBreakpoints.clear()
454+
for (const line of newLines) {
455+
debugBreakpoints.add(line)
456+
}
457+
// Sync updated positions with server if connected
458+
syncBreakpointsWithServer()
459+
}
460+
}
461+
462+
// Sync breakpoints with DAP server when connected
463+
async function syncBreakpointsWithServer(): Promise<void> {
464+
if (!dapClient || !dapClient.isConnected()) return
465+
466+
try {
467+
await dapClient.setBreakpoints(debugFilePath, Array.from(debugBreakpoints))
468+
} catch (error) {
469+
console.error('Failed to sync breakpoints:', error)
470+
}
471+
}
472+
424473
function updateCurrentLineDecoration(line: number | undefined): void {
425474
const monacoEditor = editor?.getEditor?.()
426475
if (!monacoEditor) return
@@ -446,8 +495,17 @@
446495
447496
async function startDebugging(): Promise<void> {
448497
try {
449-
// Create a new DAP client with the correct URL for the language
498+
// Always reset and create a fresh DAP client with the correct URL for the current language
499+
// This ensures we connect to the correct endpoint even if language changed
500+
resetDAPClient()
450501
dapClient = getDAPClient(dapServerUrl)
502+
503+
// Debug: log the code being sent
504+
const codeLines = code?.split('\n').length ?? 0
505+
console.log(`[DEBUG] Starting debug session with ${codeLines} lines of code`)
506+
console.log(`[DEBUG] Code preview (first 500 chars):`, code?.substring(0, 500))
507+
console.log(`[DEBUG] Breakpoints:`, Array.from(debugBreakpoints))
508+
451509
await dapClient.connect()
452510
await dapClient.initialize()
453511
await dapClient.setBreakpoints(debugFilePath, Array.from(debugBreakpoints))
@@ -518,6 +576,33 @@
518576
}
519577
})
520578
579+
// Watch for language changes - exit debug mode and reset client when language changes
580+
let lastDebugLang: typeof lang | undefined = undefined
581+
$effect(() => {
582+
const currentLang = lang
583+
if (lastDebugLang !== undefined && lastDebugLang !== currentLang && debugMode) {
584+
// Language changed while in debug mode - exit debug mode
585+
console.log('[DAP] Language changed from', lastDebugLang, 'to', currentLang, '- exiting debug mode')
586+
untrack(() => {
587+
// Stop any running debug session
588+
if (dapClient) {
589+
dapClient.terminate().catch(() => {}).finally(() => {
590+
dapClient?.disconnect()
591+
})
592+
}
593+
// Reset the singleton
594+
resetDAPClient()
595+
dapClient = null
596+
// Exit debug mode
597+
debugMode = false
598+
// Clear decorations
599+
clearAllBreakpoints()
600+
updateCurrentLineDecoration(undefined)
601+
})
602+
}
603+
lastDebugLang = currentLang
604+
})
605+
521606
522607
// Set up glyph margin click handler for breakpoints when debug mode is enabled
523608
$effect(() => {
@@ -1166,6 +1251,10 @@
11661251
awareness={wsProvider?.awareness}
11671252
on:change={(e) => {
11681253
inferSchema(e.detail)
1254+
// Refresh breakpoint positions when code changes (decorations track their lines)
1255+
if (debugMode && breakpointDecorations.length > 0) {
1256+
refreshBreakpointPositions()
1257+
}
11691258
}}
11701259
on:saveDraft
11711260
on:toggleTestPanel={toggleTestPanel}

frontend/src/lib/debug/MonacoDebugger.svelte

Lines changed: 100 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,25 @@
22
import { onMount, onDestroy } from 'svelte'
33
import { SvelteSet } from 'svelte/reactivity'
44
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'
66
import DebugToolbar from './DebugToolbar.svelte'
77
import DebugPanel from './DebugPanel.svelte'
8+
import { DAP_SERVER_URLS, getDebugFileExtension, type DebugLanguage } from './index'
89
910
interface Props {
1011
editor: meditor.IStandaloneCodeEditor | null
1112
code: string
13+
language?: DebugLanguage
1214
filePath?: string
1315
dapServerUrl?: string
1416
}
1517
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)}`)
1724
1825
let client: DAPClient | null = $state(null)
1926
let breakpointDecorations: string[] = $state([])
@@ -37,10 +44,48 @@
3744
// Track breakpoints by line number
3845
let breakpoints = new SvelteSet<number>()
3946
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+
4084
onMount(() => {
4185
if (!editor) return
4286
43-
client = getDAPClient(dapServerUrl)
87+
client = getDAPClient(effectiveServerUrl)
88+
lastServerUrl = effectiveServerUrl
4489
4590
// Add click handler for glyph margin (breakpoint toggle)
4691
const mouseDownDisposable = editor.onMouseDown((e) => {
@@ -105,6 +150,45 @@
105150
breakpointDecorations = editor.deltaDecorations(breakpointDecorations, decorations)
106151
}
107152
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+
108192
function updateCurrentLineDecoration(line: number | undefined): void {
109193
if (!editor) return
110194
@@ -127,24 +211,31 @@
127211
}
128212
129213
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+
}
131219
132220
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')
134224
} catch (error) {
135225
console.error('Failed to sync breakpoints:', error)
136226
}
137227
}
138228
139229
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)
143234
144235
try {
145236
await client.connect()
146237
await client.initialize()
147-
await client.setBreakpoints(filePath, Array.from(breakpoints))
238+
await client.setBreakpoints(effectiveFilePath, Array.from(breakpoints))
148239
await client.configurationDone()
149240
await client.launch({ code, cwd: '/tmp' })
150241
} catch (error) {

0 commit comments

Comments
 (0)