From 57f106fb47347516953198540526d04c065253a3 Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Fri, 8 Jul 2022 15:00:37 -0400 Subject: [PATCH 1/4] Add a debug config with JmC enabled --- extension-dev.code-workspace | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/extension-dev.code-workspace b/extension-dev.code-workspace index 722bac94df..209e56ae4e 100644 --- a/extension-dev.code-workspace +++ b/extension-dev.code-workspace @@ -190,6 +190,20 @@ "type": "coreclr", "request": "attach", "processId": "${command:pickProcess}", + "justMyCode": true, + "suppressJITOptimizations": true, + "symbolOptions": { + "searchPaths": [], + "searchMicrosoftSymbolServer": true, + "searchNuGetOrgSymbolServer": true + } + }, + { + // https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": "Attach to Editor Services (!jmc)", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}", "justMyCode": false, "suppressJITOptimizations": true, "symbolOptions": { From b535814b50f5d8492d1fdd9d306e066b8b2118ea Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Fri, 8 Jul 2022 15:02:43 -0400 Subject: [PATCH 2/4] Add bp manager for syncing outside of DAP --- src/features/BreakpointManager.ts | 167 ++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 src/features/BreakpointManager.ts diff --git a/src/features/BreakpointManager.ts b/src/features/BreakpointManager.ts new file mode 100644 index 0000000000..11a01c3120 --- /dev/null +++ b/src/features/BreakpointManager.ts @@ -0,0 +1,167 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import vscode = require("vscode"); +import { LanguageClient } from "vscode-languageclient/node"; +import { Range, NotificationType, RequestType } from "vscode-languageserver-protocol"; +import { LanguageClientConsumer } from "../languageClientConsumer"; + +export const BreakpointChangedNotificationType = new NotificationType("powerShell/breakpointsChanged"); +export const SetBreakpointRequestType = new RequestType("powerShell/setBreakpoint"); +export const BreakpointRemovedNotificationType = new NotificationType("powerShell/breakpointRemoved"); +export const BreakpointEnabledChanged = new NotificationType("powerShell/breakpointEnabledChanged"); + +interface IPsesBreakpoint { + id: string, + enabled: boolean, + condition?: string, + hitCondition?: string, + logMessage?: string, + location?: IPsesLocation, + functionName?: string, +} + +interface IPsesLocation { + uri: string, + range: Range, +} + +interface IPsesBreakpointChangedEventArgs { + added: IPsesBreakpoint[], + removed: IPsesBreakpoint[], + changed: IPsesBreakpoint[], +} + +export class BreakpointManager extends LanguageClientConsumer{ + private eventRegistration: vscode.Disposable; + + private notificationRegistration: vscode.Disposable; + + private requestRegistration: vscode.Disposable; + + public setLanguageClient(languageClient: LanguageClient): void { + this.languageClient = languageClient; + if (this.languageClient === undefined) { + return; + } + + this.requestRegistration = this.languageClient.onRequest( + SetBreakpointRequestType, + bp => { + const clientBp: vscode.Breakpoint = this.toVSCodeBreakpoint(bp); + vscode.debug.addBreakpoints([clientBp]); + return clientBp.id; + }); + + this.notificationRegistration = this.languageClient.onNotification( + BreakpointChangedNotificationType.method, + (eventArgs) => this.handleServerBreakpointChanged(this.toVSCodeBreakpointsChanged(eventArgs))); + + this.eventRegistration = vscode.debug.onDidChangeBreakpoints( + (eventArgs) => this.handleClientBreakpointChanged(eventArgs), + this) + } + + private handleServerBreakpointChanged(eventArgs: vscode.BreakpointsChangeEvent): void { + vscode.debug.removeBreakpoints(eventArgs.removed); + } + + private handleClientBreakpointChanged(eventArgs: vscode.BreakpointsChangeEvent): void { + this.languageClient.sendNotification( + BreakpointChangedNotificationType, + this.toPsesBreakpointsChanged(eventArgs)); + } + + private toVSCodeBreakpointsChanged(eventArgs: IPsesBreakpointChangedEventArgs): vscode.BreakpointsChangeEvent { + const map: Map = new Map( + vscode.debug.breakpoints.map(bp => [bp.id, bp])); + + return { + added: eventArgs.added.map((bp) => this.toVSCodeBreakpoint(bp, map)), + removed: eventArgs.removed.map((bp) => this.toVSCodeBreakpoint(bp, map)), + changed: eventArgs.changed.map((bp) => this.toVSCodeBreakpoint(bp, map)), + }; + } + + private toPsesBreakpointsChanged(eventArgs: vscode.BreakpointsChangeEvent): IPsesBreakpointChangedEventArgs { + return { + added: eventArgs.added.map((bp) => this.toPsesBreakpoint(bp)), + removed: eventArgs.removed.map((bp) => this.toPsesBreakpoint(bp)), + changed: eventArgs.changed.map((bp) => this.toPsesBreakpoint(bp)), + }; + } + + private toVSCodeBreakpoint(breakpoint: IPsesBreakpoint, map?: Map): vscode.Breakpoint { + const existing: vscode.Breakpoint = map?.get(breakpoint.id); + if (existing) { + return existing; + } + + if (breakpoint.location !== null && breakpoint.location !== undefined) { + const bp = new vscode.SourceBreakpoint( + new vscode.Location( + vscode.Uri.parse(breakpoint.location.uri), + new vscode.Range( + breakpoint.location.range.start.line, + breakpoint.location.range.start.character, + breakpoint.location.range.end.line, + breakpoint.location.range.end.character)), + breakpoint.enabled, + breakpoint.condition, + breakpoint.hitCondition, + breakpoint.logMessage); + + return bp; + } + + if (breakpoint.functionName !== null && breakpoint.functionName !== undefined) { + const fbp = new vscode.FunctionBreakpoint( + breakpoint.functionName, + breakpoint.enabled, + breakpoint.condition, + breakpoint.hitCondition, + breakpoint.logMessage); + + return fbp; + } + + return undefined; + } + + private toPsesBreakpoint(breakpoint: vscode.Breakpoint): IPsesBreakpoint { + const location = (breakpoint as vscode.SourceBreakpoint).location; + let psesLocation: IPsesLocation; + if (location !== null && location !== undefined) { + psesLocation = { + uri: location.uri.toString(), + range: { + start: { + character: location.range.start.character, + line: location.range.start.line, + }, + end: { + character: location.range.end.character, + line: location.range.end.line, + }, + }, + }; + } + + const functionName = (breakpoint as vscode.FunctionBreakpoint).functionName; + return { + id: breakpoint.id, + enabled: breakpoint.enabled, + condition: breakpoint.condition, + hitCondition: breakpoint.hitCondition, + logMessage: breakpoint.logMessage, + location: psesLocation, + functionName, + }; + } + + dispose(): void { + this.eventRegistration?.dispose(); + this.notificationRegistration?.dispose(); + this.requestRegistration?.dispose(); + } +} From 0a11920ae98e2e7280fd96efed966064c0ae0faa Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Fri, 8 Jul 2022 15:03:02 -0400 Subject: [PATCH 3/4] hook up bp manager --- src/main.ts | 2 ++ src/session.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/main.ts b/src/main.ts index 65ec2ddc7a..5218c6bf4c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -33,6 +33,7 @@ import { SessionManager } from "./session"; import Settings = require("./settings"); import { PowerShellLanguageId } from "./utils"; import { LanguageClientConsumer } from "./languageClientConsumer"; +import { BreakpointManager } from "./features/BreakpointManager"; // The most reliable way to get the name and version of the current extension. // tslint:disable-next-line: no-var-requires @@ -163,6 +164,7 @@ export function activate(context: vscode.ExtensionContext): IPowerShellExtension new HelpCompletionFeature(logger), new CustomViewsFeature(), new PickRunspaceFeature(), + new BreakpointManager(), externalApi ]; diff --git a/src/session.ts b/src/session.ts index 3f70221e74..515fcf6b0e 100644 --- a/src/session.ts +++ b/src/session.ts @@ -541,6 +541,7 @@ export class SessionManager implements Middleware { initializationOptions: { enableProfileLoading: this.sessionSettings.enableProfileLoading, initialWorkingDirectory: this.sessionSettings.cwd, + supportsBreakpointSync: true, }, errorHandler: { // Override the default error handler to prevent it from From 950451212baa4221c629e3b7afb90f3053a96a7e Mon Sep 17 00:00:00 2001 From: Patrick Meinecke Date: Wed, 13 Sep 2023 14:18:00 -0400 Subject: [PATCH 4/4] Fix build errors and add error logging --- src/features/BreakpointManager.ts | 86 ++++++++++++++++++++----------- src/main.ts | 2 +- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/src/features/BreakpointManager.ts b/src/features/BreakpointManager.ts index 11a01c3120..a1a3e395e4 100644 --- a/src/features/BreakpointManager.ts +++ b/src/features/BreakpointManager.ts @@ -5,6 +5,7 @@ import vscode = require("vscode"); import { LanguageClient } from "vscode-languageclient/node"; import { Range, NotificationType, RequestType } from "vscode-languageserver-protocol"; import { LanguageClientConsumer } from "../languageClientConsumer"; +import { Logger } from "../logging"; export const BreakpointChangedNotificationType = new NotificationType("powerShell/breakpointsChanged"); export const SetBreakpointRequestType = new RequestType("powerShell/setBreakpoint"); @@ -33,41 +34,61 @@ interface IPsesBreakpointChangedEventArgs { } export class BreakpointManager extends LanguageClientConsumer{ - private eventRegistration: vscode.Disposable; + private eventRegistration: vscode.Disposable | undefined; - private notificationRegistration: vscode.Disposable; + private notificationRegistration: vscode.Disposable | undefined; - private requestRegistration: vscode.Disposable; + private requestRegistration: vscode.Disposable | undefined; - public setLanguageClient(languageClient: LanguageClient): void { + private logger: Logger; + + constructor(logger: Logger) { + super(); + + this.logger = logger; + } + + public override setLanguageClient(languageClient: LanguageClient): void { this.languageClient = languageClient; - if (this.languageClient === undefined) { - return; - } this.requestRegistration = this.languageClient.onRequest( SetBreakpointRequestType, bp => { - const clientBp: vscode.Breakpoint = this.toVSCodeBreakpoint(bp); + const clientBp: vscode.Breakpoint | undefined = this.toVSCodeBreakpoint(bp); + if (clientBp === undefined) { + return -1; + } + vscode.debug.addBreakpoints([clientBp]); return clientBp.id; }); this.notificationRegistration = this.languageClient.onNotification( BreakpointChangedNotificationType.method, - (eventArgs) => this.handleServerBreakpointChanged(this.toVSCodeBreakpointsChanged(eventArgs))); + (eventArgs) => { + this.handleServerBreakpointChanged(this.toVSCodeBreakpointsChanged(eventArgs)); + }); this.eventRegistration = vscode.debug.onDidChangeBreakpoints( - (eventArgs) => this.handleClientBreakpointChanged(eventArgs), - this) + (eventArgs) => { + this.handleClientBreakpointChanged(eventArgs) + .catch((reason) => { + this.logger.writeError(`Error occurred while handling client breakpoint changed: ${reason}`); + }); + }, + this); } private handleServerBreakpointChanged(eventArgs: vscode.BreakpointsChangeEvent): void { vscode.debug.removeBreakpoints(eventArgs.removed); } - private handleClientBreakpointChanged(eventArgs: vscode.BreakpointsChangeEvent): void { - this.languageClient.sendNotification( + private async handleClientBreakpointChanged(eventArgs: vscode.BreakpointsChangeEvent): Promise { + if (this.languageClient === undefined) { + return; + } + + await this.languageClient.sendNotification( BreakpointChangedNotificationType, this.toPsesBreakpointsChanged(eventArgs)); } @@ -76,10 +97,11 @@ export class BreakpointManager extends LanguageClientConsumer{ const map: Map = new Map( vscode.debug.breakpoints.map(bp => [bp.id, bp])); + const isBreakpoint = (bp: vscode.Breakpoint | undefined): bp is vscode.Breakpoint => bp !== undefined; return { - added: eventArgs.added.map((bp) => this.toVSCodeBreakpoint(bp, map)), - removed: eventArgs.removed.map((bp) => this.toVSCodeBreakpoint(bp, map)), - changed: eventArgs.changed.map((bp) => this.toVSCodeBreakpoint(bp, map)), + added: eventArgs.added.map((bp) => this.toVSCodeBreakpoint(bp, map)).filter(isBreakpoint), + removed: eventArgs.removed.map((bp) => this.toVSCodeBreakpoint(bp, map)).filter(isBreakpoint), + changed: eventArgs.changed.map((bp) => this.toVSCodeBreakpoint(bp, map)).filter(isBreakpoint), }; } @@ -91,13 +113,13 @@ export class BreakpointManager extends LanguageClientConsumer{ }; } - private toVSCodeBreakpoint(breakpoint: IPsesBreakpoint, map?: Map): vscode.Breakpoint { - const existing: vscode.Breakpoint = map?.get(breakpoint.id); - if (existing) { + private toVSCodeBreakpoint(breakpoint: IPsesBreakpoint, map?: Map): vscode.Breakpoint | undefined { + const existing: vscode.Breakpoint | undefined = map?.get(breakpoint.id); + if (existing !== undefined) { return existing; } - if (breakpoint.location !== null && breakpoint.location !== undefined) { + if (breakpoint.location !== undefined) { const bp = new vscode.SourceBreakpoint( new vscode.Location( vscode.Uri.parse(breakpoint.location.uri), @@ -114,7 +136,7 @@ export class BreakpointManager extends LanguageClientConsumer{ return bp; } - if (breakpoint.functionName !== null && breakpoint.functionName !== undefined) { + if (breakpoint.functionName !== undefined) { const fbp = new vscode.FunctionBreakpoint( breakpoint.functionName, breakpoint.enabled, @@ -125,29 +147,33 @@ export class BreakpointManager extends LanguageClientConsumer{ return fbp; } + this.logger.writeError(`Unable to translate PSES breakpoint: ${JSON.stringify(breakpoint)}`); return undefined; } private toPsesBreakpoint(breakpoint: vscode.Breakpoint): IPsesBreakpoint { - const location = (breakpoint as vscode.SourceBreakpoint).location; - let psesLocation: IPsesLocation; - if (location !== null && location !== undefined) { + let psesLocation: IPsesLocation | undefined = undefined; + if (breakpoint instanceof vscode.SourceBreakpoint) { psesLocation = { - uri: location.uri.toString(), + uri: breakpoint.location.uri.toString(), range: { start: { - character: location.range.start.character, - line: location.range.start.line, + character: breakpoint.location.range.start.character, + line: breakpoint.location.range.start.line, }, end: { - character: location.range.end.character, - line: location.range.end.line, + character: breakpoint.location.range.end.character, + line: breakpoint.location.range.end.line, }, }, }; } - const functionName = (breakpoint as vscode.FunctionBreakpoint).functionName; + let functionName: string | undefined = undefined; + if (breakpoint instanceof vscode.FunctionBreakpoint) { + functionName = breakpoint.functionName; + } + return { id: breakpoint.id, enabled: breakpoint.enabled, diff --git a/src/main.ts b/src/main.ts index 640a3ca111..a9a16e6e9e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -160,7 +160,7 @@ export async function activate(context: vscode.ExtensionContext): Promise