Skip to content

Commit

Permalink
Apply breakpoints on startup
Browse files Browse the repository at this point in the history
Signed-off-by: Anatoliy Bazko <[email protected]>
  • Loading branch information
tolusha committed Oct 1, 2018
1 parent 0207b4c commit 89d1024
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 74 deletions.
25 changes: 10 additions & 15 deletions packages/debug/src/browser/breakpoint/breakpoint-applier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ import { DebugSession } from '../debug-model';
export class BreakpointsApplier {
constructor(@inject(BreakpointStorage) protected readonly storage: BreakpointStorage) { }

applySessionBreakpoints(debugSession: DebugSession, source?: DebugProtocol.Source): Promise<void> {
const promises: Promise<void>[] = [];

async applySessionBreakpoints(debugSession: DebugSession, source?: DebugProtocol.Source): Promise<void> {
const breakpoints = this.storage.get(DebugUtils.isSourceBreakpoint)
.filter(b => b.sessionId === debugSession.sessionId)
.filter(b => source ? DebugUtils.checkUri(b, DebugUtils.toUri(source)) : true);
Expand All @@ -44,19 +42,16 @@ export class BreakpointsApplier {

// The array elements are in the same order as the elements
// of the 'breakpoints' in the SetBreakpointsArguments.
promises.push(debugSession.setBreakpoints(args)
.then(response => {
for (const i in breakpointsBySource) {
if (breakpointsBySource) {
if (response.body.breakpoints) {
breakpointsBySource[i].created = response.body.breakpoints[i];
}
}
const response = await debugSession.setBreakpoints(args);
for (const i in breakpointsBySource) {
if (breakpointsBySource) {
if (response.body.breakpoints) {
breakpointsBySource[i].created = response.body.breakpoints[i];
}
return breakpointsBySource;
}).then(result => this.storage.update(result)));
}
}
}

return Promise.all(promises).then(() => { });
this.storage.update(breakpointsBySource);
}
}
}
20 changes: 15 additions & 5 deletions packages/debug/src/browser/breakpoint/breakpoint-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { BreakpointsApplier } from './breakpoint-applier';

/**
* The breakpoint manager implementation.
* When debug session is created then breakpoints are assigned to the session.
* When session is terminated then breakpoints are unassigned from the session.
*/
@injectable()
export class BreakpointsManager implements FrontendApplicationContribution {
Expand Down Expand Up @@ -128,14 +130,17 @@ export class BreakpointsManager implements FrontendApplicationContribution {
debugSession.on('configurationDone', event => this.onConfigurationDone(debugSession, event));
debugSession.on('breakpoint', event => this.onBreakpoint(debugSession, event));

this.assignBreakpointsToSession(debugSession);
}

private assignBreakpointsToSession(debugSession: DebugSession) {
const breakpoints = this.breakpointStorage.get(DebugUtils.isSourceBreakpoint)
.filter(b => b.sessionId === undefined)
.map(b => {
b.sessionId = debugSession.sessionId;
b.created = undefined;
return b;
});

this.breakpointStorage.update(breakpoints);
this.onDidChangeBreakpointsEmitter.fire(undefined);
}
Expand All @@ -147,7 +152,11 @@ export class BreakpointsManager implements FrontendApplicationContribution {

private onTerminated(debugSession: DebugSession, event: DebugProtocol.TerminatedEvent): void {
this.lineDecorator.applyDecorations();
this.unassignBreakpointsFromSession(debugSession);
this.breakpointDecorator.applyDecorations();
}

private unassignBreakpointsFromSession(debugSession: DebugSession) {
const breakpoints = this.breakpointStorage.get(DebugUtils.isSourceBreakpoint)
.filter(b => b.sessionId === debugSession.sessionId)
.map(b => {
Expand All @@ -157,7 +166,6 @@ export class BreakpointsManager implements FrontendApplicationContribution {
});

this.breakpointStorage.update(breakpoints);
this.breakpointDecorator.applyDecorations();
this.onDidChangeBreakpointsEmitter.fire(undefined);
}

Expand Down Expand Up @@ -192,9 +200,9 @@ export class BreakpointsManager implements FrontendApplicationContribution {
case 'changed': {
if (sourceBreakpoint) {
sourceBreakpoint.created = breakpoint;
return this.breakpointStorage.update(sourceBreakpoint);
this.breakpointStorage.update(sourceBreakpoint);
} else {
return this.breakpointStorage.update({
this.breakpointStorage.update({
sessionId: debugSession.sessionId,
source: breakpoint.source,
created: breakpoint,
Expand All @@ -204,11 +212,13 @@ export class BreakpointsManager implements FrontendApplicationContribution {
}
});
}
break;
}
case 'removed': {
if (sourceBreakpoint) {
return this.breakpointStorage.delete(sourceBreakpoint);
this.breakpointStorage.delete(sourceBreakpoint);
}
break;
}
}

Expand Down
5 changes: 4 additions & 1 deletion packages/debug/src/browser/debug-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,10 @@ export class DebugCommandHandlers implements MenuContribution, CommandContributi
debugSession.disconnect();
}
},
isEnabled: () => this.debugSessionManager.getActiveDebugSession() !== undefined,
isEnabled: () => {
const debugSession = this.debugSessionManager.getActiveDebugSession();
return !!debugSession && debugSession.state.isConnected;
},
isVisible: () => true
});

Expand Down
30 changes: 30 additions & 0 deletions packages/debug/src/browser/debug-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,35 @@ import { DebugConfiguration, DebugSessionState } from '../common/debug-common';
import { Disposable } from '@theia/core';
import { DebugProtocol } from 'vscode-debugprotocol';

/**
* Stack frame format.
*/
export const DEFAULT_STACK_FRAME_FORMAT: DebugProtocol.StackFrameFormat = {
parameters: true,
parameterTypes: true,
parameterNames: true,
parameterValues: true,
line: true,
module: true,
includeAll: true,
hex: false
};

/**
* Initialize requests arguments.
*/
export const INITIALIZE_ARGUMENTS = {
clientID: 'Theia',
clientName: 'Theia IDE',
locale: 'en-US',
linesStartAt1: true,
columnsStartAt1: true,
pathFormat: 'path',
supportsVariableType: false,
supportsVariablePaging: false,
supportsRunInTerminalRequest: false
};

/**
* DebugSession symbol for DI.
*/
Expand Down Expand Up @@ -51,6 +80,7 @@ export interface DebugSession extends Disposable, NodeJS.EventEmitter {
next(args: DebugProtocol.NextArguments): Promise<DebugProtocol.NextResponse>;
stepIn(args: DebugProtocol.StepInArguments): Promise<DebugProtocol.StepInResponse>;
stepOut(args: DebugProtocol.StepOutArguments): Promise<DebugProtocol.StepOutResponse>;
loadedSources(args: DebugProtocol.LoadedSourcesArguments): Promise<DebugProtocol.LoadedSourcesResponse>;
}

/**
Expand Down
137 changes: 86 additions & 51 deletions packages/debug/src/browser/debug-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,34 @@ import {
DebugAdapterPath,
DebugConfiguration,
DebugSessionState,
DebugSessionStateAccumulator
DebugSessionStateAccumulator,
DebugService
} from '../common/debug-common';
import { DebugProtocol } from 'vscode-debugprotocol';
import { Deferred } from '@theia/core/lib/common/promise-util';
import { Emitter, Event, DisposableCollection, ContributionProvider, Resource, ResourceResolver, Disposable } from '@theia/core';
import {
Emitter,
Event,
DisposableCollection,
ContributionProvider,
Resource,
ResourceResolver,
Disposable
} from '@theia/core';
import { EventEmitter } from 'events';
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
import { DebugSession, DebugSessionFactory, DebugSessionContribution } from './debug-model';
import {
DebugSession,
DebugSessionFactory,
DebugSessionContribution,
DEFAULT_STACK_FRAME_FORMAT,
INITIALIZE_ARGUMENTS
} from './debug-model';
import URI from '@theia/core/lib/common/uri';
import { BreakpointsApplier } from './breakpoint/breakpoint-applier';
import { WebSocketChannel } from '@theia/core/lib/common/messaging/web-socket-channel';

/**
* Stack frame format.
*/
const DEFAULT_STACK_FRAME_FORMAT: DebugProtocol.StackFrameFormat = {
parameters: true,
parameterTypes: true,
parameterNames: true,
parameterValues: true,
line: true,
module: true,
includeAll: true,
hex: false
};

/**
* Initialize requests arguments.
*/
const INITIALIZE_ARGUMENTS = {
clientID: 'Theia',
locale: '',
linesStartAt1: true,
columnsStartAt1: true,
pathFormat: 'path',
supportsVariableType: false,
supportsVariablePaging: false,
supportsRunInTerminalRequest: false
};
import { NotificationsMessageClient } from '@theia/messages/lib/browser/notifications-message-client';
import { MessageType } from '@theia/core/lib/common/message-service-protocol';

/**
* DebugSession implementation.
Expand Down Expand Up @@ -182,6 +171,10 @@ export class DebugSessionImpl extends EventEmitter implements DebugSession {
return this.proceedRequest('stepOut', args);
}

loadedSources(args: DebugProtocol.LoadedSourcesRequest): Promise<DebugProtocol.LoadedSourcesResponse> {
return this.proceedRequest('loadedSources', args);
}

protected handleMessage(data: string) {
const message: DebugProtocol.ProtocolMessage = JSON.parse(data);
if (message.type === 'response') {
Expand Down Expand Up @@ -278,7 +271,9 @@ export class DebugSessionManager {
@inject(DebugSessionFactory) protected readonly debugSessionFactory: DebugSessionFactory,
@inject(OutputChannelManager) protected readonly outputChannelManager: OutputChannelManager,
@inject(ContributionProvider) @named(DebugSessionContribution) protected readonly contributions: ContributionProvider<DebugSessionContribution>,
@inject(BreakpointsApplier) protected readonly breakpointApplier: BreakpointsApplier) {
@inject(BreakpointsApplier) protected readonly breakpointApplier: BreakpointsApplier,
@inject(DebugService) protected readonly debugService: DebugService,
@inject(NotificationsMessageClient) protected readonly notification: NotificationsMessageClient) {

for (const contrib of this.contributions.getContributions()) {
this.contribs.set(contrib.debugType, contrib);
Expand All @@ -291,7 +286,7 @@ export class DebugSessionManager {
* @param configuration The debug configuration
* @returns The debug session
*/
create(sessionId: string, debugConfiguration: DebugConfiguration): Promise<DebugSession> {
async create(sessionId: string, debugConfiguration: DebugConfiguration): Promise<DebugSession> {
this.onDidPreCreateDebugSessionEmitter.fire(sessionId);

const contrib = this.contribs.get(debugConfiguration.type);
Expand All @@ -313,24 +308,62 @@ export class DebugSessionManager {
adapterID: debugConfiguration.type
};

return session.initialize(initializeArgs)
.then(() => {
const request = debugConfiguration.request;
switch (request) {
case 'attach': {
const attachArgs: DebugProtocol.AttachRequestArguments = Object.assign(debugConfiguration, { __restart: false });
return session.attach(attachArgs);
}
case 'launch': {
const launchArgs: DebugProtocol.LaunchRequestArguments = Object.assign(debugConfiguration, { __restart: false, noDebug: false });
return session.launch(launchArgs);
}
default: return Promise.reject(`Unsupported request '${request}' type.`);
}
})
.then(() => this.breakpointApplier.applySessionBreakpoints(session))
.then(() => session.configurationDone())
.then(() => session);
session.once('initialized', () => this.onSessionInitialized(session));
const request = session.configuration.request;

switch (request) {
case 'attach': {
this.attach(session, initializeArgs);
break;
}
case 'launch': {
this.launch(session, initializeArgs);
break;
}
default: throw new Error(`Unsupported request '${request}' type.`);
}

return session;
}

private async attach(session: DebugSession, initializeArgs: DebugProtocol.InitializeRequestArguments): Promise<void> {
await session.initialize(initializeArgs);

const attachArgs: DebugProtocol.AttachRequestArguments = Object.assign(session.configuration, { __restart: false });
try {
await session.attach(attachArgs);
} catch (cause) {
this.onSessionInitializationFailed(session, cause as DebugProtocol.Response);
throw cause;
}
}

private async launch(session: DebugSession, initializeArgs: DebugProtocol.InitializeRequestArguments): Promise<void> {
await session.initialize(initializeArgs);

const launchArgs: DebugProtocol.LaunchRequestArguments = Object.assign(session.configuration, { __restart: false, noDebug: false });
try {
await session.launch(launchArgs);
} catch (cause) {
this.onSessionInitializationFailed(session, cause as DebugProtocol.Response);
throw cause;
}
}

private async onSessionInitialized(session: DebugSession): Promise<void> {
await this.breakpointApplier.applySessionBreakpoints(session);
await session.configurationDone();
}

private async onSessionInitializationFailed(session: DebugSession, cause: DebugProtocol.Response): Promise<void> {
this.destroy(session.sessionId);
await this.notification.showMessage({
type: MessageType.Error,
text: cause.message || 'Debug session initialization failed. See console for details.',
options: {
timeout: 10000
}
});
}

/**
Expand Down Expand Up @@ -410,6 +443,8 @@ export class DebugSessionManager {
}

private doDestroy(session: DebugSession): void {
this.debugService.stop(session.sessionId);

session.dispose();
this.remove(session.sessionId);
this.onDidDestroyDebugSessionEmitter.fire(session);
Expand Down
4 changes: 2 additions & 2 deletions packages/debug/src/browser/view/debug-threads-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ export class DebugThreadsWidget extends VirtualWidget {
const terminatedEventListener = (event: DebugProtocol.TerminatedEvent) => this.onTerminatedEvent(event);

this.debugSession.on('thread', threadEventListener);
this.debugSession.on('connected', connectedEventListener);
this.debugSession.on('configurationDone', connectedEventListener);
this.debugSession.on('terminated', terminatedEventListener);

this.toDispose.push(Disposable.create(() => this.debugSession.removeListener('thread', threadEventListener)));
this.toDispose.push(Disposable.create(() => this.debugSession.removeListener('connected', connectedEventListener)));
this.toDispose.push(Disposable.create(() => this.debugSession.removeListener('configurationDone', connectedEventListener)));
this.toDispose.push(Disposable.create(() => this.debugSession.removeListener('terminated', terminatedEventListener)));

if (this.debugSession.state.isConnected) {
Expand Down
18 changes: 18 additions & 0 deletions packages/debug/src/node/debug-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,24 @@ export class DebugAdapterSessionImpl extends EventEmitter implements DebugAdapte
break;
}

case 'loadedSources': {
const loadedSourcesResponse = response as DebugProtocol.LoadedSourcesResponse;

for (const source of loadedSourcesResponse.body.sources) {
const event: DebugProtocol.LoadedSourceEvent = {
type: 'event',
seq: -1,
event: 'loadedSource',
body: {
source,
reason: 'new'
}
};
this.proceedEvent(JSON.stringify(event), event);
}
break;
}

case 'initialized': {
const initializeResponse = response as DebugProtocol.InitializeResponse;
const event: DebugProtocol.CapabilitiesEvent = {
Expand Down

0 comments on commit 89d1024

Please sign in to comment.