Browse Source

[WIP] Work on being able to set breakpoints in each of the JS editors and have it update the debugger tab.

Shaddock Heath 8 years ago
parent
commit
164b3ab228

+ 1 - 0
Data/AtomicEditor/CodeEditor/MonacoEditor.html

@@ -20,6 +20,7 @@
             right: 0;
         }
     </style>
+    <link rel="stylesheet" href="./css/editor.css" type="text/css" />
 </head>
 
 <body>

+ 0 - 16
Data/AtomicEditor/CodeEditor/css/duk_debug.css

@@ -558,22 +558,6 @@ code.sourcecode div.execution {
 	color: #ffffff;
 }
 
-.code-breakpoint {
-	background: red;
-	border-radius: 50%;
-	margin-left: 5px;
-}
-.code-breakpoint-pending {
-	background: goldenrod;
-	border-radius: 50%;
-	margin-left: 5px;
-}
-.code-breakpoint-margin-hover {
-	background: pink;
-	border-radius: 50%;
-	margin-left: 5px;
-}
-
 #source-select {
 	margin-top: 5px;
 }

+ 15 - 0
Data/AtomicEditor/CodeEditor/css/editor.css

@@ -0,0 +1,15 @@
+.code-breakpoint {
+	background: red;
+	border-radius: 50%;
+	margin-left: 5px;
+}
+.code-breakpoint-pending {
+	background: goldenrod;
+	border-radius: 50%;
+	margin-left: 5px;
+}
+.code-breakpoint-margin-hover {
+	background: darkred;
+	border-radius: 50%;
+	margin-left: 5px;
+}

+ 1 - 0
Data/AtomicEditor/CodeEditor/duk_debug.html

@@ -8,6 +8,7 @@
     <link rel="stylesheet" href="./css/reset.css" type="text/css" />
     <link rel="stylesheet" href="./source/thirdparty/jquery-ui.min.css" type="text/css" />
     <link rel="stylesheet" href="./css/duk_debug.css" type="text/css" />
+    <link rel="stylesheet" href="./css/editor.css" type="text/css" />
     <title>Duktape debugger</title>
 </head>
 

+ 2 - 0
Script/AtomicEditor/hostExtensions/ServiceLocator.ts

@@ -25,6 +25,7 @@ import * as EditorUI from "../ui/EditorUI";
 import ProjectBasedExtensionLoader from "./coreExtensions/ProjectBasedExtensionLoader";
 import TypescriptLanguageExtension from "./languageExtensions/TypescriptLanguageExtension";
 import CSharpLanguageExtension from "./languageExtensions/CSharpLanguageExtension";
