Jelajahi Sumber

- More work on trying to coordinate multiple editors so that if toggling breakpoints in the debugger view will also toggle the associated breakpoint in the actual editor.
- Extracted the logic that controls adding and removing breakpoints on an editor to it’s own client extension that attaches to the current editor window.

Shaddock Heath 8 tahun lalu
induk
melakukan
d11b33f152

+ 62 - 35
Script/AtomicEditor/hostExtensions/coreExtensions/DuktapeDebuggerExtension.ts

@@ -83,6 +83,18 @@ class BreakpointList {
     }
 }
 
+interface ClientProxyMappings {
+    toggleBreakpoint: string;
+    addBreakpoint: string;
+    removeBreakpoint: string;
+}
+
+interface ClientListener {
+    name: string;
+    frame: WebView.WebClient;
+    callbacks: ClientProxyMappings;
+}
+
 /**
  * extension that will communicate with the duktape debugger
  */
@@ -90,14 +102,8 @@ export default class DuktapeDebuggerExtension extends Atomic.ScriptObject implem
     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 listeners: ClientListener[] = [];
 
     private breakpointList = new BreakpointList();
 
@@ -129,7 +135,7 @@ export default class DuktapeDebuggerExtension extends Atomic.ScriptObject implem
                 this.webMessageEventResponse(webMessage);
                 break;
             case "Debugger.ToggleBreakpoint":
-                this.toggleBreakpoint(data);
+                this.toggleBreakpoint(data, webMessage.handler.webClient);
                 this.webMessageEventResponse(webMessage);
                 break;
             case "Debugger.RemoveAllBreakpoints":
@@ -139,8 +145,8 @@ export default class DuktapeDebuggerExtension extends Atomic.ScriptObject implem
             case "Debugger.GetBreakpoints":
                 this.webMessageEventResponse(webMessage, this.breakpointList.getBreakpoints());
                 break;
-            case "Debugger.RegisterDebuggerFrame":
-                this.registerDebuggerFrame(webMessage, data);
+            case "Debugger.RegisterDebuggerListener":
+                this.registerDebuggerListener(webMessage, data);
                 this.webMessageEventResponse(webMessage);
                 break;
         }
@@ -158,44 +164,65 @@ export default class DuktapeDebuggerExtension extends Atomic.ScriptObject implem
         }
     }
 
-    registerDebuggerFrame(originalMessage: WebView.WebMessageEvent, mappings) {
-        this.debuggerWebClient = originalMessage.handler.webClient;
-        this.clientProxyMappings = mappings;
+    registerDebuggerListener(originalMessage: WebView.WebMessageEvent, messageData: {
+        name: string,
+        callbacks: any
+    }) {
+        console.log(`Registering debug listener: ${messageData.name}`);
+        const listenerReference: ClientListener = {
+            name: messageData.name,
+            frame: originalMessage.handler.webClient,
+            callbacks: messageData.callbacks
+        };
+
+        this.listeners.push(listenerReference);
+
+        // clean up any stale ones
+        this.listeners = this.listeners.filter(l => l.frame);
     }
 
     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);
