@theia/debug 1.18.0 → 1.21.0-next.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/lib/browser/console/debug-console-contribution.d.ts +1 -0
  2. package/lib/browser/console/debug-console-contribution.d.ts.map +1 -1
  3. package/lib/browser/console/debug-console-contribution.js +8 -6
  4. package/lib/browser/console/debug-console-contribution.js.map +1 -1
  5. package/lib/browser/console/debug-console-session.d.ts +1 -1
  6. package/lib/browser/console/debug-console-session.js +2 -2
  7. package/lib/browser/console/debug-console-session.js.map +1 -1
  8. package/lib/browser/debug-configuration-manager.d.ts.map +1 -1
  9. package/lib/browser/debug-configuration-manager.js +8 -7
  10. package/lib/browser/debug-configuration-manager.js.map +1 -1
  11. package/lib/browser/debug-frontend-application-contribution.d.ts +5 -12
  12. package/lib/browser/debug-frontend-application-contribution.d.ts.map +1 -1
  13. package/lib/browser/debug-frontend-application-contribution.js +139 -112
  14. package/lib/browser/debug-frontend-application-contribution.js.map +1 -1
  15. package/lib/browser/debug-preferences.d.ts +1 -0
  16. package/lib/browser/debug-preferences.d.ts.map +1 -1
  17. package/lib/browser/debug-preferences.js +22 -6
  18. package/lib/browser/debug-preferences.js.map +1 -1
  19. package/lib/browser/debug-prefix-configuration.d.ts.map +1 -1
  20. package/lib/browser/debug-prefix-configuration.js +3 -3
  21. package/lib/browser/debug-prefix-configuration.js.map +1 -1
  22. package/lib/browser/debug-session-connection.d.ts +23 -18
  23. package/lib/browser/debug-session-connection.d.ts.map +1 -1
  24. package/lib/browser/debug-session-connection.js +86 -65
  25. package/lib/browser/debug-session-connection.js.map +1 -1
  26. package/lib/browser/debug-session-contribution.d.ts +1 -1
  27. package/lib/browser/debug-session-contribution.d.ts.map +1 -1
  28. package/lib/browser/debug-session-contribution.js +1 -1
  29. package/lib/browser/debug-session-contribution.js.map +1 -1
  30. package/lib/browser/debug-session-manager.d.ts +6 -14
  31. package/lib/browser/debug-session-manager.d.ts.map +1 -1
  32. package/lib/browser/debug-session-manager.js +41 -44
  33. package/lib/browser/debug-session-manager.js.map +1 -1
  34. package/lib/browser/debug-session.d.ts +9 -8
  35. package/lib/browser/debug-session.d.ts.map +1 -1
  36. package/lib/browser/debug-session.js +55 -58
  37. package/lib/browser/debug-session.js.map +1 -1
  38. package/lib/browser/model/debug-function-breakpoint.d.ts.map +1 -1
  39. package/lib/browser/model/debug-function-breakpoint.js +4 -3
  40. package/lib/browser/model/debug-function-breakpoint.js.map +1 -1
  41. package/lib/browser/preferences/launch-preferences.d.ts.map +1 -1
  42. package/lib/browser/preferences/launch-preferences.js +2 -1
  43. package/lib/browser/preferences/launch-preferences.js.map +1 -1
  44. package/lib/browser/view/debug-breakpoints-source.d.ts +0 -1
  45. package/lib/browser/view/debug-breakpoints-source.d.ts.map +1 -1
  46. package/lib/browser/view/debug-breakpoints-source.js +1 -7
  47. package/lib/browser/view/debug-breakpoints-source.js.map +1 -1
  48. package/lib/browser/view/debug-breakpoints-widget.d.ts.map +1 -1
  49. package/lib/browser/view/debug-breakpoints-widget.js +2 -1
  50. package/lib/browser/view/debug-breakpoints-widget.js.map +1 -1
  51. package/lib/browser/view/debug-configuration-widget.d.ts +1 -1
  52. package/lib/browser/view/debug-configuration-widget.d.ts.map +1 -1
  53. package/lib/browser/view/debug-configuration-widget.js +8 -8
  54. package/lib/browser/view/debug-configuration-widget.js.map +1 -1
  55. package/lib/browser/view/debug-session-widget.d.ts +2 -1
  56. package/lib/browser/view/debug-session-widget.d.ts.map +1 -1
  57. package/lib/browser/view/debug-session-widget.js +12 -6
  58. package/lib/browser/view/debug-session-widget.js.map +1 -1
  59. package/lib/browser/view/debug-stack-frames-source.d.ts +0 -1
  60. package/lib/browser/view/debug-stack-frames-source.d.ts.map +1 -1
  61. package/lib/browser/view/debug-stack-frames-source.js +2 -5
  62. package/lib/browser/view/debug-stack-frames-source.js.map +1 -1
  63. package/lib/browser/view/debug-stack-frames-widget.d.ts.map +1 -1
  64. package/lib/browser/view/debug-stack-frames-widget.js +2 -1
  65. package/lib/browser/view/debug-stack-frames-widget.js.map +1 -1
  66. package/lib/browser/view/debug-threads-source.d.ts +0 -1
  67. package/lib/browser/view/debug-threads-source.d.ts.map +1 -1
  68. package/lib/browser/view/debug-threads-source.js +1 -7
  69. package/lib/browser/view/debug-threads-source.js.map +1 -1
  70. package/lib/browser/view/debug-threads-widget.d.ts.map +1 -1
  71. package/lib/browser/view/debug-threads-widget.js +2 -1
  72. package/lib/browser/view/debug-threads-widget.js.map +1 -1
  73. package/lib/browser/view/debug-toolbar-widget.d.ts.map +1 -1
  74. package/lib/browser/view/debug-toolbar-widget.js +9 -8
  75. package/lib/browser/view/debug-toolbar-widget.js.map +1 -1
  76. package/lib/browser/view/debug-variables-source.d.ts +0 -1
  77. package/lib/browser/view/debug-variables-source.d.ts.map +1 -1
  78. package/lib/browser/view/debug-variables-source.js +2 -5
  79. package/lib/browser/view/debug-variables-source.js.map +1 -1
  80. package/lib/browser/view/debug-variables-widget.d.ts.map +1 -1
  81. package/lib/browser/view/debug-variables-widget.js +2 -1
  82. package/lib/browser/view/debug-variables-widget.js.map +1 -1
  83. package/lib/browser/view/debug-view-model.js +3 -3
  84. package/lib/browser/view/debug-view-model.js.map +1 -1
  85. package/lib/browser/view/debug-watch-source.d.ts +0 -1
  86. package/lib/browser/view/debug-watch-source.d.ts.map +1 -1
  87. package/lib/browser/view/debug-watch-source.js +2 -5
  88. package/lib/browser/view/debug-watch-source.js.map +1 -1
  89. package/lib/browser/view/debug-watch-widget.d.ts.map +1 -1
  90. package/lib/browser/view/debug-watch-widget.js +2 -1
  91. package/lib/browser/view/debug-watch-widget.js.map +1 -1
  92. package/lib/browser/view/debug-widget.d.ts.map +1 -1
  93. package/lib/browser/view/debug-widget.js +2 -1
  94. package/lib/browser/view/debug-widget.js.map +1 -1
  95. package/lib/common/debug-service.d.ts +10 -0
  96. package/lib/common/debug-service.d.ts.map +1 -1
  97. package/lib/node/debug-adapter-contribution-registry.d.ts +1 -1
  98. package/lib/node/debug-adapter-contribution-registry.d.ts.map +1 -1
  99. package/lib/node/debug-adapter-contribution-registry.js +1 -1
  100. package/lib/node/debug-adapter-contribution-registry.js.map +1 -1
  101. package/lib/node/debug-adapter-factory.d.ts +4 -4
  102. package/lib/node/debug-adapter-factory.d.ts.map +1 -1
  103. package/lib/node/debug-adapter-factory.js +10 -12
  104. package/lib/node/debug-adapter-factory.js.map +1 -1
  105. package/lib/node/debug-adapter-session-manager.d.ts +1 -1
  106. package/lib/node/debug-adapter-session-manager.d.ts.map +1 -1
  107. package/lib/node/debug-adapter-session-manager.js +1 -2
  108. package/lib/node/debug-adapter-session-manager.js.map +1 -1
  109. package/lib/node/debug-adapter-session.d.ts +7 -13
  110. package/lib/node/debug-adapter-session.d.ts.map +1 -1
  111. package/lib/node/debug-adapter-session.js +25 -68
  112. package/lib/node/debug-adapter-session.js.map +1 -1
  113. package/lib/node/debug-backend-module.js +1 -1
  114. package/lib/node/debug-backend-module.js.map +1 -1
  115. package/lib/{common → node}/debug-model.d.ts +37 -19
  116. package/lib/node/debug-model.d.ts.map +1 -0
  117. package/lib/{common → node}/debug-model.js +0 -0
  118. package/lib/node/debug-model.js.map +1 -0
  119. package/lib/node/inline-debug-adapter.d.ts +34 -0
  120. package/lib/node/inline-debug-adapter.d.ts.map +1 -0
  121. package/lib/node/inline-debug-adapter.js +45 -0
  122. package/lib/node/inline-debug-adapter.js.map +1 -0
  123. package/lib/node/stream-debug-adapter.d.ts +52 -0
  124. package/lib/node/stream-debug-adapter.d.ts.map +1 -0
  125. package/lib/node/stream-debug-adapter.js +113 -0
  126. package/lib/node/stream-debug-adapter.js.map +1 -0
  127. package/lib/node/vscode/vscode-debug-adapter-contribution.d.ts +1 -1
  128. package/lib/node/vscode/vscode-debug-adapter-contribution.d.ts.map +1 -1
  129. package/package.json +17 -17
  130. package/src/browser/console/debug-console-contribution.tsx +9 -6
  131. package/src/browser/console/debug-console-session.ts +2 -2
  132. package/src/browser/debug-configuration-manager.ts +10 -8
  133. package/src/browser/debug-frontend-application-contribution.ts +160 -125
  134. package/src/browser/debug-preferences.ts +23 -6
  135. package/src/browser/debug-prefix-configuration.ts +4 -4
  136. package/src/browser/debug-session-connection.ts +102 -76
  137. package/src/browser/debug-session-contribution.ts +3 -4
  138. package/src/browser/debug-session-manager.ts +45 -47
  139. package/src/browser/debug-session.tsx +54 -57
  140. package/src/browser/model/debug-function-breakpoint.tsx +4 -3
  141. package/src/browser/preferences/launch-preferences.ts +2 -1
  142. package/src/browser/view/debug-breakpoints-source.tsx +0 -6
  143. package/src/browser/view/debug-breakpoints-widget.ts +2 -1
  144. package/src/browser/view/debug-configuration-widget.tsx +8 -7
  145. package/src/browser/view/debug-session-widget.ts +12 -6
  146. package/src/browser/view/debug-stack-frames-source.tsx +0 -6
  147. package/src/browser/view/debug-stack-frames-widget.ts +2 -1
  148. package/src/browser/view/debug-threads-source.tsx +0 -6
  149. package/src/browser/view/debug-threads-widget.ts +2 -1
  150. package/src/browser/view/debug-toolbar-widget.tsx +13 -8
  151. package/src/browser/view/debug-variables-source.ts +0 -6
  152. package/src/browser/view/debug-variables-widget.ts +2 -1
  153. package/src/browser/view/debug-view-model.ts +3 -3
  154. package/src/browser/view/debug-watch-source.ts +0 -6
  155. package/src/browser/view/debug-watch-widget.ts +2 -1
  156. package/src/browser/view/debug-widget.ts +2 -1
  157. package/src/common/debug-service.ts +13 -0
  158. package/src/node/debug-adapter-contribution-registry.ts +1 -1
  159. package/src/node/debug-adapter-factory.ts +17 -17
  160. package/src/node/debug-adapter-session-manager.ts +1 -2
  161. package/src/node/debug-adapter-session.ts +32 -80
  162. package/src/node/debug-backend-module.ts +1 -1
  163. package/src/{common → node}/debug-model.ts +38 -18
  164. package/src/node/inline-debug-adapter.ts +47 -0
  165. package/src/node/stream-debug-adapter.ts +126 -0
  166. package/src/node/vscode/vscode-debug-adapter-contribution.ts +1 -1
  167. package/lib/common/debug-model.d.ts.map +0 -1
  168. package/lib/common/debug-model.js.map +0 -1
