From 57f106fb47347516953198540526d04c065253a3 Mon Sep 17 00:00:00 2001
From: Patrick Meinecke <seeminglyscience@gmail.com>
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 <seeminglyscience@gmail.com>
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<IPsesBreakpointChangedEventArgs>("powerShell/breakpointsChanged");
+export const SetBreakpointRequestType = new RequestType<IPsesBreakpoint, string, void>("powerShell/setBreakpoint");
+export const BreakpointRemovedNotificationType = new NotificationType<IPsesBreakpoint>("powerShell/breakpointRemoved");
+export const BreakpointEnabledChanged = new NotificationType<IPsesBreakpoint>("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<string, vscode.Breakpoint> = new Map<string, vscode.Breakpoint>(
+            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<string, vscode.Breakpoint>): 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 <seeminglyscience@gmail.com>
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 <seeminglyscience@gmail.com>
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<IPsesBreakpointChangedEventArgs>("powerShell/breakpointsChanged");
 export const SetBreakpointRequestType = new RequestType<IPsesBreakpoint, string, void>("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<void> {
+        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<string, vscode.Breakpoint> = new Map<string, vscode.Breakpoint>(
             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<string, vscode.Breakpoint>): vscode.Breakpoint {
-        const existing: vscode.Breakpoint = map?.get(breakpoint.id);
-        if (existing) {
+    private toVSCodeBreakpoint(breakpoint: IPsesBreakpoint, map?: Map<string, vscode.Breakpoint>): 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<IPower
         new PickPSHostProcessFeature(logger),
         new HelpCompletionFeature(),
         new CustomViewsFeature(),
-        new BreakpointManager(),
+        new BreakpointManager(logger),
         new PickRunspaceFeature(logger),
         externalApi
     ];