+        for (let listener of this.listeners) {
+            if (listener.frame) {
+                console.log(`Adding breakpoint ${bp.fileName}:${bp.lineNumber} to ${listener.name}`);
+                this.proxyWebClientMethod(
+                    listener.frame,
+                    listener.callbacks.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);
+        for (let listener of this.listeners) {
+            if (listener.frame) {
+                console.log(`Remove breakpoint ${bp.fileName}:${bp.lineNumber} to ${listener.name}`);
+                this.proxyWebClientMethod(
+                    listener.frame,
+                    listener.callbacks.removeBreakpoint,
+                    bp.fileName,
+                    bp.lineNumber,
+                    false);
+            }
         }
     }
 
-    toggleBreakpoint(bp: Breakpoint) {
+    toggleBreakpoint(bp: Breakpoint, sender: WebView.WebClient) {
         this.breakpointList.toggleBreakpoint(bp.fileName, bp.lineNumber);
-        if (this.debuggerWebClient) {
-            this.proxyWebClientMethod(
-                this.debuggerWebClient,
-                this.clientProxyMappings.toggleBreakpoint,
-                bp.fileName,
-                bp.lineNumber,
-                false);
+        for (let listener of this.listeners) {
+            if (listener.frame && listener.frame != sender) {
+                console.log(`Sending Toggle breakpoint ${bp.fileName}:${bp.lineNumber} to ${listener.name}`);
+                this.proxyWebClientMethod(
+                    listener.frame,
+                    listener.callbacks.toggleBreakpoint,
+                    bp.fileName,
+                    bp.lineNumber,
+                    false);
+            }
         }
     }
 

+ 2 - 0
Script/AtomicWebViewEditor/clientExtensions/ServiceLocator.ts

@@ -25,6 +25,7 @@ import * as ClientExtensionServices from "./ClientExtensionServices";
 import tsExtension from "./languageExtensions/typescript/TypescriptLanguageExtension";
 import jsExtension from "./languageExtensions/javascript/JavascriptLanguageExtension";
 import tbExtension from "./languageExtensions/turbobadger/TurboBadgerLanguageExtension";
+import debuggerHookExtension from "./languageExtensions/debugger/EditorDebuggingHook";
 
 /**
  * Generic service locator of editor services that may be injected by either a plugin
@@ -92,3 +93,4 @@ export default serviceLocator;
 serviceLocator.loadService(new tsExtension());
 serviceLocator.loadService(new jsExtension());
 serviceLocator.loadService(new tbExtension());
+serviceLocator.loadService(new debuggerHookExtension());

+ 169 - 0
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/debugger/EditorDebuggingHook.ts

@@ -0,0 +1,169 @@
+//
+// 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.
+//
+// Based upon the TypeScript language services example at https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API#incremental-build-support-using-the-language-services
+
+import HostInteropType from "../../../interop";
+import ClientExtensionEventNames from "../../ClientExtensionEventNames";
+import BreakpointDecoratorManager from "../../../debugger/BreakpointDecoratorManager";
+import * as debuggerProxy from "../../../debugger/HostDebuggerExtensionProxy";
+
+/**
+ * Resource extension that handles compiling or transpling typescript on file save.
+ */
+export default class EditorDebuggingHook implements Editor.ClientExtensions.WebViewServiceEventListener {
+    name = "EditorDebuggingHook";
+    description = "This extension adds debugger functionality to the current editor";
+
+    /**
+     * current filename
+     * @type {string}
+     */
+    filename: string;
+
+    /**
+     * Is this instance of the extension active?  Only true if the current editor
+     * is a typescript file.
+     * @type {Boolean}
+     */
+    private active = false;
+
+    private serviceLocator: Editor.ClientExtensions.ClientServiceLocator;
+
+    private editor: monaco.editor.IStandaloneCodeEditor;
+
+    private breakpointDecorator: BreakpointDecoratorManager;
+
+    /**
+    * Inject this language service into the registry
+     * @param  {Editor.ClientExtensions.ClientServiceLocator} serviceLocator
+     */
+    initialize(serviceLocator: Editor.ClientExtensions.ClientServiceLocator) {
+        // initialize the language service
+        this.serviceLocator = serviceLocator;
+        serviceLocator.clientServices.register(this);
+    }
+
+    /**
+     * Determines if the file name/path provided is something we care about
+     * @param  {string} path
+     * @return {boolean}
+     */
+    private isValidFiletype(path: string): boolean {
+
+        // For now, only allow JS files
+        let ext = path.split(".").pop();
+        return ext == "js";
+    }
+
+    /**
+     * Grab the list of breakpoints from the host
+     */
+    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 => {
+            if (b.fileName == this.filename) {
+                this.breakpointDecorator.addBreakpointDecoration(b.fileName, b.lineNumber);
+            }
+        });
+    }
+
+    registerDebuggerFunctions() {
+        // Register the callback functions
+        const interop = HostInteropType.getInstance();
+        interop.addCustomHostRoutine(
+            debuggerProxy.debuggerHostKeys.toggleBreakpoint,
+            (filename, linenumber) => {
+                if (filename == this.filename || "Resources/" + filename == this.filename) {
+                    this.breakpointDecorator.toggleBreakpoint(this.filename, linenumber);
+                }
+            });
+
+        interop.addCustomHostRoutine(
+            debuggerProxy.debuggerHostKeys.addBreakpoint,
+            (filename, linenumber) => {
+                if (filename == this.filename || "Resources/" + filename == this.filename) {
+                    this.breakpointDecorator.addBreakpointDecoration(this.filename, linenumber);
+                }
+            });
+
+        interop.addCustomHostRoutine(
+            debuggerProxy.debuggerHostKeys.removeBreakpoint,
+            (filename, linenumber) => {
+                if (filename == this.filename || "Resources/" + filename == this.filename) {
+                    this.breakpointDecorator.removeBreakpointDecoration(this.filename, linenumber);
+                }
+            });
+
+        debuggerProxy.registerDebuggerListener(this.filename);
+    }
+
+    /**
+     * Called when the editor needs to be configured for a particular file
+     * @param  {Editor.ClientExtensions.EditorFileEvent} ev
+     */
+    configureEditor(ev: Editor.ClientExtensions.EditorFileEvent) {
+        if (this.isValidFiletype(ev.filename)) {
+            let editor = ev.editor as monaco.editor.IStandaloneCodeEditor;
+            this.editor = editor; // cache this so that we can reference it later
+            this.active = true;
+            this.filename = 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);
+            this.registerDebuggerFunctions();
+
+            this.retrieveBreakpoints();
+
+            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);
+                }
+            });
+        }
+    }
+}

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