@@ -19,13 +19,9 @@
19
19
  import { DebugProtocol } from 'vscode-debugprotocol';
20
20
  import { Deferred } from '@theia/core/lib/common/promise-util';
21
21
  import { Event, Emitter, DisposableCollection, Disposable, MaybePromise } from '@theia/core';
22
- import { OutputChannel } from '@theia/output/lib/common/output-channel';
23
- import { IWebSocket } from '@theia/core/shared/vscode-ws-jsonrpc';
22
+ import { OutputChannel } from '@theia/output/lib/browser/output-channel';
24
23
 
25
- export interface DebugExitEvent {
26
- code?: number
27
- reason?: string | Error
28
- }
24
+ import { Channel } from '../common/debug-service';
29
25
 
30
26
  export type DebugRequestHandler = (request: DebugProtocol.Request) => MaybePromise<any>;
31
27
 
@@ -78,7 +74,7 @@ export interface DebugEventTypes {
78
74
  'breakpoint': DebugProtocol.BreakpointEvent
79
75
  'capabilities': DebugProtocol.CapabilitiesEvent
80
76
  'continued': DebugProtocol.ContinuedEvent
81
- 'exited': DebugExitEvent
77
+ 'exited': DebugProtocol.ExitedEvent,
82
78
  'initialized': DebugProtocol.InitializedEvent
83
79
  'invalidated': DebugProtocol.InvalidatedEvent
84
80
  'loadedSource': DebugProtocol.LoadedSourceEvent
@@ -92,6 +88,15 @@ export interface DebugEventTypes {
92
88
  'terminated': DebugProtocol.TerminatedEvent
93
89
  'thread': DebugProtocol.ThreadEvent
94
90
  }
91
+
92
+ export type DebugEventNames = keyof DebugEventTypes;
93
+
94
+ export namespace DebugEventTypes {
95
+ export function isStandardEvent(evt: string): evt is DebugEventNames {
96
+ return standardDebugEvents.has(evt);
97
+ };
98
+ }
99
+
95
100
  const standardDebugEvents = new Set<string>([
96
101
  'breakpoint',
97
102
  'capabilities',
@@ -115,14 +120,19 @@ export class DebugSessionConnection implements Disposable {
115
120
 
116
121
  private sequence = 1;
117
122
 
118
- protected readonly pendingRequests = new Map<number, (response: DebugProtocol.Response) => void>();
119
- protected readonly connection: Promise<IWebSocket>;
123
+ protected readonly pendingRequests = new Map<number, Deferred<DebugProtocol.Response>>();
124
+ protected readonly connectionPromise: Promise<Channel>;
120
125
 
121
126
  protected readonly requestHandlers = new Map<string, DebugRequestHandler>();
122
127
 
123
128
  protected readonly onDidCustomEventEmitter = new Emitter<DebugProtocol.Event>();
124
129
  readonly onDidCustomEvent: Event<DebugProtocol.Event> = this.onDidCustomEventEmitter.event;
125
130
 
131
+ protected readonly onDidCloseEmitter = new Emitter<void>();
132
+ readonly onDidClose: Event<void> = this.onDidCloseEmitter.event;
133
+
134
+ protected isClosed = false;
135
+
126
136
  protected readonly toDispose = new DisposableCollection(
127
137
  this.onDidCustomEventEmitter,
128
138
  Disposable.create(() => this.pendingRequests.clear()),
@@ -131,15 +141,16 @@ export class DebugSessionConnection implements Disposable {
131
141
 
132
142
  constructor(
133
143
  readonly sessionId: string,
134
- protected readonly connectionFactory: (sessionId: string) => Promise<IWebSocket>,
144
+ connectionFactory: (sessionId: string) => Promise<Channel>,
135
145
  protected readonly traceOutputChannel: OutputChannel | undefined
136
146
  ) {
137
- this.connection = this.createConnection();
147
+ this.connectionPromise = this.createConnection(connectionFactory);
138
148
  }
139
149
 
140
150
  get disposed(): boolean {
141
151
  return this.toDispose.disposed;
142
152
  }
153
+
143
154
  protected checkDisposed(): void {
144
155
  if (this.disposed) {
145
156
  throw new Error('the debug session connection is disposed, id: ' + this.sessionId);
@@ -150,23 +161,20 @@ export class DebugSessionConnection implements Disposable {
150
161
  this.toDispose.dispose();
151
162
  }
152
163
 
153
- protected async createConnection(): Promise<IWebSocket> {
154
- if (this.disposed) {
155
- throw new Error('Connection has been already disposed.');
156
- } else {
157
- const connection = await this.connectionFactory(this.sessionId);
158
- connection.onClose((code, reason) => {
159
- connection.dispose();
160
- this.fire('exited', { code, reason });
161
- });
162
- connection.onMessage(data => this.handleMessage(data));
163
- return connection;
164
- }
164
+ protected async createConnection(connectionFactory: (sessionId: string) => Promise<Channel>): Promise<Channel> {
165
+ const connection = await connectionFactory(this.sessionId);
166
+ connection.onClose(() => {
167
+ this.isClosed = true;
168
+ this.cancelPendingRequests();
169
+ this.onDidCloseEmitter.fire();
170
+ });
171
+ connection.onMessage(data => this.handleMessage(data));
172
+ return connection;
165
173
  }
166
174
 
167
175
  protected allThreadsContinued = true;
168
- async sendRequest<K extends keyof DebugRequestTypes>(command: K, args: DebugRequestTypes[K][0]): Promise<DebugRequestTypes[K][1]> {
169
- const result = await this.doSendRequest(command, args);
176
+ async sendRequest<K extends keyof DebugRequestTypes>(command: K, args: DebugRequestTypes[K][0], timeout?: number): Promise<DebugRequestTypes[K][1]> {
177
+ const result = await this.doSendRequest(command, args, timeout);
170
178
  if (command === 'next' || command === 'stepIn' ||
171
179
  command === 'stepOut' || command === 'stepBack' ||
172
180
  command === 'reverseContinue' || command === 'restartFrame') {
@@ -186,56 +194,69 @@ export class DebugSessionConnection implements Disposable {
186
194
  sendCustomRequest<T extends DebugProtocol.Response>(command: string, args?: any): Promise<T> {
187
195
  return this.doSendRequest<T>(command, args);
188
196
  }
189
- protected async doSendRequest<K extends DebugProtocol.Response>(command: string, args?: any): Promise<K> {
197
+
198
+ protected cancelPendingRequests(): void {
199
+ this.pendingRequests.forEach((deferred, requestId) => {
200
+ deferred.reject(new Error(`Request ${requestId} cancelled on connection close`));
201
+ });
202
+ }
203
+
204
+ protected doSendRequest<K extends DebugProtocol.Response>(command: string, args?: any, timeout?: number): Promise<K> {
190
205
  const result = new Deferred<K>();
191
206
 
192
- const request: DebugProtocol.Request = {
193
- seq: this.sequence++,
194
- type: 'request',
195
- command: command,
196
- arguments: args
197
- };
207
+ if (this.isClosed) {
208
+ result.reject(new Error('Connection is closed'));
209
+ } else {
210
+ const request: DebugProtocol.Request = {
211
+ seq: this.sequence++,
212
+ type: 'request',
213
+ command: command,
214
+ arguments: args
215
+ };
198
216
 
199
- const onDispose = this.toDispose.push(Disposable.create(() => {
200
- const pendingRequest = this.pendingRequests.get(request.seq);
201
- if (pendingRequest) {
202
- pendingRequest({
203
- type: 'response',
204
- request_seq: request.seq,
205
- command: request.command,
206
- seq: 0,
207
- success: false,
208
- message: 'debug session is closed'
209
- });
217
+ this.pendingRequests.set(request.seq, result);
218
+ if (timeout) {
219
+ const handle = setTimeout(() => {
220
+ const pendingRequest = this.pendingRequests.get(request.seq);
221
+ if (pendingRequest) {
222
+ // request has not been handled
223
+ this.pendingRequests.delete(request.seq);
224
+ const error: DebugProtocol.Response = {
225
+ type: 'response',
226
+ seq: 0,
227
+ request_seq: request.seq,
228
+ success: false,
229
+ command,
230
+ message: `Request #${request.seq}: ${request.command} timed out`
231
+ };
232
+ pendingRequest.reject(error);
233
+ }
234
+ }, timeout);
235
+ result.promise.finally(() => clearTimeout(handle));
210
236
  }
211
- }));
212
- this.pendingRequests.set(request.seq, (response: K) => {
213
- onDispose.dispose();
214
- if (!response.success) {
215
- result.reject(response);
216
- } else {
217
- result.resolve(response);
218
- }
219
- });
220
-
221
- await this.send(request);
237
+ this.send(request);
238
+ }
222
239
  return result.promise;
223
240
  }
224
241
 
225
242
  protected async send(message: DebugProtocol.ProtocolMessage): Promise<void> {
226
- const connection = await this.connection;
243
+ const connection = await this.connectionPromise;
227
244
  const messageStr = JSON.stringify(message);
228
245
  if (this.traceOutputChannel) {
229
- this.traceOutputChannel.appendLine(`${this.sessionId.substring(0, 8)} theia -> adapter: ${messageStr}`);
246
+ const now = new Date();
247
+ const dateStr = `${now.toLocaleString(undefined, { hour12: false })}.${now.getMilliseconds()}`;
248
+ this.traceOutputChannel.appendLine(`${this.sessionId.substring(0, 8)} ${dateStr} theia -> adapter: ${JSON.stringify(message, undefined, 4)}`);
230
249
  }
231
250
  connection.send(messageStr);
232
251
  }
233
252
 
234
253
  protected handleMessage(data: string): void {
254
+ const message: DebugProtocol.ProtocolMessage = JSON.parse(data);
235
255
  if (this.traceOutputChannel) {
236
- this.traceOutputChannel.appendLine(`${this.sessionId.substring(0, 8)} theia <- adapter: ${data}`);
256
+ const now = new Date();
257
+ const dateStr = `${now.toLocaleString(undefined, { hour12: false })}.${now.getMilliseconds()}`;
258
+ this.traceOutputChannel.appendLine(`${this.sessionId.substring(0, 8)} ${dateStr} theia <- adapter: ${JSON.stringify(message, undefined, 4)}`);
237
259
  }
238
- const message: DebugProtocol.ProtocolMessage = JSON.parse(data);
239
260
  if (message.type === 'request') {
240
261
  this.handleRequest(message as DebugProtocol.Request);
241
262
  } else if (message.type === 'response') {
@@ -246,10 +267,14 @@ export class DebugSessionConnection implements Disposable {
246
267
  }
247
268
 
248
269
  protected handleResponse(response: DebugProtocol.Response): void {
249
- const callback = this.pendingRequests.get(response.request_seq);
250
- if (callback) {
270
+ const pendingRequest = this.pendingRequests.get(response.request_seq);
271
+ if (pendingRequest) {
251
272
  this.pendingRequests.delete(response.request_seq);
252
- callback(response);
273
+ if (!response.success) {
274
+ pendingRequest.reject(response);
275
+ } else {
276
+ pendingRequest.resolve(response);
277
+ }
253
278
  }
254
279
  }
255
280
 
@@ -280,36 +305,37 @@ export class DebugSessionConnection implements Disposable {
280
305
  }
281
306
 
282
307
  protected handleEvent(event: DebugProtocol.Event): void {
283
- if ('event' in event) {
284
- if (event.event === 'continued') {
285
- this.allThreadsContinued = (<DebugProtocol.ContinuedEvent>event).body.allThreadsContinued === false ? false : true;
286
- }
287
- if (standardDebugEvents.has(event.event)) {
288
- this.doFire(event.event, event);
289
- } else {
290
- this.onDidCustomEventEmitter.fire(event);
291
- }
308
+ if (event.event === 'continued') {
309
+ this.allThreadsContinued = (<DebugProtocol.ContinuedEvent>event).body.allThreadsContinued === false ? false : true;
310
+ }
311
+ if (DebugEventTypes.isStandardEvent(event.event)) {
312
+ this.doFire(event.event, event);
292
313
  } else {
293
- this.fire('exited', event);
314
+ this.onDidCustomEventEmitter.fire(event);
294
315
  }
295
316
  }
296
317
 
297
- protected readonly emitters = new Map<string, Emitter<DebugProtocol.Event | DebugExitEvent>>();
318
+ protected readonly emitters = new Map<string, Emitter<DebugProtocol.Event>>();
298
319
  on<K extends keyof DebugEventTypes>(kind: K, listener: (e: DebugEventTypes[K]) => any): Disposable {
299
320
  return this.getEmitter(kind).event(listener);
300
321
  }
322
+
323
+ onEvent<K extends keyof DebugEventTypes>(kind: K): Event<DebugEventTypes[K]> {
324
+ return this.getEmitter(kind).event;
325
+ }
326
+
301
327
  protected fire<K extends keyof DebugEventTypes>(kind: K, e: DebugEventTypes[K]): void {
302
328
  this.doFire(kind, e);
303
329
  }
304
- protected doFire(kind: string, e: DebugProtocol.Event | DebugExitEvent): void {
330
+ protected doFire<K extends keyof DebugEventTypes>(kind: K, e: DebugEventTypes[K]): void {
305
331
  this.getEmitter(kind).fire(e);
306
332
  }
307
- protected getEmitter(kind: string): Emitter<DebugProtocol.Event | DebugExitEvent> {
333
+ protected getEmitter<K extends keyof DebugEventTypes>(kind: K): Emitter<DebugEventTypes[K]> {
308
334
  const emitter = this.emitters.get(kind) || this.newEmitter();
309
335
  this.emitters.set(kind, emitter);
310
- return emitter;
336
+ return <Emitter<DebugEventTypes[K]>>emitter;
311
337
  }
312
- protected newEmitter(): Emitter<DebugProtocol.Event | DebugExitEvent> {
338
+ protected newEmitter(): Emitter<DebugProtocol.Event> {
313
339
  const emitter = new Emitter();
314
340
  this.checkDisposed();
315
341
  this.toDispose.push(emitter);
@@ -23,11 +23,10 @@ import { WebSocketConnectionProvider } from '@theia/core/lib/browser/messaging/w
23
23
  import { DebugSession } from './debug-session';
24
24
  import { BreakpointManager } from './breakpoint/breakpoint-manager';
25
25
  import { DebugSessionOptions } from './debug-session-options';
26
- import { OutputChannelManager, OutputChannel } from '@theia/output/lib/common/output-channel';
26
+ import { OutputChannelManager, OutputChannel } from '@theia/output/lib/browser/output-channel';
27
27
  import { DebugPreferences } from './debug-preferences';
28
28
  import { DebugSessionConnection } from './debug-session-connection';
29
- import { IWebSocket } from '@theia/core/shared/vscode-ws-jsonrpc';
30
- import { DebugAdapterPath } from '../common/debug-service';
29
+ import { Channel, DebugAdapterPath } from '../common/debug-service';
31
30
  import { ContributionProvider } from '@theia/core/lib/common/contribution-provider';
32
31
  import { FileService } from '@theia/filesystem/lib/browser/file-service';
33
32
  import { DebugContribution } from './debug-contribution';
@@ -119,7 +118,7 @@ export class DefaultDebugSessionFactory implements DebugSessionFactory {
119
118
  get(sessionId: string, options: DebugSessionOptions, parentSession?: DebugSession): DebugSession {
120
119
  const connection = new DebugSessionConnection(
121
120
  sessionId,
122
- () => new Promise<IWebSocket>(resolve =>
121
+ () => new Promise<Channel>(resolve =>
123
122
  this.connectionProvider.openChannel(`${DebugAdapterPath}/${sessionId}`, channel => {
124
123
  resolve(channel);
125
124
  }, { reconnecting: false })
@@ -278,51 +278,73 @@ export class DebugSessionManager {
278
278
  const restart = event.body && event.body.restart;
279
279
  if (restart) {
280
280
  // postDebugTask isn't run in case of auto restart as well as preLaunchTask
281
- this.doRestart(session, restart);
281
+ this.doRestart(session, !!restart);
282
282
  } else {
283
- session.terminate();
283
+ await session.disconnect(false, () => this.debug.terminateDebugSession(session.id));
284
284
  await this.runTask(session.options.workspaceFolderUri, session.configuration.postDebugTask);
285
285
  }
286
286
  });
287
- session.on('exited', () => this.destroy(session.id));
288
- session.start().then(() => this.onDidStartDebugSessionEmitter.fire(session));
287
+
288
+ session.on('exited', async event => {
289
+ await session.disconnect(false, () => this.debug.terminateDebugSession(session.id));
290
+ });
291
+
292
+ session.onDispose(() => this.cleanup(session));
293
+ session.start().then(() => this.onDidStartDebugSessionEmitter.fire(session)).catch(e => {
294
+ session.stop(false, () => {
295
+ this.debug.terminateDebugSession(session.id);
296
+ });
297
+ });
289
298
  session.onDidCustomEvent(({ event, body }) =>
290
299
  this.onDidReceiveDebugSessionCustomEventEmitter.fire({ event, body, session })
291
300
  );
292
301
  return session;
293
302
  }
294
303
 
295
- restart(): Promise<DebugSession | undefined>;
296
- restart(session: DebugSession): Promise<DebugSession>;
297
- async restart(session: DebugSession | undefined = this.currentSession): Promise<DebugSession | undefined> {
298
- return session && this.doRestart(session);
304
+ protected cleanup(session: DebugSession): void {
305
+ if (this.remove(session.id)) {
306
+ this.onDidDestroyDebugSessionEmitter.fire(session);
307
+ }
299
308
  }
300
- protected async doRestart(session: DebugSession, restart?: any): Promise<DebugSession | undefined> {
301
- if (await session.restart()) {
309
+
310
+ protected async doRestart(session: DebugSession, isRestart: boolean): Promise<DebugSession | undefined> {
311
+ if (session.canRestart()) {
312
+ await session.restart();
302
313
  return session;
303
314
  }
304
- await session.terminate(true);
305
315
  const { options, configuration } = session;
306
- configuration.__restart = restart;
316
+ session.stop(isRestart, () => this.debug.terminateDebugSession(session.id));
317
+ configuration.__restart = isRestart;
307
318
  return this.start(options);
308
319
  }
309
320
 
310
- async terminateSessions(): Promise<void> {
311
- this.updateCurrentSession(undefined);
312
- this.currentSession?.terminate();
321
+ async terminateSession(session?: DebugSession): Promise<void> {
322
+ if (!session) {
323
+ this.updateCurrentSession(this._currentSession);
324
+ session = this._currentSession;
325
+ }
326
+ if (session) {
327
+ session.stop(false, () => this.debug.terminateDebugSession(session!.id));
328
+ }
313
329
  }
314
330
 
315
- async restartSessions(): Promise<void> {
316
- this.updateCurrentSession(undefined);
317
- this.currentSession?.restart();
331
+ async restartSession(session?: DebugSession): Promise<DebugSession | undefined> {
332
+ if (!session) {
333
+ this.updateCurrentSession(this._currentSession);
334
+ session = this._currentSession;
335
+ }
336
+ if (session) {
337
+ return this.doRestart(session, true);
338
+ }
318
339
  }
319
340
 
320
- protected remove(sessionId: string): void {
321
- this._sessions.delete(sessionId);
341
+ protected remove(sessionId: string): boolean {
342
+ const existed = this._sessions.delete(sessionId);
322
343
  const { currentSession } = this;
323
344
  if (currentSession && currentSession.id === sessionId) {
324
345
  this.updateCurrentSession(undefined);
325
346
  }
347
+ return existed;
326
348
  }
327
349
 
328
350
  getSession(sessionId: string): DebugSession | undefined {
@@ -334,7 +356,7 @@ export class DebugSessionManager {
334
356
  }
335
357
 
336
358
  protected _currentSession: DebugSession | undefined;
337
- protected readonly toDisposeOnCurrentSession = new DisposableCollection();
359
+ protected readonly disposeOnCurrentSessionChanged = new DisposableCollection();
338
360
  get currentSession(): DebugSession | undefined {
339
361
  return this._currentSession;
340
362
  }
@@ -342,12 +364,12 @@ export class DebugSessionManager {
342
364
  if (this._currentSession === current) {
343
365
  return;
344
366
  }
345
- this.toDisposeOnCurrentSession.dispose();
367
+ this.disposeOnCurrentSessionChanged.dispose();
346
368
  const previous = this.currentSession;
347
369
  this._currentSession = current;
348
370
  this.onDidChangeActiveDebugSessionEmitter.fire({ previous, current });
349
371
  if (current) {
350
- this.toDisposeOnCurrentSession.push(current.onDidChange(() => {
372
+ this.disposeOnCurrentSessionChanged.push(current.onDidChange(() => {
351
373
  if (this.currentFrame === this.topFrame) {
352
374
  this.open();
353
375
  }
@@ -403,30 +425,6 @@ export class DebugSessionManager {
403
425
  return currentThread && currentThread.topFrame;
404
426
  }
405
427
 
406
- /**
407
- * Destroy the debug session. If session identifier isn't provided then
408
- * all active debug session will be destroyed.
409
- * @param sessionId The session identifier
410
- */
411
- destroy(sessionId?: string): void {
412
- if (sessionId) {
413
- const session = this._sessions.get(sessionId);
414
- if (session) {
415
- this.doDestroy(session);
416
- }
417
- } else {
418
- this._sessions.forEach(session => this.doDestroy(session));
419
- }
420
- }
421
-
422
- private doDestroy(session: DebugSession): void {
423
- this.debug.terminateDebugSession(session.id);
424
-
425
- session.dispose();
426
- this.remove(session.id);
427
- this.onDidDestroyDebugSessionEmitter.fire(session);
428
- }
429
-
430
428
  getFunctionBreakpoints(session: DebugSession | undefined = this.currentSession): DebugFunctionBreakpoint[] {
431
429
  if (session && session.state > DebugState.Initializing) {
432
430
  return session.getFunctionBreakpoints();
@@ -40,6 +40,7 @@ import { TerminalWidgetOptions, TerminalWidget } from '@theia/terminal/lib/brows
40
40
  import { DebugFunctionBreakpoint } from './model/debug-function-breakpoint';
41
41
  import { FileService } from '@theia/filesystem/lib/browser/file-service';
42
42
  import { DebugContribution } from './debug-contribution';
43
+ import { waitForEvent } from '@theia/core/lib/common/promise-util';
43
44
 
44
45
  export enum DebugState {
45
46
  Inactive,
@@ -63,9 +64,10 @@ export class DebugSession implements CompositeTreeElement {
63
64
  }
64
65
 
65
66
  protected readonly childSessions = new Map<string, DebugSession>();
66
-
67
67
  protected readonly toDispose = new DisposableCollection();
68
68
 
69
+ private isStopping: boolean = false;
70
+
69
71
  constructor(
70
72
  readonly id: string,
71
73
  readonly options: DebugSessionOptions,
@@ -80,6 +82,9 @@ export class DebugSession implements CompositeTreeElement {
80
82
  protected readonly debugContributionProvider: ContributionProvider<DebugContribution>
81
83
  ) {
82
84
  this.connection.onRequest('runInTerminal', (request: DebugProtocol.RunInTerminalRequest) => this.runInTerminal(request));
85
+ this.connection.onDidClose(() => {
86
+ this.toDispose.dispose();
87
+ });
83
88
  this.registerDebugContributions(options.configuration.type, this.connection);
84
89
 
85
90
  if (parentSession) {
@@ -88,6 +93,7 @@ export class DebugSession implements CompositeTreeElement {
88
93
  this.parentSession?.childSessions?.delete(id);
89
94
  }));
90
95
  }
96
+ this.connection.onDidClose(() => this.toDispose.dispose());
91
97
  this.toDispose.pushAll([
92
98
  this.onDidChangeEmitter,
93
99
  this.onDidChangeBreakpointsEmitter,
@@ -96,19 +102,18 @@ export class DebugSession implements CompositeTreeElement {
96
102
  this.doUpdateThreads([]);
97
103
  }),
98
104
  this.connection,
99
- this.on('initialized', () => this.configure()),
100
- this.on('breakpoint', ({ body }) => this.updateBreakpoint(body)),
101
- this.on('continued', e => this.handleContinued(e)),
102
- this.on('stopped', e => this.handleStopped(e)),
103
- this.on('thread', e => this.handleThread(e)),
104
- this.on('terminated', () => this.terminated = true),
105
- this.on('capabilities', event => this.updateCapabilities(event.body.capabilities)),
105
+ this.connection.on('initialized', () => this.configure()),
106
+ this.connection.on('breakpoint', ({ body }) => this.updateBreakpoint(body)),
107
+ this.connection.on('continued', e => this.handleContinued(e)),
108
+ this.connection.on('stopped', e => this.handleStopped(e)),
109
+ this.connection.on('thread', e => this.handleThread(e)),
110
+ this.connection.on('capabilities', event => this.updateCapabilities(event.body.capabilities)),
106
111
  this.breakpoints.onDidChangeMarkers(uri => this.updateBreakpoints({ uri, sourceModified: true }))
107
112
  ]);
108
113
  }
109
114
 
110
- dispose(): void {
111
- this.toDispose.dispose();
115
+ get onDispose(): Event<void> {
116
+ return this.toDispose.onDispose;
112
117
  }
113
118
 
114
119
  get configuration(): DebugConfiguration {
@@ -278,8 +283,7 @@ export class DebugSession implements CompositeTreeElement {
278
283
  try {
279
284
  await this.sendRequest((this.configuration.request as keyof DebugRequestTypes), this.configuration);
280
285
  } catch (reason) {
281
- this.fireExited(reason);
282
- await this.messages.showMessage({
286
+ this.messages.showMessage({
283
287
  type: MessageType.Error,
284
288
  text: reason.message || 'Debug session initialization failed. See console for details.',
285
289
  options: {
@@ -308,59 +312,48 @@ export class DebugSession implements CompositeTreeElement {
308
312
  await this.updateThreads(undefined);
309
313
  }
310
314
 
311
- protected terminated = false;
312
- async terminate(restart?: boolean): Promise<void> {
313
- if (!this.terminated && this.capabilities.supportsTerminateRequest && this.configuration.request === 'launch') {
314
- this.terminated = true;
315
- await this.connection.sendRequest('terminate', { restart });
316
- if (!await this.exited(1000)) {
317
- await this.disconnect(restart);
318
- }
319
- } else {
320
- await this.disconnect(restart);
321
- }
315
+ canTerminate(): boolean {
316
+ return !!this.capabilities.supportsTerminateRequest;
322
317
  }
323
318
 
324
- protected async disconnect(restart?: boolean): Promise<void> {
325
- const TIMEOUT_MS = 1000;
326
- Promise.race([
327
- this.sendRequest('disconnect', { restart }),
328
- new Promise(reject => setTimeout(reject, TIMEOUT_MS, new Error('TIMEOUT_ERR')))
329
- ]).then(res => {
330
- if (res instanceof Error) {
331
- this.fireExited(res);
332
- }
333
- }).catch(() => this.fireExited());
319
+ canRestart(): boolean {
320
+ return !!this.capabilities.supportsRestartRequest;
334
321
  }
335
322
 
336
- protected fireExited(reason?: Error): void {
337
- try {
338
- this.connection['fire']('exited', { reason });
339
- } catch (e) {
340
- console.error(e);
323
+ async restart(): Promise<void> {
324
+ if (this.canRestart()) {
325
+ await this.sendRequest('restart', {});
341
326
  }
342
327
  }
343
328
 
344
- protected exited(timeout: number): Promise<boolean> {
345
- return new Promise<boolean>(resolve => {
346
- const listener = this.on('exited', () => {
347
- listener.dispose();
348
- resolve(true);
349
- });
350
- setTimeout(() => {
351
- listener.dispose();
352
- resolve(false);
353
- }, timeout);
354
- });
329
+ async stop(isRestart: boolean, callback: () => void): Promise<void> {
330
+ if (!this.isStopping) {
331
+ this.isStopping = true;
332
+ if (this.canTerminate()) {
333
+ const terminated = this.waitFor('terminated', 5000);
334
+ try {
335
+ await this.connection.sendRequest('terminate', { restart: isRestart }, 5000);
336
+ await terminated;
337
+ } catch (e) {
338
+ console.error('Did not receive terminated event in time', e);
339
+ }
340
+ } else {
341
+ try {
342
+ await this.sendRequest('disconnect', { restart: isRestart }, 5000);
343
+ } catch (e) {
344
+ console.error('Error on disconnect', e);
345
+ }
346
+ }
347
+ callback();
348
+ }
355
349
  }
356
350
 
357
- async restart(): Promise<boolean> {
358
- if (this.capabilities.supportsRestartRequest) {
359
- this.terminated = false;
360
- await this.sendRequest('restart', {});
361
- return true;
351
+ async disconnect(isRestart: boolean, callback: () => void): Promise<void> {
352
+ if (!this.isStopping) {
353
+ this.isStopping = true;
354
+ await this.sendRequest('disconnect', { restart: isRestart });
355
+ callback();
362
356
  }
363
- return false;
364
357
  }
365
358
 
366
359
  async completions(text: string, column: number, line: number): Promise<DebugProtocol.CompletionItem[]> {
@@ -375,8 +368,8 @@ export class DebugSession implements CompositeTreeElement {
375
368
  return response.body;
376
369
  }
377
370
 
378
- sendRequest<K extends keyof DebugRequestTypes>(command: K, args: DebugRequestTypes[K][0]): Promise<DebugRequestTypes[K][1]> {
379
- return this.connection.sendRequest(command, args);
371
+ sendRequest<K extends keyof DebugRequestTypes>(command: K, args: DebugRequestTypes[K][0], timeout?: number): Promise<DebugRequestTypes[K][1]> {
372
+ return this.connection.sendRequest(command, args, timeout);
380
373
  }
381
374
 
382
375
  sendCustomRequest<T extends DebugProtocol.Response>(command: string, args?: any): Promise<T> {
@@ -387,6 +380,10 @@ export class DebugSession implements CompositeTreeElement {
387
380
  return this.connection.on(kind, listener);
388
381
  }
389
382
 
383
+ waitFor<K extends keyof DebugEventTypes>(kind: K, ms: number): Promise<void> {
384
+ return waitForEvent(this.connection.onEvent(kind), ms).then();
385
+ }
386
+
390
387
  get onDidCustomEvent(): Event<DebugProtocol.Event> {
391
388
  return this.connection.onDidCustomEvent;
392
389
  }