+import DuktapeDebuggerExtension from "./coreExtensions/DuktapeDebuggerExtension";
 
 /**
  * Generic service locator of editor services that may be injected by either a plugin
@@ -110,3 +111,4 @@ export default serviceLocator;
 serviceLocator.loadService(new ProjectBasedExtensionLoader());
 serviceLocator.loadService(new TypescriptLanguageExtension());
 serviceLocator.loadService(new CSharpLanguageExtension());
+serviceLocator.loadService(new DuktapeDebuggerExtension());

+ 240 - 0
Script/AtomicEditor/hostExtensions/coreExtensions/DuktapeDebuggerExtension.ts

@@ -0,0 +1,240 @@
+//
+// Copyright (c) 2014-2016 THUNDERBEAST GAMES LLC
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+export interface Breakpoint {
+    fileName: string;
+    lineNumber: number;
+}
+
+class BreakpointList {
+    // list of breakpoints
+    private breakpoints: Breakpoint[] = [];
+
+
+    private indexOfBreakpoint(fileName: string, lineNumber: number): number {
+
+        let curr: Breakpoint;
+        for (let i = 0, iEnd = this.breakpoints.length; i < iEnd; i++) {
+            curr = this.breakpoints[i];
+            if (curr.fileName == fileName && curr.lineNumber == lineNumber) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    removeAllBreakpoints() {
+        this.breakpoints.length = 0;
+    }
+
+    removeBreakpoint(fileName: string, lineNumber: number) {
+        const idx = this.indexOfBreakpoint(fileName, lineNumber);
+        if (idx != -1) {
+            this.breakpoints.splice(idx);
+        }
+    }
+
+    addBreakpoint(fileName: string, lineNumber: number) {
+        const idx = this.indexOfBreakpoint(fileName, lineNumber);
+        if (idx == -1) {
+            console.log("Adding breakpoint: " + fileName + ":" + lineNumber);
+            this.breakpoints.push({
+                fileName,
+                lineNumber
+            });
+        }
+    }
+
+    getBreakpoint(fileName: string, lineNumber: number): Breakpoint {
+        const idx = this.indexOfBreakpoint(fileName, lineNumber);
+        return this.breakpoints[idx];
+    }
+
+    toggleBreakpoint(fileName: string, lineNumber: number) {
+        let decoration = this.getBreakpoint(fileName, lineNumber);
+        if (decoration) {
+            this.removeBreakpoint(fileName, lineNumber);
+        } else {
+            this.addBreakpoint(fileName, lineNumber);
+        }
+    }
+
+    getBreakpoints(): Breakpoint[] {
+        return this.breakpoints;
+    }
+}
+
+/**
+ * extension that will communicate with the duktape debugger
+ */
+export default class DuktapeDebuggerExtension extends Atomic.ScriptObject implements Editor.HostExtensions.HostEditorService {
+    name: string = "HostDebuggerExtension";
+    description: string = "This service supports the duktape debugger interface";
+
+    private debuggerWebClient: WebView.WebClient;
+    private clientProxyMappings: {
+        toggleBreakpoint: string,
+        addBreakpoint: string,
+        removeBreakpoint: string
+    };
+
+    private serviceRegistry: Editor.HostExtensions.HostServiceLocator = null;
+
+    private breakpointList = new BreakpointList();
+
+    /**
+     * Inject this language service into the registry
+     * @return {[type]}             True if successful
+     */
+    initialize(serviceLocator: Editor.HostExtensions.HostServiceLocator) {
+        // We care about both resource events as well as project events
+        serviceLocator.resourceServices.register(this);
+        serviceLocator.projectServices.register(this);
+        serviceLocator.uiServices.register(this);
+        this.serviceRegistry = serviceLocator;
+    }
+
+    /**
+     * Handle messages that are submitted via Atomic.Query from within a web view editor.
+     * @param message The message type that was submitted to be used to determine what the data contains if present
+     * @param data any additional data that needs to be submitted with the message
+     */
+    handleWebMessage(webMessage: WebView.WebMessageEvent, messageType: string, data: any) {
+        switch (messageType) {
+            case "Debugger.AddBreakpoint":
+                this.addBreakpoint(data);
+                this.webMessageEventResponse(webMessage);
+                break;
+            case "Debugger.RemoveBreakpoint":
+                this.removeBreakpoint(data);
+                this.webMessageEventResponse(webMessage);
+                break;
+            case "Debugger.ToggleBreakpoint":
+                this.toggleBreakpoint(data);
+                this.webMessageEventResponse(webMessage);
+                break;
+            case "Debugger.RemoveAllBreakpoints":
+                this.breakpointList.removeAllBreakpoints();
+                this.webMessageEventResponse(webMessage);
+                break;
+            case "Debugger.GetBreakpoints":
+                this.webMessageEventResponse(webMessage, this.breakpointList.getBreakpoints());
+                break;
+            case "Debugger.RegisterDebuggerFrame":
+                this.registerDebuggerFrame(webMessage, data);
+                this.webMessageEventResponse(webMessage);
+                break;
+        }
+    }
+
+    webMessageEventResponse<T>(originalMessage: WebView.WebMessageEvent, response?: T) {
+        if (response) {
+            const wrappedResponse: WebView.WebMessageEventResponse<T> = {
+                response
+            };
+
+            originalMessage.handler.success(JSON.stringify(wrappedResponse));
+        } else {
+            originalMessage.handler.success();
+        }
+    }
+
+    registerDebuggerFrame(originalMessage: WebView.WebMessageEvent, mappings) {
+        this.debuggerWebClient = originalMessage.handler.webClient;
+        this.clientProxyMappings = mappings;
+    }
+
+    addBreakpoint(bp: Breakpoint) {
+        this.breakpointList.addBreakpoint(bp.fileName, bp.lineNumber);
+        if (this.debuggerWebClient) {
+            this.proxyWebClientMethod(
+                this.debuggerWebClient,
+                this.clientProxyMappings.addBreakpoint,
+                bp.fileName,
+                bp.lineNumber,
+                false);
+        }
+    }
+
+    removeBreakpoint(bp: Breakpoint) {
+        this.breakpointList.removeBreakpoint(bp.fileName, bp.lineNumber);
+        if (this.debuggerWebClient) {
+            this.proxyWebClientMethod(
+                this.debuggerWebClient,
+                this.clientProxyMappings.removeBreakpoint,
+                bp.fileName,
+                bp.lineNumber,
+                false);
+        }
+    }
+
+    toggleBreakpoint(bp: Breakpoint) {
+        this.breakpointList.toggleBreakpoint(bp.fileName, bp.lineNumber);
+        if (this.debuggerWebClient) {
+            this.proxyWebClientMethod(
+                this.debuggerWebClient,
+                this.clientProxyMappings.toggleBreakpoint,
+                bp.fileName,
+                bp.lineNumber,
+                false);
+        }
+    }
+
+
+    resume() {
+
+    }
+
+    attach() {
+
+    }
+
+    detach() {
+
+    }
+
+    /**
+     * Utility function that will compose a method call to the web client and execute it
+     * It will construct it in the form of "MethodName(....);"
+     * @param  {WebView.WebClient} webClient
+     * @param  {string} methodName
+     * @param  {any} ...params
+     */
+    proxyWebClientMethod(webClient: WebView.WebClient, methodName: string, ...params) {
+        let paramBuilder = [];
+
+        for (let p of params) {
+            switch (typeof (p)) {
+                case "boolean":
+                case "number":
+                    paramBuilder.push(p.toString());
+                    break;
+                default:
+                    paramBuilder.push(`"${p}"`);
+                    break;
+            }
+        }
+
+        const methodCall = `${methodName}(${paramBuilder.join(",")});`;
+        webClient.executeJavaScript(methodCall);
+    }
+}

