Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

10011: Add DebugProtocolSource and DebugProtocolBreakpoint #10926

Merged
merged 2 commits into from
Jul 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [core] `handleDefault`, `handleElectronDefault` method no longer called in `BrowserMainMenuFactory.registerMenu()`, `DynamicMenuWidget.buildSubMenus()` or `ElectronMainMenuFactory.fillSubmenus()`. Override the respective calling function rather than `handleDefault`. The argument to each of the three methods listed above is now `MenuNode` and not `CompositeMenuNode`, and the methods are truly recursive and called on entire menu tree. `ActionMenuNode.action` removed; access relevant field on `ActionMenuNode.command`, `.when` etc. [#11290](https://github.com/eclipse-theia/theia/pull/11290)
- [core] renamed `CommonCommands.NEW_FILE` to `CommonCommands.NEW_UNTITLED_FILE` [#11429](https://github.com/eclipse-theia/theia/pull/11429)
- [plugin-ext] `CodeEditorWidgetUtil` moved to `packages/plugin-ext/src/main/browser/menus/vscode-theia-menu-mappings.ts`. `MenusContributionPointHandler` extensively refactored. See PR description for details. [#11290](https://github.com/eclipse-theia/theia/pull/11290)
- [plugin] added support for `DebugProtocolBreakpoint` and `DebugProtocolSource` [#10011](https://github.com/eclipse-theia/theia/issues/10011) - Contributed on behalf of STMicroelectronics

## v1.27.0 - 6/30/2022

Expand Down
11 changes: 11 additions & 0 deletions packages/debug/src/browser/debug-session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,17 @@ export class DebugSession implements CompositeTreeElement {
return result;
}

getBreakpoint(id: string): DebugBreakpoint | undefined {
for (const breakpoints of this._breakpoints.values()) {
const breakpoint = breakpoints.find(b => b.id === id);
if (breakpoint) {
return breakpoint;
}

}
return undefined;
}

protected clearBreakpoints(): void {
const uris = [...this._breakpoints.keys()];
this._breakpoints.clear();
Expand Down
11 changes: 6 additions & 5 deletions packages/debug/src/browser/model/debug-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import URI from '@theia/core/lib/common/uri';
import { DebugProtocol } from 'vscode-debugprotocol/lib/debugProtocol';
import { DebugSession } from '../debug-session';
import { URI as Uri } from '@theia/core/shared/vscode-uri';
import { DEBUG_SCHEME, SCHEME_PATTERN } from '../../common/debug-uri-utils';

export class DebugSourceData {
readonly raw: DebugProtocol.Source;
Expand Down Expand Up @@ -58,7 +59,7 @@ export class DebugSource extends DebugSourceData {
}

get inMemory(): boolean {
return this.uri.scheme === DebugSource.SCHEME;
return this.uri.scheme === DEBUG_SCHEME;
}

get name(): string {
Expand All @@ -75,16 +76,16 @@ export class DebugSource extends DebugSourceData {
return this.labelProvider.getLongName(this.uri);
}

static SCHEME = 'debug';
static SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/;
static SCHEME = DEBUG_SCHEME;
static SCHEME_PATTERN = SCHEME_PATTERN;
static toUri(raw: DebugProtocol.Source): URI {
if (raw.sourceReference && raw.sourceReference > 0) {
return new URI().withScheme(DebugSource.SCHEME).withPath(raw.name!).withQuery(String(raw.sourceReference));
return new URI().withScheme(DEBUG_SCHEME).withPath(raw.name!).withQuery(String(raw.sourceReference));
}
if (!raw.path) {
throw new Error('Unrecognized source type: ' + JSON.stringify(raw));
}
if (raw.path.match(DebugSource.SCHEME_PATTERN)) {
if (raw.path.match(SCHEME_PATTERN)) {
return new URI(raw.path);
}
return new URI(Uri.file(raw.path));
Expand Down
24 changes: 24 additions & 0 deletions packages/debug/src/common/debug-uri-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/********************************************************************************
* Copyright (C) 2022 STMicroelectronics and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

/**
* The URI scheme for debug URIs.
*/
export const DEBUG_SCHEME = 'debug';
/**
* The pattern for URI schemes.
*/
export const SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/;
1 change: 1 addition & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1750,6 +1750,7 @@ export interface DebugMain {
$startDebugging(folder: theia.WorkspaceFolder | undefined, nameOrConfiguration: string | theia.DebugConfiguration, options: theia.DebugSessionOptions): Promise<boolean>;
$stopDebugging(sessionId?: string): Promise<void>;
$customRequest(sessionId: string, command: string, args?: any): Promise<DebugProtocol.Response>;
$getDebugProtocolBreakpoint(sessionId: string, breakpointId: string): Promise<theia.DebugProtocolBreakpoint | undefined>;
}

export interface FileSystemExt {
Expand Down
9 changes: 9 additions & 0 deletions packages/plugin-ext/src/main/browser/debug/debug-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,15 @@ export class DebugMainImpl implements DebugMain, Disposable {
}
}

async $getDebugProtocolBreakpoint(sessionId: string, breakpointId: string): Promise<DebugProtocol.Breakpoint | undefined> {
const session = this.sessionManager.getSession(sessionId);
if (session) {
return session.getBreakpoint(breakpointId)?.raw;
} else {
throw new Error(`Debug session '${sessionId}' not found`);
}
}

async $removeBreakpoints(breakpoints: string[]): Promise<void> {
const { labelProvider, breakpointsManager, editorManager } = this;
const session = this.sessionManager.currentSession;
Expand Down
35 changes: 30 additions & 5 deletions packages/plugin-ext/src/plugin/debug/debug-ext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ import { PluginPackageDebuggersContribution } from '../../common/plugin-protocol
import { RPCProtocol } from '../../common/rpc-protocol';
import { CommandRegistryImpl } from '../command-registry';
import { ConnectionImpl } from '../../common/connection';
import { Disposable, Breakpoint as BreakpointExt, SourceBreakpoint, FunctionBreakpoint, Location, Range } from '../types-impl';
import { DEBUG_SCHEME, SCHEME_PATTERN } from '@theia/debug/lib/common/debug-uri-utils';
import { Disposable, Breakpoint as BreakpointExt, SourceBreakpoint, FunctionBreakpoint, Location, Range, URI as URIImpl } from '../types-impl';
import { PluginDebugAdapterSession } from './plugin-debug-adapter-session';
import { PluginDebugAdapterTracker } from './plugin-debug-adapter-tracker';
import uuid = require('uuid');
import { DebugAdapter } from '@theia/debug/lib/common/debug-model';
import { PluginDebugAdapterCreator } from './plugin-debug-adapter-creator';
import { NodeDebugAdapterCreator } from '../node/debug/plugin-node-debug-adapter-creator';
import { DebugProtocol } from 'vscode-debugprotocol';

interface ConfigurationProviderRecord {
handle: number;
Expand Down Expand Up @@ -176,6 +178,27 @@ export class DebugExtImpl implements DebugExt {
return this.proxy.$stopDebugging(session?.id);
}

asDebugSourceUri(source: theia.DebugProtocolSource, session?: theia.DebugSession): theia.Uri {
return this.getDebugSourceUri(source, session?.id);
}

private getDebugSourceUri(raw: DebugProtocol.Source, sessionId?: string): theia.Uri {
if (raw.sourceReference && raw.sourceReference > 0) {
let query = 'ref=' + String(raw.sourceReference);
if (sessionId) {
query += `&session=${sessionId}`;
}
return URIImpl.from({ scheme: DEBUG_SCHEME, path: raw.path ?? '', query });
}
if (!raw.path) {
throw new Error('Unrecognized source type: ' + JSON.stringify(raw));
}
if (raw.path.match(SCHEME_PATTERN)) {
return URIImpl.parse(raw.path);
}
return URIImpl.file(raw.path);
}

registerDebugAdapterDescriptorFactory(debugType: string, factory: theia.DebugAdapterDescriptorFactory): Disposable {
if (this.descriptorFactories.has(debugType)) {
throw new Error(`Descriptor factory for ${debugType} has been already registered`);
Expand Down Expand Up @@ -279,13 +302,13 @@ export class DebugExtImpl implements DebugExt {
this.onDidChangeBreakpointsEmitter.fire({ added: a, removed: r, changed: c });
}

protected toBreakpointExt({ functionName, location, enabled, condition, hitCondition, logMessage }: Breakpoint): BreakpointExt | undefined {
protected toBreakpointExt({ functionName, location, enabled, condition, hitCondition, logMessage, id }: Breakpoint): BreakpointExt | undefined {
if (location) {
const range = new Range(location.range.startLineNumber, location.range.startColumn, location.range.endLineNumber, location.range.endColumn);
return new SourceBreakpoint(new Location(URI.revive(location.uri), range), enabled, condition, hitCondition, logMessage);
return new SourceBreakpoint(new Location(URI.revive(location.uri), range), enabled, condition, hitCondition, logMessage, id);
}
if (functionName) {
return new FunctionBreakpoint(functionName!, enabled, condition, hitCondition, logMessage);
return new FunctionBreakpoint(functionName!, enabled, condition, hitCondition, logMessage, id);
}
return undefined;
}
Expand All @@ -305,7 +328,9 @@ export class DebugExtImpl implements DebugExt {
return response.body;
}
return Promise.reject(new Error(response.message ?? 'custom request failed'));
}
},
getDebugProtocolBreakpoint: async (breakpoint: Breakpoint) =>
this.proxy.$getDebugProtocolBreakpoint(sessionId, breakpoint.id)
};

const tracker = await this.createDebugAdapterTracker(theiaSession);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export class PluginDebugAdapterSession extends DebugAdapterSessionImpl {
return this.theiaSession.customRequest(command, args);
}

async getDebugProtocolBreakpoint(breakpoint: theia.Breakpoint): Promise<theia.DebugProtocolBreakpoint | undefined> {
return this.theiaSession.getDebugProtocolBreakpoint(breakpoint);
}

protected override onDebugAdapterError(error: Error): void {
if (this.tracker.onError) {
this.tracker.onError(error);
Expand Down
94 changes: 94 additions & 0 deletions packages/plugin-ext/src/plugin/node/debug/debug.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/********************************************************************************
* Copyright (C) 2022 STMicroelectronics and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import { DebugSession } from '@theia/plugin';
import * as chai from 'chai';
import { ProxyIdentifier, RPCProtocol } from '../../../common/rpc-protocol';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: remove extra newline.

import { DebugExtImpl } from '../../debug/debug-ext';

const expect = chai.expect;

describe('Debug API', () => {

describe('#asDebugSourceURI', () => {

const mockRPCProtocol: RPCProtocol = {
getProxy<T>(_proxyId: ProxyIdentifier<T>): T {
return {} as T;
},
set<T, R extends T>(_id: ProxyIdentifier<T>, instance: R): R {
return instance;
},
dispose(): void {
// Nothing
}
};

const debug = new DebugExtImpl(mockRPCProtocol);

it('should use sourceReference, path and sessionId', () => {
const source = {
sourceReference: 3,
path: 'test/path'
};
const session = { id: 'test-session' } as DebugSession;
const uri = debug.asDebugSourceUri(source, session);
expect(uri.toString(true)).to.be.equal('debug:test/path?ref=3&session=test-session');
});

it('should use sourceReference', () => {
const source = {
sourceReference: 5
};
const uri = debug.asDebugSourceUri(source);
expect(uri.toString(true)).to.be.equal('debug:?ref=5');
});

it('should use sourceReference and session', () => {
const source = {
sourceReference: 5
};
const session = { id: 'test-session' } as DebugSession;
const uri = debug.asDebugSourceUri(source, session);
expect(uri.toString(true)).to.be.equal('debug:?ref=5&session=test-session');
});

it('should use sourceReference and path', () => {
const source = {
sourceReference: 4,
path: 'test/path'
};
const uri = debug.asDebugSourceUri(source);
expect(uri.toString(true)).to.be.equal('debug:test/path?ref=4');
});

it('should use path', () => {
const source = {
path: 'scheme:/full/path'
};
const uri = debug.asDebugSourceUri(source);
expect(uri.toString(true)).to.be.equal('scheme:/full/path');
});

it('should use file path', () => {
const source = {
path: '/full/path'
};
const uri = debug.asDebugSourceUri(source);
expect(uri.toString(true)).to.be.equal('file:///full/path');
});
});
});
3 changes: 3 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,9 @@ export function createAPIFactory(
},
removeBreakpoints(breakpoints: readonly theia.Breakpoint[]): void {
debugExt.removeBreakpoints(breakpoints);
},
asDebugSourceUri(source: theia.DebugProtocolSource, session?: theia.DebugSession): theia.Uri {
return debugExt.asDebugSourceUri(source, session);
}
};

Expand Down
11 changes: 6 additions & 5 deletions packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2250,11 +2250,12 @@ export class Breakpoint {
*/
logMessage?: string;

protected constructor(enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) {
protected constructor(enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, id?: string) {
this.enabled = enabled || false;
this.condition = condition;
this.hitCondition = hitCondition;
this.logMessage = logMessage;
this._id = id;
}

private _id: string | undefined;
Expand Down Expand Up @@ -2283,8 +2284,8 @@ export class SourceBreakpoint extends Breakpoint {
/**
* Create a new breakpoint for a source location.
*/
constructor(location: Location, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) {
super(enabled, condition, hitCondition, logMessage);
constructor(location: Location, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, id?: string) {
super(enabled, condition, hitCondition, logMessage, id);
this.location = location;
}
}
Expand All @@ -2302,8 +2303,8 @@ export class FunctionBreakpoint extends Breakpoint {
/**
* Create a new function breakpoint.
*/
constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) {
super(enabled, condition, hitCondition, logMessage);
constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string, id?: string) {
super(enabled, condition, hitCondition, logMessage, id);
this.functionName = functionName;
}
}
Expand Down
33 changes: 33 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9868,6 +9868,20 @@ export module '@theia/plugin' {
// Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Base_Protocol_ProtocolMessage).
}

/**
* A DebugProtocolBreakpoint is an opaque stand-in type for the [Breakpoint](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Breakpoint) type defined in the Debug Adapter Protocol.
*/
export interface DebugProtocolBreakpoint {
// Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Breakpoint)
}

/**
* A DebugProtocolSource is an opaque stand-in type for the [Source](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source) type defined in the Debug Adapter Protocol.
*/
export interface DebugProtocolSource {
// Properties: see details [here](https://microsoft.github.io/debug-adapter-protocol/specification#Types_Source)
}

/**
* Configuration for a debug session.
*/
Expand Down Expand Up @@ -9927,6 +9941,15 @@ export module '@theia/plugin' {
* Send a custom request to the debug adapter.
*/
customRequest(command: string, args?: any): Thenable<any>;

/**
* Maps a breakpoint in the editor to the corresponding Debug Adapter Protocol (DAP) breakpoint that
* is managed by the debug adapter of the debug session. If no DAP breakpoint exists (either because
* the editor breakpoint was not yet registered or because the debug adapter is not interested in the
* breakpoint), the value undefined is returned.
* @param breakpoint a Breakpoint in the editor.
*/
getDebugProtocolBreakpoint(breakpoint: Breakpoint): PromiseLike<DebugProtocolBreakpoint | undefined>
}

/**
Expand Down Expand Up @@ -10402,6 +10425,16 @@ export module '@theia/plugin' {
*/
export function registerDebugAdapterDescriptorFactory(debugType: string, factory: DebugAdapterDescriptorFactory): Disposable;

/**
* Converts a "Source" descriptor object received via the Debug Adapter Protocol into a Uri that can be used to load its contents.
* If the source descriptor is based on a path, a file Uri is returned. If the source descriptor uses a reference number, a
* specific debug Uri (scheme 'debug') is constructed that requires a corresponding ContentProvider and a running debug session
* If the "Source" descriptor has insufficient information for creating the Uri, an error is thrown.
* @param source An object conforming to the Source type defined in the Debug Adapter Protocol.
* @param session An optional debug session that will be used when the source descriptor uses a reference number to load the contents from an active debug session.
*/
export function asDebugSourceUri(source: DebugProtocolSource, session?: DebugSession): Uri;

/**
* Register a {@link DebugConfigurationProvider debug configuration provider} for a specific debug type.
* The optional {@link DebugConfigurationProviderTriggerKind triggerKind} can be used to specify when the `provideDebugConfigurations` method of the provider is triggered.
Expand Down