@@ -25,8 +25,6 @@ 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.
@@ -54,8 +52,6 @@ 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}
@@ -152,45 +148,6 @@ 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,
-                    allowNonTsExtensions: true,
-                    noLib: true,
-                    target: monaco.languages.typescript.ScriptTarget.ES5
-                });
-
                 monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
                     noSemanticValidation: true,
                     noSyntaxValidation: true

+ 1 - 1
Script/AtomicWebViewEditor/debugger/DuktapeDebugger.ts

@@ -856,7 +856,7 @@ export default class DuktapeDebugger {
             debuggerProxy.debuggerHostKeys.removeBreakpoint,
             this.removeBreakpoint.bind(this));
 
-        debuggerProxy.registerDebuggerFrameWithHost();
+        debuggerProxy.registerDebuggerListener("DuktapeDebugger");
     }
 
     fixBreakpointFilename(fileName: string): string {

+ 9 - 3
Script/AtomicWebViewEditor/debugger/HostDebuggerExtensionProxy.ts

@@ -63,8 +63,14 @@ export const debuggerHostKeys = {
 };
 
 /**
- * Register host-callable commands with the web view
+ * Register host-callable commands with the web view.  This registers the
+ * this named web view with the host so that it can be notified of any
+ * host initiated debugger messages
+ * @param {string} name The name associated with the listener.  Ususally the filename.
  */
-export function  registerDebuggerFrameWithHost() {
-    atomicHostEvent("Debugger.RegisterDebuggerFrame", debuggerHostKeys);
+export function  registerDebuggerListener(name: string) {
+    atomicHostEvent("Debugger.RegisterDebuggerListener", {
+        name,
+        callbacks: debuggerHostKeys
+    });
 }

+ 1 - 0
Script/AtomicWebViewEditor/tsconfig.json

@@ -22,6 +22,7 @@
     "files": [
         "./clientExtensions/ClientExtensionEventNames.ts",
         "./clientExtensions/ClientExtensionServices.ts",
+        "./clientExtensions/languageExtensions/debugger/EditorDebuggingHook.ts",
         "./clientExtensions/languageExtensions/javascript/JavascriptLanguageExtension.ts",
         "./clientExtensions/languageExtensions/turbobadger/TurboBadgerLanguageExtension.ts",
         "./clientExtensions/languageExtensions/typescript/tsLanguageSupport.ts",