+ 36 - 0
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/TypescriptLanguageExtension.ts

@@ -25,6 +25,8 @@ import * as ts from "../../../modules/typescript";
 import * as WorkerProcessTypes from "./workerprocess/workerProcessTypes";
 import ClientExtensionEventNames from "../../ClientExtensionEventNames";
 import * as tsLanguageSupport from "./tsLanguageSupport";
+import BreakpointDecoratorManager from "../../../debugger/BreakpointDecoratorManager";
+import * as debuggerProxy from "../../../debugger/HostDebuggerExtensionProxy";
 
 /**
  * Resource extension that handles compiling or transpling typescript on file save.
@@ -52,6 +54,8 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
 
     private editor: monaco.editor.IStandaloneCodeEditor;
 
+    private breakpointDecorator: BreakpointDecoratorManager;
+
     /**
      * Perform a full compile on save, or just transpile the current file
      * @type {boolean}
@@ -147,6 +151,38 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
 
             // Let's turn some things off in the editor.  These will be provided by the shared web worker
             if (this.isJsFile(ev.filename)) {
+
+                // for now, we just want to set breakpoints in JS files
+                editor.updateOptions({
+                    glyphMargin: true
+                });
+
+                this.breakpointDecorator = new BreakpointDecoratorManager(editor);
+                this.breakpointDecorator.setCurrentFileName(ev.filename);
+
+                let marginTimeout = 0;
+                editor.onMouseMove((e) => {
+                    var targetZone = e.target.toString();
+                    if (targetZone.indexOf("GUTTER_GLYPH_MARGIN") != -1) {
+                        clearTimeout(marginTimeout);
+                        var line = e.target.position.lineNumber;
+                        this.breakpointDecorator.updateMarginHover(line);
+                        marginTimeout = setTimeout(() => this.breakpointDecorator.removeMarginHover(), 500);
+                    } else {
+                        clearTimeout(marginTimeout);
+                        this.breakpointDecorator.removeMarginHover();
+                    }
+                });
+
+                editor.onMouseDown((e) => {
+                    var targetZone = e.target.toString();
+                    if (targetZone.indexOf("GUTTER_GLYPH_MARGIN") != -1) {
+                        var line = e.target.position.lineNumber;
+                        this.breakpointDecorator.toggleBreakpoint(ev.filename, line);
+                        debuggerProxy.toggleBreakpoint(ev.filename, line);
+                    }
+                });
+
                 monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
                     noEmit: true,
                     noResolve: true,

+ 108 - 0
Script/AtomicWebViewEditor/debugger/BreakpointDecoratorManager.ts

@@ -0,0 +1,108 @@
+export interface Breakpoint {
+    fileName: string;
+    lineNumber: number;
+}
+
+export default class BreakpointDecoratorManager {
+    currEditorDecorations: monaco.editor.IModelDeltaDecoration[] = [];
+    currEditorDecorationIds: string[] = [];
+    currFileName: string;
+
+    constructor(private editor: monaco.editor.IStandaloneCodeEditor) { }
+
+    setCurrentFileName(fileName: string) {
+        if (this.currFileName != fileName) {
+            this.clearBreakpointDecorations();
+        }
+        this.currFileName = fileName;
+    }
+
+    clearBreakpointDecorations() {
+        this.currEditorDecorations.length = 0;
+        this.currEditorDecorationIds = this.editor.deltaDecorations(this.currEditorDecorationIds, this.currEditorDecorations);
+    }
+
+    removeBreakpointDecoration(fileName: string, lineNumber: number) {
+        if (fileName != this.currFileName) {
+            return;
+        }
+
+        const idx = this.currEditorDecorations.findIndex(d => d.range.startLineNumber == lineNumber);
+        if (idx != -1) {
+            this.currEditorDecorations.splice(idx);
+            this.currEditorDecorationIds = this.editor.deltaDecorations(this.currEditorDecorationIds, this.currEditorDecorations);
+        }
+    }
+
+    addBreakpointDecoration(fileName: string, lineNumber: number, pendingBreakpoint?: boolean) {
+        if (fileName != this.currFileName) {
+            return;
+        }
+
+        const requestedBreakpointClass = pendingBreakpoint ? "code-breakpoint-pending" : "code-breakpoint";
+        let idx = this.currEditorDecorations.findIndex(d => d.range.startLineNumber == lineNumber);
+        if (idx != -1 && this.currEditorDecorations[idx].options.glyphMarginClassName != requestedBreakpointClass) {
+            this.removeBreakpointDecoration(fileName, lineNumber);
+            idx = -1;
+        }
+
+        if (idx == -1) {
+            this.currEditorDecorations.push({
+                range: new monaco.Range(lineNumber, 1, lineNumber, 1),
+                options: {
+                    glyphMarginClassName: pendingBreakpoint ? "code-breakpoint-pending" : "code-breakpoint"
+                }
+            });
+
+            this.currEditorDecorationIds = this.editor.deltaDecorations(this.currEditorDecorationIds, this.currEditorDecorations);
+        }
+    }
+
+    getBreakpointDecorator(fileName: string, lineNumber: number) {
+        return this.currEditorDecorations.find(d => d.range.startLineNumber == lineNumber && d.options.glyphMarginClassName != "code-breakpoint-margin-hover");
+    }
+
+    toggleBreakpoint(fileName: string, lineNumber: number) {
+        if (fileName != this.currFileName) {
+            return;
+        }
+
+        let decoration = this.getBreakpointDecorator(fileName, lineNumber);
+        if (decoration) {
+            this.removeBreakpointDecoration(fileName, lineNumber);
+        } else {
+            this.addBreakpointDecoration(fileName, lineNumber, false);
+        }
+    }
+
+    updateMarginHover(lineNumber: number) {
+        let idx = this.currEditorDecorations.findIndex(d => d.options.glyphMarginClassName == "code-breakpoint-margin-hover");
+
+        if (idx != -1 && this.currEditorDecorations[idx].range.startLineNumber == lineNumber) {
+            return; // nothing to do
+        }
+
+        const hover = {
+            range: new monaco.Range(lineNumber, 1, lineNumber, 1),
+            options: {
+                glyphMarginClassName: "code-breakpoint-margin-hover"
+            }
+        };
+
+        if (idx == -1) {
+            this.currEditorDecorations.push(hover);
+        } else {
+            this.currEditorDecorations[idx] = hover;
+        }
+
+        this.currEditorDecorationIds = this.editor.deltaDecorations(this.currEditorDecorationIds, this.currEditorDecorations);
+    }
+
+    removeMarginHover() {
+        let idx = this.currEditorDecorations.findIndex(d => d.options.glyphMarginClassName == "code-breakpoint-margin-hover");
+        if (idx != -1) {
+            this.currEditorDecorations.splice(idx);
+            this.currEditorDecorationIds = this.editor.deltaDecorations(this.currEditorDecorationIds, this.currEditorDecorations);
+        }
+    }
+}

+ 61 - 115
Script/AtomicWebViewEditor/debugger/DuktapeDebugger.ts

@@ -1,114 +1,7 @@
 import HostInteropType from "../interop";
+import * as debuggerProxy from "./HostDebuggerExtensionProxy";
+import {default as BreakpointDecoratorManager, Breakpoint} from "./BreakpointDecoratorManager";
 
-
-interface Breakpoint {
-    fileName: string;
-    lineNumber: number;
-}
-
-class BreakpointDecoratorManager {
-    currEditorDecorations: monaco.editor.IModelDeltaDecoration[] = [];
-    currEditorDecorationIds: string[] = [];
-    currFileName: string;
-
-    constructor(private editor: monaco.editor.IStandaloneCodeEditor) { }
-
-    setCurrentFileName(fileName: string) {
-        if (this.currFileName != fileName) {
-            this.clearBreakpointDecorations();
-        }
-        this.currFileName = fileName;
-    }
-
-    clearBreakpointDecorations() {
-        this.currEditorDecorations.length = 0;
-        this.currEditorDecorationIds = this.editor.deltaDecorations(this.currEditorDecorationIds, this.currEditorDecorations);
-    }
-
-    removeBreakpointDecoration(fileName: string, lineNumber: number) {
-        if (fileName != this.currFileName) {
-            return;
-        }
-
-        const idx = this.currEditorDecorations.findIndex(d => d.range.startLineNumber == lineNumber);
-        if (idx != -1) {
-            this.currEditorDecorations.splice(idx);
-            this.currEditorDecorationIds = this.editor.deltaDecorations(this.currEditorDecorationIds, this.currEditorDecorations);
-        }
-    }
-
-    addBreakpointDecoration(fileName: string, lineNumber: number, pendingBreakpoint?: boolean) {
-        if (fileName != this.currFileName) {
-            return;
-        }
-
-        const requestedBreakpointClass = pendingBreakpoint ? "code-breakpoint-pending" : "code-breakpoint";
-        let idx = this.currEditorDecorations.findIndex(d => d.range.startLineNumber == lineNumber);
-        if (idx != -1 && this.currEditorDecorations[idx].options.glyphMarginClassName != requestedBreakpointClass) {
-            this.removeBreakpointDecoration(fileName, lineNumber);
-            idx = -1;
-        }
-
-        if (idx == -1) {
-            this.currEditorDecorations.push({
-                range: new monaco.Range(lineNumber, 1, lineNumber, 1),
-                options: {
-                    glyphMarginClassName: pendingBreakpoint ? "code-breakpoint-pending" : "code-breakpoint"
-                }
-            });
-
-            this.currEditorDecorationIds = this.editor.deltaDecorations(this.currEditorDecorationIds, this.currEditorDecorations);
-        }
-    }
-
-    getBreakpointDecorator(fileName: string, lineNumber: number) {
-        return this.currEditorDecorations.find(d => d.range.startLineNumber == lineNumber && d.options.glyphMarginClassName != "code-breakpoint-margin-hover");
-    }
-
-    toggleBreakpoint(fileName: string, lineNumber: number) {
-        if (fileName != this.currFileName) {
-            return;
-        }
-
-        let decoration = this.getBreakpointDecorator(fileName, lineNumber);
-        if (decoration) {
-            this.removeBreakpointDecoration(fileName, lineNumber);
-        } else {
-            this.addBreakpointDecoration(fileName, lineNumber, true);
-        }
-    }
-
-    updateMarginHover(lineNumber: number) {
-        let idx = this.currEditorDecorations.findIndex(d => d.options.glyphMarginClassName == "code-breakpoint-margin-hover");
-
-        if (idx != -1 && this.currEditorDecorations[idx].range.startLineNumber == lineNumber) {
-            return; // nothing to do
-        }
-
-        const hover = {
-            range: new monaco.Range(lineNumber, 1, lineNumber, 1),
-            options: {
-                glyphMarginClassName: "code-breakpoint-margin-hover"
-            }
-        };
-
-        if (idx == -1) {
-            this.currEditorDecorations.push(hover);
-        } else {
-            this.currEditorDecorations[idx] = hover;
-        }
-
-        this.currEditorDecorationIds = this.editor.deltaDecorations(this.currEditorDecorationIds, this.currEditorDecorations);
-    }
-
-    removeMarginHover() {
-        let idx = this.currEditorDecorations.findIndex(d => d.options.glyphMarginClassName == "code-breakpoint-margin-hover");
-        if (idx != -1) {
-            this.currEditorDecorations.splice(idx);
-            this.currEditorDecorationIds = this.editor.deltaDecorations(this.currEditorDecorationIds, this.currEditorDecorations);
-        }
-    }
-}
 /*
  *  Duktape debugger web client
  *
@@ -348,33 +241,52 @@ export default class DuktapeDebugger {
         }
     }
 
-    deleteAllBreakpoints() {
+    deleteAllBreakpoints(notifyHost = true) {
         this.socket.emit("delete-all-breakpoints");
         this.breakpointDecorator.clearBreakpointDecorations();
+
+        if (notifyHost) {
+            debuggerProxy.removeAllBreakpoints();
+        }
     }
 
-    addBreakpoint(fileName: string, lineNumber: number) {
+    addBreakpoint(fileName: string, lineNumber: number, notifyHost = true) {
+        fileName = this.fixBreakpointFilename(fileName);
         this.breakpointDecorator.addBreakpointDecoration(fileName, lineNumber);
         this.socket.emit("add-breakpoint", {
             fileName,
             lineNumber
         });
+
+        if (notifyHost) {
+            debuggerProxy.addBreakpoint(fileName, lineNumber);
+        }
     }
 
-    toggleBreakpoint(fileName: string, lineNumber: number) {
+    toggleBreakpoint(fileName: string, lineNumber: number, notifyHost = true) {
+        fileName = this.fixBreakpointFilename(fileName);
         this.breakpointDecorator.toggleBreakpoint(fileName, lineNumber);
         this.socket.emit("toggle-breakpoint", {
             fileName,
             lineNumber
         });
+
+        if (notifyHost) {
+            debuggerProxy.toggleBreakpoint(fileName, lineNumber);
+        }
     }
 
-    deleteBreakpoint(fileName: string, lineNumber: number) {
+    removeBreakpoint(fileName: string, lineNumber: number, notifyHost = true) {
+        fileName = this.fixBreakpointFilename(fileName);
         this.breakpointDecorator.removeBreakpointDecoration(fileName, lineNumber);
         this.socket.emit("delete-breakpoint", {
             fileName,
             lineNumber
         });
+
+        if (notifyHost) {
+            debuggerProxy.removeBreakpoint(fileName, lineNumber);
+        }
     }
 
     initSocket() {
@@ -560,7 +472,7 @@ export default class DuktapeDebugger {
             //div.append(sub);
             //sub = $("<button id='add-breakpoint-button'></button>").text("Add breakpoint");
             //sub.on("click", () => {
-                //this.addBreakpoint($("#add-breakpoint-file").val(), Number($("#add-breakpoint-line").val()));
+            //this.addBreakpoint($("#add-breakpoint-file").val(), Number($("#add-breakpoint-line").val()));
             //});
             //div.append(sub);
             //sub = $("<span id='breakpoint-hint'></span>").text("or dblclick source");
@@ -575,7 +487,7 @@ export default class DuktapeDebugger {
                 div = $("<div class='breakpoint-line'></div>");
                 sub = $("<button class='delete-breakpoint-button'></button>").text("Delete");
                 sub.on("click", () => {
-                    this.deleteBreakpoint(bp.fileName, bp.lineNumber);
+                    this.removeBreakpoint(bp.fileName, bp.lineNumber);
                 });
                 div.append(sub);
                 sub = $("<a></a>").text((bp.fileName || "?") + ":" + (bp.lineNumber || 0));
@@ -645,6 +557,7 @@ export default class DuktapeDebugger {
 
         $("#attach-button").click(() => {
             this.socket.emit("attach", {});
+            this.retrieveBreakpoints();
         });
 
         $("#detach-button").click(() => {
@@ -913,8 +826,41 @@ export default class DuktapeDebugger {
             // nop
         });
 
+        this.registerDebuggerFunctions();
+
         this.forceButtonUpdate = true;
         this.doUiUpdate();
+
     };
 
+    async retrieveBreakpoints() {
+        let s = await debuggerProxy.getBreakpoints();
+
+        // If the filename starts with "Resources" then trim it off since the module
+        // name won't have that, but the editor uses it
+        s.forEach(b => this.addBreakpoint(b.fileName, b.lineNumber));
+    }
+
+    registerDebuggerFunctions() {
+        // Register the callback functions
+        const interop = HostInteropType.getInstance();
+        interop.addCustomHostRoutine(
+            debuggerProxy.debuggerHostKeys.toggleBreakpoint,
+            this.toggleBreakpoint.bind(this));
+
+        interop.addCustomHostRoutine(
+            debuggerProxy.debuggerHostKeys.addBreakpoint,
+            this.addBreakpoint.bind(this));
+
+        interop.addCustomHostRoutine(
+            debuggerProxy.debuggerHostKeys.removeBreakpoint,
+            this.removeBreakpoint.bind(this));
+
+        debuggerProxy.registerDebuggerFrameWithHost();
+    }
+
+    fixBreakpointFilename(fileName: string): string {
+        return fileName.replace(/^Resources\//, "");
+    }
+
 }

+ 70 - 0
Script/AtomicWebViewEditor/debugger/HostDebuggerExtensionProxy.ts

@@ -0,0 +1,70 @@
+import HostInteropType from "../interop";
+
+export interface Breakpoint {
+    fileName: string;
+    lineNumber: number;
+}
+
+/**
+ * Get a list of all breakpoints that have been registered
+ * @return {Breakpoint[]}
+ */
+export async function getBreakpoints() : Promise<Breakpoint[]> {
+    return atomicHostRequest<Breakpoint[]>("Debugger.GetBreakpoints");
+}
+
+/**
+ * Add a single breakpoint
+ * @param  {string} fileName
+ * @param  {number} lineNumber
+ */
+export async function addBreakpoint(fileName: string, lineNumber: number) {
+    return atomicHostEvent("Debugger.AddBreakpoint", {
+        fileName,
+        lineNumber
+    });
+}
+
+/**
+ * Remove a single breakpoint
+ * @param  {string} fileName
+ * @param  {number} lineNumber
+ */
+export async function removeBreakpoint(fileName: string, lineNumber: number) {
+    return atomicHostEvent("Debugger.RemoveBreakpoint", {
+        fileName,
+        lineNumber
+    });
+}
+
+/**
+ * Toggle a single breakpoint
+ * @param  {string} fileName
+ * @param  {number} lineNumber
+ */
+export async function toggleBreakpoint(fileName: string, lineNumber: number) {
+    return atomicHostEvent("Debugger.ToggleBreakpoint", {
+        fileName,
+        lineNumber
+    });
+}
+
+/**
+ * Remove all breakpoints
+ */
+export async function removeAllBreakpoints() {
+    return atomicHostEvent("Debugger.RemoveAllBreakpoints");
+}
+
+export const debuggerHostKeys = {
+    toggleBreakpoint: "HOST_DEBUGGER_ToggleBreakpoint",
+    addBreakpoint: "HOST_DEBUGGER_AddBreakpoint",
+    removeBreakpoint: "HOST_DEBUGGER_RemoveBreakpoint"
+};
+
+/**
+ * Register host-callable commands with the web view
+ */
+export function  registerDebuggerFrameWithHost() {
+    atomicHostEvent("Debugger.RegisterDebuggerFrame", debuggerHostKeys);
+}

