Skip to content

Commit

Permalink
Plugin API for debug
Browse files Browse the repository at this point in the history
Signed-off-by: Anatoliy Bazko <abazko@redhat.com>
  • Loading branch information
tolusha committed Dec 11, 2018
1 parent eb586cd commit d289124
Show file tree
Hide file tree
Showing 43 changed files with 2,121 additions and 374 deletions.
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [

{
"type": "node",
"request": "attach",
Expand Down
21 changes: 21 additions & 0 deletions packages/core/src/common/objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,27 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

export function deepClone<T>(obj: T): T {
if (!obj || typeof obj !== 'object') {
return obj;
}
if (obj instanceof RegExp) {
return obj;
}
// tslint:disable-next-line:no-any
const result: any = Array.isArray(obj) ? [] : {};
Object.keys(obj).forEach((key: string) => {
// tslint:disable-next-line:no-any
const prop = (<any>obj)[key];
if (prop && typeof prop === 'object') {
result[key] = deepClone(prop);
} else {
result[key] = prop;
}
});
return result;
}

export function deepFreeze<T>(obj: T): T {
if (!obj || typeof obj !== 'object') {
return obj;
Expand Down
57 changes: 41 additions & 16 deletions packages/core/src/common/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { injectable, inject, named } from 'inversify';
import { TextDocumentContentChangeEvent } from 'vscode-languageserver-types';
import URI from '../common/uri';
import { ContributionProvider } from './contribution-provider';
import { Event } from './event';
import { Event, Emitter } from './event';
import { Disposable } from './disposable';
import { MaybePromise } from './types';
import { CancellationToken } from './cancellation';
Expand Down Expand Up @@ -117,26 +117,52 @@ export class DefaultResourceProvider {

}

export class MutableResource implements Resource {
private contents: string;

constructor(readonly uri: URI, contents: string, readonly dispose: () => void) {
this.contents = contents;
}

async readContents(): Promise<string> {
return this.contents;
}

async saveContents(contents: string): Promise<void> {
this.contents = contents;
this.fireDidChangeContents();
}

protected readonly onDidChangeContentsEmitter = new Emitter<void>();
onDidChangeContents = this.onDidChangeContentsEmitter.event;
protected fireDidChangeContents(): void {
this.onDidChangeContentsEmitter.fire(undefined);
}
}

@injectable()
export class InMemoryResources implements ResourceResolver {

private resources = new Map<string, Resource>();
private resources = new Map<string, MutableResource>();

add(uri: URI, contents: string): Resource {
const stringUri = uri.toString();
if (this.resources.has(stringUri)) {
throw new Error(`Cannot add already existing in-memory resource '${stringUri}'`);
const resourceUri = uri.toString();
if (this.resources.has(resourceUri)) {
throw new Error(`Cannot add already existing in-memory resource '${resourceUri}'`);
}
const resource: Resource = {
uri,
async readContents(): Promise<string> {
return contents;
},
dispose: () => {
this.resources.delete(stringUri);
}
};
this.resources.set(stringUri, resource);

const resource = new MutableResource(uri, contents, () => this.resources.delete(resourceUri));
this.resources.set(resourceUri, resource);
return resource;
}

update(uri: URI, contents: string): Resource {
const resourceUri = uri.toString();
const resource = this.resources.get(resourceUri);
if (!resource) {
throw new Error(`Cannot update non-existed in-memory resource '${resourceUri}'`);
}
resource.saveContents(contents);
return resource;
}

Expand All @@ -146,5 +172,4 @@ export class InMemoryResources implements ResourceResolver {
}
return this.resources.get(uri.toString())!;
}

}
1 change: 1 addition & 0 deletions packages/debug-nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "Theia - NodeJS Debug Extension",
"dependencies": {
"@theia/debug": "^0.3.17",
"@theia/core": "^0.3.17",
"ps-list": "5.0.1",
"vscode-debugprotocol": "^1.32.0"
},
Expand Down
63 changes: 52 additions & 11 deletions packages/debug-nodejs/src/node/node-debug-adapter-contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,41 @@ import { injectable } from 'inversify';
// tslint:disable-next-line:no-implicit-dependencies
import { FileUri } from '@theia/core/lib/node';
import { DebugConfiguration } from '@theia/debug/lib/common/debug-configuration';
import { AbstractVSCodeDebugAdapterContribution } from '@theia/debug/lib/node/vscode/vscode-debug-adapter-contribution';
import { VSCodeDebugAdapterContribution } from '@theia/debug/lib/node/vscode/vscode-debug-adapter-contribution';
import { DebugAdapterContribution, DebugAdapterExecutable } from '@theia/debug/lib/node/debug-model';
import { IJSONSchema, IJSONSchemaSnippet } from '@theia/core/lib/common/json-schema';

export const INSPECTOR_PORT_DEFAULT = 9229;
export const LEGACY_PORT_DEFAULT = 5858;

@injectable()
export class NodeDebugAdapterContribution extends AbstractVSCodeDebugAdapterContribution {
export class NodeDebugAdapterContribution implements DebugAdapterContribution {
readonly type: string;
private readonly delegated: VSCodeDebugAdapterContribution;

constructor() {
super(
'node',
path.join(__dirname, '../../download/node-debug/extension')
);
this.type = 'node';
this.delegated = new VSCodeDebugAdapterContribution(this.type, path.join(__dirname, '../../download/node-debug/extension'));
}

get label(): Promise<string | undefined> {
return this.delegated.label;
}

get languages(): Promise<string[] | undefined> {
return this.delegated.languages;
}

async getSchemaAttributes(): Promise<IJSONSchema[]> {
return this.delegated.getSchemaAttributes();
}

async getConfigurationSnippets?(): Promise<IJSONSchemaSnippet[]> {
return this.delegated.getConfigurationSnippets();
}

async provideDebugAdapterExecutable(): Promise<DebugAdapterExecutable | undefined> {
return this.delegated.provideDebugAdapterExecutable();
}

// TODO: construct based on package.json of the given workspace
Expand Down Expand Up @@ -93,13 +115,32 @@ export class NodeDebugAdapterContribution extends AbstractVSCodeDebugAdapterCont
}

@injectable()
export class Node2DebugAdapterContribution extends AbstractVSCodeDebugAdapterContribution {
export class Node2DebugAdapterContribution implements DebugAdapterContribution {
readonly type: string;
private readonly delegated: VSCodeDebugAdapterContribution;

constructor() {
super(
'node2',
path.join(__dirname, '../../download/node-debug2/extension')
);
this.type = 'node2';
this.delegated = new VSCodeDebugAdapterContribution(this.type, path.join(__dirname, '../../download/node-debug2/extension'));
}

get label(): Promise<string | undefined> {
return this.delegated.label;
}

get languages(): Promise<string[] | undefined> {
return this.delegated.languages;
}

async getSchemaAttributes(): Promise<IJSONSchema[]> {
return this.delegated.getSchemaAttributes();
}

async getConfigurationSnippets?(): Promise<IJSONSchemaSnippet[]> {
return this.delegated.getConfigurationSnippets();
}

async provideDebugAdapterExecutable(): Promise<DebugAdapterExecutable | undefined> {
return this.delegated.provideDebugAdapterExecutable();
}
}
8 changes: 7 additions & 1 deletion packages/debug/src/browser/breakpoint/breakpoint-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
return marker && marker.data;
}

getBreakpoints(uri: URI): SourceBreakpoint[] {
getBreakpoints(uri?: URI): SourceBreakpoint[] {
return this.findMarkers({ uri }).map(marker => marker.data);
}

Expand All @@ -64,6 +64,12 @@ export class BreakpointManager extends MarkerManager<SourceBreakpoint> {
}
}

deleteBreakpoint(uri: URI, line: number, column?: number): void {
const breakpoints = this.getBreakpoints(uri);
const newBreakpoints = breakpoints.filter(({ raw }) => raw.line !== line);
this.setBreakpoints(uri, newBreakpoints);
}

enableAllBreakpoints(enabled: boolean): void {
for (const uriString of this.getUris()) {
let didChange = false;
Expand Down
25 changes: 25 additions & 0 deletions packages/debug/src/browser/console/debug-console-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export class DebugConsoleSession extends ConsoleSession {
readonly id = 'debug';
protected items: ConsoleItem[] = [];

// content buffer for [append](#append) method
protected uncompletedItemContent: string | undefined;

@inject(DebugSessionManager)
protected readonly manager: DebugSessionManager;

Expand Down Expand Up @@ -125,6 +128,28 @@ export class DebugConsoleSession extends ConsoleSession {
this.fireDidChange();
}

append(value: string): void {
if (!value) {
return;
}

const lastItem = this.items.slice(-1)[0];
if (lastItem instanceof AnsiConsoleItem && lastItem.content === this.uncompletedItemContent) {
this.items.pop();
this.uncompletedItemContent += value;
} else {
this.uncompletedItemContent = value;
}

this.items.push(new AnsiConsoleItem(this.uncompletedItemContent, MessageType.Info));
this.fireDidChange();
}

appendLine(value: string): void {
this.items.push(new AnsiConsoleItem(value, MessageType.Info));
this.fireDidChange();
}

protected async logOutput(session: DebugSession, event: DebugProtocol.OutputEvent): Promise<void> {
const body = event.body;
const { category, variablesReference } = body;
Expand Down
12 changes: 6 additions & 6 deletions packages/debug/src/browser/debug-configuration-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ import { EditorManager, EditorWidget } from '@theia/editor/lib/browser';
import { MonacoEditor } from '@theia/monaco/lib/browser/monaco-editor';
import { QuickPickService, StorageService } from '@theia/core/lib/browser';
import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service';
import { DebugService } from '../common/debug-service';
import { DebugConfiguration } from '../common/debug-configuration';
import { DebugConfigurationModel } from './debug-configuration-model';
import { DebugSessionOptions } from './debug-session-options';
import { DebugContributionManager } from './debug-contribution-manager';

@injectable()
export class DebugConfigurationManager {
Expand All @@ -42,8 +42,8 @@ export class DebugConfigurationManager {
protected readonly workspaceService: WorkspaceService;
@inject(EditorManager)
protected readonly editorManager: EditorManager;
@inject(DebugService)
protected readonly debug: DebugService;
@inject(DebugContributionManager)
protected readonly debugManager: DebugContributionManager;
@inject(QuickPickService)
protected readonly quickPick: QuickPickService;

Expand Down Expand Up @@ -104,7 +104,7 @@ export class DebugConfigurationManager {
return this.getSupported();
}
protected async getSupported(): Promise<IterableIterator<DebugSessionOptions>> {
const [, debugTypes] = await Promise.all([await this.initialized, this.debug.debugTypes()]);
const [, debugTypes] = await Promise.all([await this.initialized, this.debugManager.debugTypes()]);
return this.doGetSupported(new Set(debugTypes));
}
protected *doGetSupported(debugTypes: Set<string>): IterableIterator<DebugSessionOptions> {
Expand Down Expand Up @@ -231,7 +231,7 @@ export class DebugConfigurationManager {
}
protected async doCreate(model: DebugConfigurationModel): Promise<void> {
const debugType = await this.selectDebugType();
const configurations = debugType ? await this.debug.provideDebugConfigurations(debugType, model.workspaceFolderUri) : [];
const configurations = debugType ? await this.debugManager.provideDebugConfigurations(debugType, model.workspaceFolderUri) : [];
const content = this.getInitialConfigurationContent(configurations);
await model.save(content);
}
Expand All @@ -252,7 +252,7 @@ export class DebugConfigurationManager {
return undefined;
}
const { languageId } = widget.editor.document;
const debuggers = await this.debug.getDebuggersForLanguage(languageId);
const debuggers = await this.debugManager.getDebuggersForLanguage(languageId);
return this.quickPick.show(debuggers.map(
({ label, type }) => ({ label, value: type }),
{ placeholder: 'Select Environment' })
Expand Down
Loading

0 comments on commit d289124

Please sign in to comment.