@@ -43,13 +43,22 @@ import { DebugContribution } from './debug-contribution';
43
43
import { Deferred , waitForEvent } from '@theia/core/lib/common/promise-util' ;
44
44
import { WorkspaceService } from '@theia/workspace/lib/browser' ;
45
45
import { DebugInstructionBreakpoint } from './model/debug-instruction-breakpoint' ;
46
+ import { FrontendApplicationConfigProvider } from '@theia/core/lib/browser/frontend-application-config-provider' ;
46
47
47
48
export enum DebugState {
48
49
Inactive ,
49
50
Initializing ,
50
51
Running ,
51
52
Stopped
52
53
}
54
+ export function debugStateLabel ( state : DebugState ) : string {
55
+ switch ( state ) {
56
+ case DebugState . Initializing : return 'initializing' ;
57
+ case DebugState . Stopped : return 'stopped' ;
58
+ case DebugState . Running : return 'running' ;
59
+ default : return 'inactive' ;
60
+ }
61
+ }
53
62
54
63
// FIXME: make injectable to allow easily inject services
55
64
export class DebugSession implements CompositeTreeElement {
@@ -74,7 +83,11 @@ export class DebugSession implements CompositeTreeElement {
74
83
protected readonly childSessions = new Map < string , DebugSession > ( ) ;
75
84
protected readonly toDispose = new DisposableCollection ( ) ;
76
85
77
- private isStopping : boolean = false ;
86
+ protected isStopping : boolean = false ;
87
+ /**
88
+ * Number of millis after a `stop` request times out.
89
+ */
90
+ protected stopTimeout = 5_000 ;
78
91
79
92
constructor (
80
93
readonly id : string ,
@@ -274,19 +287,26 @@ export class DebugSession implements CompositeTreeElement {
274
287
}
275
288
276
289
protected async initialize ( ) : Promise < void > {
277
- const response = await this . connection . sendRequest ( 'initialize' , {
278
- clientID : 'Theia' ,
279
- clientName : 'Theia IDE' ,
280
- adapterID : this . configuration . type ,
281
- locale : 'en-US' ,
282
- linesStartAt1 : true ,
283
- columnsStartAt1 : true ,
284
- pathFormat : 'path' ,
285
- supportsVariableType : false ,
286
- supportsVariablePaging : false ,
287
- supportsRunInTerminalRequest : true
288
- } ) ;
289
- this . updateCapabilities ( response ?. body || { } ) ;
290
+ const clientName = FrontendApplicationConfigProvider . get ( ) . applicationName ;
291
+ try {
292
+ const response = await this . connection . sendRequest ( 'initialize' , {
293
+ clientID : clientName . toLocaleLowerCase ( ) . replace ( / \s + / g, '_' ) ,
294
+ clientName,
295
+ adapterID : this . configuration . type ,
296
+ locale : 'en-US' ,
297
+ linesStartAt1 : true ,
298
+ columnsStartAt1 : true ,
299
+ pathFormat : 'path' ,
300
+ supportsVariableType : false ,
301
+ supportsVariablePaging : false ,
302
+ supportsRunInTerminalRequest : true
303
+ } ) ;
304
+ this . updateCapabilities ( response ?. body || { } ) ;
305
+ this . didReceiveCapabilities . resolve ( ) ;
306
+ } catch ( err ) {
307
+ this . didReceiveCapabilities . reject ( err ) ;
308
+ throw err ;
309
+ }
290
310
}
291
311
292
312
protected async launchOrAttach ( ) : Promise < void > {
@@ -304,8 +324,17 @@ export class DebugSession implements CompositeTreeElement {
304
324
}
305
325
}
306
326
327
+ /**
328
+ * The `send('initialize')` request could resolve later than `on('initialized')` emits the event.
329
+ * Hence, the `configure` would use the empty object `capabilities`.
330
+ * Using the empty `capabilities` could result in missing exception breakpoint filters, as
331
+ * always `capabilities.exceptionBreakpointFilters` is falsy. This deferred promise works
332
+ * around this timing issue. https://github.com/eclipse-theia/theia/issues/11886
333
+ */
334
+ protected didReceiveCapabilities = new Deferred < void > ( ) ;
307
335
protected initialized = false ;
308
336
protected async configure ( ) : Promise < void > {
337
+ await this . didReceiveCapabilities . promise ;
309
338
if ( this . capabilities . exceptionBreakpointFilters ) {
310
339
const exceptionBreakpoints = [ ] ;
311
340
for ( const filter of this . capabilities . exceptionBreakpointFilters ) {
@@ -340,24 +369,39 @@ export class DebugSession implements CompositeTreeElement {
340
369
if ( ! this . isStopping ) {
341
370
this . isStopping = true ;
342
371
if ( this . canTerminate ( ) ) {
343
- const terminated = this . waitFor ( 'terminated' , 5000 ) ;
372
+ const terminated = this . waitFor ( 'terminated' , this . stopTimeout ) ;
344
373
try {
345
- await this . connection . sendRequest ( 'terminate' , { restart : isRestart } , 5000 ) ;
374
+ await this . connection . sendRequest ( 'terminate' , { restart : isRestart } , this . stopTimeout ) ;
346
375
await terminated ;
347
376
} catch ( e ) {
348
- console . error ( 'Did not receive terminated event in time' , e ) ;
377
+ this . handleTerminateError ( e ) ;
349
378
}
350
379
} else {
380
+ const terminateDebuggee = this . initialized && this . capabilities . supportTerminateDebuggee ;
351
381
try {
352
- await this . sendRequest ( 'disconnect' , { restart : isRestart } , 5000 ) ;
382
+ await this . sendRequest ( 'disconnect' , { restart : isRestart , terminateDebuggee } , this . stopTimeout ) ;
353
383
} catch ( e ) {
354
- console . error ( 'Error on disconnect' , e ) ;
384
+ this . handleDisconnectError ( e ) ;
355
385
}
356
386
}
357
387
callback ( ) ;
358
388
}
359
389
}
360
390
391
+ /**
392
+ * Invoked when sending the `terminate` request to the debugger is rejected or timed out.
393
+ */
394
+ protected handleTerminateError ( err : unknown ) : void {
395
+ console . error ( 'Did not receive terminated event in time' , err ) ;
396
+ }
397
+
398
+ /**
399
+ * Invoked when sending the `disconnect` request to the debugger is rejected or timed out.
400
+ */
401
+ protected handleDisconnectError ( err : unknown ) : void {
402
+ console . error ( 'Error on disconnect' , err ) ;
403
+ }
404
+
361
405
async disconnect ( isRestart : boolean , callback : ( ) => void ) : Promise < void > {
362
406
if ( ! this . isStopping ) {
363
407
this . isStopping = true ;
@@ -665,12 +709,17 @@ export class DebugSession implements CompositeTreeElement {
665
709
const response = await this . sendRequest ( 'setFunctionBreakpoints' , {
666
710
breakpoints : enabled . map ( b => b . origin . raw )
667
711
} ) ;
668
- response . body . breakpoints . forEach ( ( raw , index ) => {
669
- // node debug adapter returns more breakpoints sometimes
670
- if ( enabled [ index ] ) {
671
- enabled [ index ] . update ( { raw } ) ;
672
- }
673
- } ) ;
712
+ // Apparently, `body` and `breakpoints` can be missing.
713
+ // https://github.com/eclipse-theia/theia/issues/11885
714
+ // https://github.com/microsoft/vscode/blob/80004351ccf0884b58359f7c8c801c91bb827d83/src/vs/workbench/contrib/debug/browser/debugSession.ts#L448-L449
715
+ if ( response && response . body ) {
716
+ response . body . breakpoints . forEach ( ( raw , index ) => {
717
+ // node debug adapter returns more breakpoints sometimes
718
+ if ( enabled [ index ] ) {
719
+ enabled [ index ] . update ( { raw } ) ;
720
+ }
721
+ } ) ;
722
+ }
674
723
} catch ( error ) {
675
724
// could be error or promise rejection of DebugProtocol.SetFunctionBreakpoints
676
725
if ( error instanceof Error ) {
@@ -699,10 +748,12 @@ export class DebugSession implements CompositeTreeElement {
699
748
) ;
700
749
const enabled = all . filter ( b => b . enabled ) ;
701
750
try {
751
+ const breakpoints = enabled . map ( ( { origin } ) => origin . raw ) ;
702
752
const response = await this . sendRequest ( 'setBreakpoints' , {
703
753
source : source . raw ,
704
754
sourceModified,
705
- breakpoints : enabled . map ( ( { origin } ) => origin . raw )
755
+ breakpoints,
756
+ lines : breakpoints . map ( ( { line } ) => line )
706
757
} ) ;
707
758
response . body . breakpoints . forEach ( ( raw , index ) => {
708
759
// node debug adapter returns more breakpoints sometimes
0 commit comments