+ 1 - 1
Script/AtomicWebViewEditor/interop.ts

@@ -193,7 +193,7 @@ export default class HostInteropType {
      * @param  {string} routineName
      * @param  {(} callback
      */
-    addCustomHostRoutine(routineName: string, callback: () => void) {
+    addCustomHostRoutine(routineName: string, callback: (...args) => void) {
         window[routineName] = callback;
     }
 

+ 2 - 0
Script/AtomicWebViewEditor/tsconfig.json

@@ -31,7 +31,9 @@
         "./clientExtensions/languageExtensions/typescript/workerprocess/workerLoader.ts",
         "./clientExtensions/languageExtensions/typescript/workerprocess/workerProcessTypes.ts",
         "./clientExtensions/ServiceLocator.ts",
+        "./debugger/BreakpointDecoratorManager.ts",
         "./debugger/DuktapeDebugger.ts",
+        "./debugger/HostDebuggerExtensionProxy.ts",
         "./editor/editor.ts",
         "./editor/editorCommands.ts",
         "./interop.ts",

+ 1 - 0
Script/tsconfig.json

@@ -28,6 +28,7 @@
         "./AtomicEditor/editor/EditorEvents.ts",
         "./AtomicEditor/editor/EditorLicense.ts",
         "./AtomicEditor/editor/Preferences.ts",
+        "./AtomicEditor/hostExtensions/coreExtensions/DuktapeDebuggerExtension.ts",
         "./AtomicEditor/hostExtensions/coreExtensions/ProjectBasedExtensionLoader.ts",
         "./AtomicEditor/hostExtensions/HostExtensionServices.ts",
         "./AtomicEditor/hostExtensions/languageExtensions/CSharpLanguageExtension.ts",