Browse Source

migrated the autocomplete functionality for typescript into a shared web worker so that each tab can share the same instance of the language service. It also runs on a background thread so it performs better

Shaddock Heath 9 years ago
parent
commit
1ed3b35a06

+ 126 - 165
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/TypescriptLanguageExtension.ts

@@ -7,74 +7,7 @@
 // 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 * as ts from "../../../modules/typescript";
-import {TypescriptLanguageService, FileSystemInterface} from "./TypescriptLanguageService";
-
-interface TSConfigFile {
-    compilerOptions?: ts.CompilerOptions;
-    files: Array<string>;
-}
-
-/**
- * Class that provides access to the Atomic filesystem routines
- */
-class WebFileSystem implements FileSystemInterface {
-
-    private fileCache = {};
-    /**
-     * Deterimine if the particular file exists in the resources
-     * @param  {string} filename
-     * @return {boolean}
-     */
-    fileExists(filename: string): boolean {
-        return this.fileCache[filename] != null;
-    }
-
-    /**
-     * Cache a file in the filesystem
-     * @param  {string} filename
-     * @param  {string} file
-     */
-    cacheFile(filename: string, file: string) {
-        this.fileCache[filename] = file;
-    }
-
-    /**
-     * Grab the contents of the file
-     * @param  {string} filename
-     * @return {string}
-     */
-    getFile(filename: string): string {
-        console.log("FS.GETFILE!!");
-        // return HostInterop.getResource("atomic:" + filename);
-        return this.fileCache[filename];
-    }
-
-    /**
-     * Write the contents to the file specified
-     * @param  {string} filename
-     * @param  {string} contents
-     */
-    writeFile(filename: string, contents: string) {
-        //TODO:
-        /*let script = new Atomic.File(filename, Atomic.FILE_WRITE);
-        try {
-            script.writeString(contents);
-            script.flush();
-        } finally {
-            script.close();
-        }
-        */
-    }
-
-    /**
-     * Returns the current directory / root of the source tree
-     * @return {string}
-     */
-    getCurrentDirectory(): string {
-        return "";
-    }
-
-}
+import * as WorkerProcessCommands from "./workerprocess/workerProcessCommands";
 
 /**
  * Resource extension that handles compiling or transpling typescript on file save.
@@ -90,20 +23,17 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
     private filename: string;
 
     private serviceLocator: Editor.ClientExtensions.ClientServiceLocator;
+
+    private worker: SharedWorker.SharedWorker;
+
+    private editor;
+
     /**
      * Perform a full compile on save, or just transpile the current file
      * @type {boolean}
      */
     fullCompile: boolean = true;
 
-    /**
-     * The language service that will handle building
-     * @type {TypescriptLanguageService}
-     */
-    languageService: TypescriptLanguageService = null;
-
-    fs: WebFileSystem; // needed?
-
     /**
     * Inject this language service into the registry
      * @param  {Editor.ClientExtensions.ClientServiceLocator} serviceLocator
@@ -111,8 +41,6 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
     initialize(serviceLocator: Editor.ClientExtensions.ClientServiceLocator) {
         // initialize the language service
         this.serviceLocator = serviceLocator;
-        this.fs = new WebFileSystem();
-        this.languageService = new TypescriptLanguageService(this.fs);
     }
 
     /**
@@ -125,28 +53,26 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
         return ext == "ts";
     }
 
-
     /**
-     * Seed the language service with all of the relevant files in the project
+     * Utility function that handles sending a request to the worker process and then when
+     * a response is received pass it back to the caller.  Since this is all handled async,
+     * it will be facilitated by passing back a promise
+     * @param  {string} responseChannel The unique string name of the response message channel
+     * @param  {any} message
+     * @return {PromiseLike}
      */
-    private loadProjectFiles() {
-        this.serviceLocator.getHostInterop().getFileResource("resources/tsconfig.atomic").then((jsonTsConfig: string) => {
-            let promises: PromiseLike<void>[] = [];
-            let tsConfig: TSConfigFile = JSON.parse(jsonTsConfig);
+    private workerRequest(responseChannel: string, message: any): PromiseLike<{}> {
+        let worker = this.worker;
 
-            if (tsConfig.compilerOptions) {
-                this.languageService.compilerOptions = tsConfig.compilerOptions;
+        return new Promise((resolve, reject) => {
+            const responseCallback = function(e: WorkerProcessCommands.WorkerProcessMessage<any>) {
+                if (e.data.command == responseChannel) {
+                    worker.port.removeEventListener("message", responseCallback);
+                    resolve(e.data);
+                }
             };
-
-            tsConfig.files.forEach((f) => {
-                promises.push(this.serviceLocator.getHostInterop().getFileResource(f).then((code: string) => {
-                    this.languageService.addProjectFile(f, code);
-                }));
-            });
-            return Promise.all(promises);
-        }).then(() => {
-            // Let's seed the compiler state
-            this.languageService.compile([this.filename]);
+            this.worker.port.addEventListener("message", responseCallback);
+            this.worker.port.postMessage(message);
         });
     }
 
@@ -156,8 +82,8 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
      */
     configureEditor(ev: Editor.EditorEvents.EditorFileEvent) {
         if (this.isValidFiletype(ev.filename)) {
-            let editor = <AceAjax.Editor>ev.editor;
-            editor.session.setMode("ace/mode/typescript");
+            this.editor = <AceAjax.Editor>ev.editor;
+            this.editor.session.setMode("ace/mode/typescript");
         }
     }
 
@@ -173,14 +99,50 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
             let editor = ev.editor;
 
             // we only want the typescript completer, otherwise we get a LOT of noise
-            editor.completers = [this.buildWordCompleter(ev.filename, this.languageService)];
+            editor.completers = [this.buildWordCompleter(ev.filename)];
 
-            // for now, we will handle project stuff in the same thread.  In the future we need to share this between sessions
-            this.languageService.reset();
-            this.loadProjectFiles();
+            // Build our worker
+            this.buildWorker();
+
+            // post a message to the shared web worker
+            this.worker.port.postMessage({ command: WorkerProcessCommands.Connect, sender: "Typescript Language Extension", filename: ev.filename });
         }
     }
 
+    /**
+     * Handler for any messages initiated by the worker process
+     * @param  {any} e
+     * @return {[type]}
+     */
+    handleWorkerMessage(e: WorkerProcessCommands.WorkerProcessMessage<any>) {
+        switch (e.data.command) {
+            case WorkerProcessCommands.Message:
+                console.log(e.data.message);
+                break;
+            case WorkerProcessCommands.Alert:
+                alert(e.data.message);
+                break;
+        }
+    }
+
+    /**
+     * Build/Attach to the shared web worker
+     */
+    buildWorker() {
+
+        this.worker = new SharedWorker("./source/editorCore/clientExtensions/languageExtensions/typescript/workerprocess/workerLoader.js");
+
+        // hook up the event listener
+        this.worker.port.addEventListener("message", this.handleWorkerMessage.bind(this), false);
+
+        // Tell the SharedWorker we're closing
+        addEventListener("beforeunload", () => {
+            this.worker.port.postMessage({ command: WorkerProcessCommands.Disconnect });
+        });
+
+        this.worker.port.start();
+    }
+
     /**
      * Builds the word completer for the Ace Editor.  This will handle determining which items to display in the popup and in which order
      * @param  {string} filename the filename of the current file
@@ -188,49 +150,42 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
      * @param  {FileSystemInterface} fs the interface into the file system
      * @return {[type]} returns a completer
      */
-    private buildWordCompleter(filename: string, langService: TypescriptLanguageService): {
-        getDocTooltip?: (selected: any) => { docText?: string, docHTML?: string },
+    private buildWordCompleter(filename: string): {
+        getDocTooltip?: (selected: WorkerProcessCommands.WordCompletion) => void,
         getCompletions: (editor, session, pos, prefix, callback) => void
     } {
-        //let langTools = ace.require("ace/ext/language_tools");
+        let extension = this;
         let wordCompleter = {
-            getDocTooltip: function(selected: any): { docText?: string, docHTML?: string } {
-                let details = langService.getCompletionEntryDetails(filename, selected.pos, selected.caption);
-                if (details) {
-                    return {
-                        docHTML: details.displayParts.map(part => part.text).join("")
-                        + "<br/>"
-                        + details.documentation.map(part => part.text).join("")
-                    };
-                } else {
-                    return null;
-                }
+            getDocTooltip: function(selected: WorkerProcessCommands.WordCompletion) {
+                const message: WorkerProcessCommands.GetDocTooltipMessageData = {
+                    command: WorkerProcessCommands.GetDocTooltip,
+                    filename: extension.filename,
+                    completionItem: selected,
+                    pos: selected.pos
+                };
+
+                // Since the doc tooltip built in function of Ace doesn't support async calls to retrieve the tootip,
+                // we need to go ahead and call the worker and then force the display of the tooltip when we get
+                // a result back
+                extension.workerRequest(WorkerProcessCommands.DocTooltipResponse, message)
+                    .then((e: WorkerProcessCommands.GetDocTooltipResponseMessageData) => {
+                    extension.editor.completer.showDocTooltip(e);
+                });
             },
 
             getCompletions: function(editor, session, pos, prefix, callback) {
-                try {
-                    let sourceFile = langService.updateProjectFile(filename, editor.session.getValue());
-
-                    //langService.compile([ev.filename]);
-                    let newpos = langService.getPositionOfLineAndCharacter(sourceFile, pos.row, pos.column);
-                    let completions = langService.getCompletions(filename, newpos);
-                    if (completions) {
-                        callback(null, completions.entries.map(function(completion: ts.CompletionEntry) {
-                            return {
-                                caption: completion.name,
-                                value: completion.name,
-                                score: 100 - parseInt(completion.sortText, 0),
-                                meta: completion.kind,
-                                pos: newpos
-                            };
-                        }));
-                    } else {
-                        console.log("No completions available: " + prefix);
-                    }
-                } catch (e) {
-                    console.log("Failure completing " + filename);
-                    console.log(e);
-                }
+                const message: WorkerProcessCommands.GetCompletionsMessageData = {
+                    command: WorkerProcessCommands.GetCompletions,
+                    filename: extension.filename,
+                    pos: pos,
+                    sourceText: editor.session.getValue(),
+                    prefix: prefix
+                };
+
+                extension.workerRequest(WorkerProcessCommands.CompletionResponse, message)
+                    .then((e: WorkerProcessCommands.GetCompletionsResponseMessageData) => {
+                    callback(null, e.completions);
+                });
             }
         };
         return wordCompleter;
@@ -242,40 +197,46 @@ export default class TypescriptLanguageExtension implements Editor.ClientExtensi
      * Called once a resource has been saved
      * @param  {Editor.EditorEvents.SaveResourceEvent} ev
      */
-    save(ev: Editor.EditorEvents.SaveResourceEvent) {
-        if (this.isValidFiletype(ev.path)) {
-            console.log(`${this.name}: received a save resource event for ${ev.path}`);
-            if (this.fullCompile) {
-                this.languageService.compile([ev.path]);
-            } else {
-                this.languageService.transpile([ev.path]);
-            }
-        }
-    }
+    // save(ev: Editor.EditorEvents.SaveResourceEvent) {
+    //     if (this.isValidFiletype(ev.path)) {
+    //         console.log(`${this.name}: received a save resource event for ${ev.path}`);
+    //         this.worker.port.postMessage({
+    //             command: WorkerProcessCommands.Save,
+    //             path: ev.path
+    //         });
+    //     }
+    // }
 
     /**
      * Handle the delete.  This should delete the corresponding javascript file
      * @param  {Editor.EditorEvents.DeleteResourceEvent} ev
      */
-    delete(ev: Editor.EditorEvents.DeleteResourceEvent) {
-        if (this.isValidFiletype(ev.path)) {
-            console.log(`${this.name}: received a delete resource event`);
-
-            // notify the typescript language service that the file has been deleted
-            this.languageService.deleteProjectFile(ev.path);
-        }
-    }
+    // delete(ev: Editor.EditorEvents.DeleteResourceEvent) {
+    //     if (this.isValidFiletype(ev.path)) {
+    //         console.log(`${this.name}: received a delete resource event`);
+    //
+    //         // notify the typescript language service that the file has been deleted
+    //         this.worker.port.postMessage({
+    //             command: WorkerProcessCommands.Delete,
+    //             path: ev.path
+    //         });
+    //     }
+    // }
 
     /**
      * Handle the rename.  Should rename the corresponding .js file
      * @param  {Editor.EditorEvents.RenameResourceEvent} ev
      */
-    rename(ev: Editor.EditorEvents.RenameResourceEvent) {
-        if (this.isValidFiletype(ev.path)) {
-            console.log(`${this.name}: received a rename resource event`);
-
-            // notify the typescript language service that the file has been renamed
-            this.languageService.renameProjectFile(ev.path, ev.newPath);
-        }
-    }
+    // rename(ev: Editor.EditorEvents.RenameResourceEvent) {
+    //     if (this.isValidFiletype(ev.path)) {
+    //         console.log(`${this.name}: received a rename resource event`);
+    //
+    //         // notify the typescript language service that the file has been renamed
+    //         this.worker.port.postMessage({
+    //             command: WorkerProcessCommands.Rename,
+    //             path: ev.path,
+    //             newPath: ev.newPath
+    //         });
+    //     }
+    // }
 }

+ 1 - 18
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/TypescriptLanguageService.ts → Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/workerprocess/TypescriptLanguageService.ts

@@ -6,7 +6,7 @@
 //
 // 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 * as ts from "../../../modules/typescript";
+import * as ts from "../../../../modules/typescript";
 
 /**
  * Abstraction over the file system
@@ -82,33 +82,16 @@ export class TypescriptLanguageService {
             getScriptVersion: (fileName) => this.versionMap[fileName] && this.versionMap[fileName].version.toString(),
             getScriptSnapshot: (filename) => {
                 const scriptVersion = this.versionMap[filename];
-                // if (!this.fs.fileExists(fileName)) {
-                //     if (scriptVersion) {
-                //         delete this.versionMap[fileName];
-                //         let idx = this.projectFiles.indexOf(fileName);
-                //         if (idx > -1) {
-                //             this.projectFiles.splice(idx, 1);
-                //         }
-                //     }
-                //     console.log("file not exist: " + fileName);
-                //     return undefined;
-                // }
 
                 // Grab the cached version
                 if (scriptVersion) {
                     if (scriptVersion.snapshot) {
-                        console.log(`returning snapshot for ${filename} version ${scriptVersion.version}`);
                         return scriptVersion.snapshot;
                     } else {
                         console.log(`!!! creating snapshot for ${filename}`);
                         let sourceFile = this.documentRegistry.acquireDocument(filename, this.compilerOptions, ts.ScriptSnapshot.fromString(""), scriptVersion.version.toString());
                         return ts.ScriptSnapshot.fromString(sourceFile.text);
-                        //let script = this.fs.getFile(fileName);
-                        //scriptVersion.snapshot = ts.ScriptSnapshot.fromString(script);
-                        //return scriptVersion.snapshot;
                     }
-                } else {
-                    // console.log(`no script version for ${fileName}`);
                 }
             },
             getCurrentDirectory: () => this.fs.getCurrentDirectory(),

+ 280 - 0
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/workerprocess/TypescriptLanguageServiceWebWorker.ts

@@ -0,0 +1,280 @@
+/**
+ * Typescript Language Worker - Handles bringing down the source, providing completions, and compiling.
+ * since this is a shared web worker, each editor tab will be sharing the same data.
+ */
+import * as ts from "../../../../modules/typescript";
+import {TypescriptLanguageService, FileSystemInterface} from "./TypescriptLanguageService";
+import * as WorkerProcessCommands from "./workerProcessCommands";
+
+interface TSConfigFile {
+    compilerOptions?: ts.CompilerOptions;
+    files: Array<string>;
+}
+
+/**
+ * Queries the host for a particular resource and returns it in a promise
+ * @param  {string} codeUrl
+ * @return {Promise}
+ */
+function getResource(codeUrl: string): Promise<{}> {
+    return new Promise(function(resolve, reject) {
+        const xmlHttp = new XMLHttpRequest();
+        xmlHttp.onreadystatechange = () => {
+            if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
+                resolve(xmlHttp.responseText);
+            }
+        };
+        xmlHttp.open("GET", codeUrl, true); // true for asynchronous
+        xmlHttp.send(null);
+    });
+}
+
+/**
+ * Returns a file resource from the resources directory
+ * @param  {string} filename name and path of file under the project directory or a fully qualified file name
+ * @return {Promise}
+ */
+function getFileResource(filename: string): Promise<{}> {
+    return getResource(`atomic://${filename}`);
+}
+
+/**
+ * Class that provides access to the Atomic filesystem routines
+ */
+class WebFileSystem implements FileSystemInterface {
+
+    private fileCache = {};
+    /**
+     * Deterimine if the particular file exists in the resources
+     * @param  {string} filename
+     * @return {boolean}
+     */
+    fileExists(filename: string): boolean {
+        return this.fileCache[filename] != null;
+    }
+
+    /**
+     * Cache a file in the filesystem
+     * @param  {string} filename
+     * @param  {string} file
+     */
+    cacheFile(filename: string, file: string) {
+        this.fileCache[filename] = file;
+    }
+
+    /**
+     * Grab the contents of the file
+     * @param  {string} filename
+     * @return {string}
+     */
+    getFile(filename: string): string {
+        console.log("FS.GETFILE!!");
+        // return HostInterop.getResource("atomic:" + filename);
+        return this.fileCache[filename];
+    }
+
+    /**
+     * Write the contents to the file specified
+     * @param  {string} filename
+     * @param  {string} contents
+     */
+    writeFile(filename: string, contents: string) {
+        //TODO:
+        /*let script = new Atomic.File(filename, Atomic.FILE_WRITE);
+        try {
+            script.writeString(contents);
+            script.flush();
+        } finally {
+            script.close();
+        }
+        */
+    }
+
+    /**
+     * Returns the current directory / root of the source tree
+     * @return {string}
+     */
+    getCurrentDirectory(): string {
+        return "";
+    }
+
+}
+
+export default class TypescriptLanguageServiceWebWorker {
+    /**
+     * Number of editors connected to this shared worker
+     * @type {Number}
+     */
+    connections = 0;
+
+    /**
+     * The language service that will handle building
+     * @type {TypescriptLanguageService}
+     */
+    languageService: TypescriptLanguageService = null;
+
+    fs: WebFileSystem; // needed?
+
+    projectLoaded = false;
+
+    constructor() {
+        this.fs = new WebFileSystem();
+        this.languageService = new TypescriptLanguageService(this.fs);
+    }
+
+    /**
+     * Handle when an editor connects to this worker
+     * @param  {any} e
+     */
+    connect(e) {
+        let port: MessagePort = e.ports[0];
+        this.connections++;
+
+        port.addEventListener("message", (e:WorkerProcessCommands.WorkerProcessMessage<any>) => {
+            switch (e.data.command) {
+                case WorkerProcessCommands.Connect:
+                    this.handleHELO(port, e.data);
+                    break;
+                case WorkerProcessCommands.Disconnect:
+                    this.handleCLOSE(port, e.data);
+                    break;
+                case WorkerProcessCommands.GetCompletions:
+                    this.handleGetCompletions(port, e.data);
+                    break;
+                case WorkerProcessCommands.GetDocTooltip:
+                    this.handleGetDocTooltip(port, e.data);
+                    break;
+            }
+
+        }, false);
+
+        port.start();
+    }
+
+    /**
+     * Called when we want to reset the language service
+     * @return {[type]}
+     */
+    reset() {
+        this.languageService.reset();
+        this.projectLoaded = false;
+    }
+
+    /**
+     * Seed the language service with all of the relevant files in the project
+     * @return {Promise}
+     */
+    private loadProjectFiles() {
+        if (!this.projectLoaded) {
+            return getFileResource("resources/tsconfig.atomic").then((jsonTsConfig: string) => {
+                let promises: PromiseLike<void>[] = [];
+                let tsConfig: TSConfigFile = JSON.parse(jsonTsConfig);
+
+                if (tsConfig.compilerOptions) {
+                    this.languageService.compilerOptions = tsConfig.compilerOptions;
+                };
+
+                tsConfig.files.forEach((f) => {
+                    promises.push(getFileResource(f).then((code: string) => {
+                        this.languageService.addProjectFile(f, code);
+                    }));
+                });
+                return Promise.all(promises);
+            }).then(() => {
+                // Let's seed the compiler state
+                // this.languageService.compile([this.filename]);
+                this.projectLoaded = true;
+            });
+        } else {
+            // just return a resolved promise.. we are done
+            return Promise.resolve();
+        }
+    }
+
+    /**
+     * Handle initial tab connection
+     * @param  {MessagePort} port
+     * @param  {any} eventData
+     */
+    handleHELO(port: MessagePort, eventData: any | {
+        sender: string,
+        filename: string
+    }) {
+        port.postMessage({ command: WorkerProcessCommands.Message, message: "Hello " + eventData.sender + " (port #" + this.connections + ")" });
+        this.loadProjectFiles().then(() => {
+            this.languageService.compile([eventData.filename]);
+        });
+    }
+
+    /**
+     * Handle when a tab closes
+     * @param  {MessagePort} port
+     * @param  {any} eventData
+     */
+    handleCLOSE(port: MessagePort, eventData: any) {
+        this.connections--;
+        if (this.connections <= 0) {
+            this.reset();
+        }
+        console.log("Got a close");
+    }
+
+    /**
+     * Get completions
+     * @param  {MessagePort} port
+     * @param  {WorkerProcessCommands.GetCompletionsMessage} eventData
+     */
+    handleGetCompletions(port: MessagePort, eventData: WorkerProcessCommands.GetCompletionsMessageData) {
+        let sourceFile = this.languageService.updateProjectFile(eventData.filename, eventData.sourceText);
+
+        let newpos = this.languageService.getPositionOfLineAndCharacter(sourceFile, eventData.pos.row, eventData.pos.column);
+        let completions = this.languageService.getCompletions(eventData.filename, newpos);
+
+        let message: WorkerProcessCommands.GetCompletionsResponseMessageData = {
+            command: WorkerProcessCommands.CompletionResponse,
+            completions: []
+        };
+        let langService = this.languageService;
+
+        if (completions) {
+            message.completions = completions.entries.map((completion: ts.CompletionEntry) => {
+                let value = completion.name;
+                let completionItem: WorkerProcessCommands.WordCompletion = {
+                    caption: completion.name,
+                    value: value,
+                    score: 100 - parseInt(completion.sortText, 0),
+                    meta: completion.kind,
+                    pos: newpos
+                };
+
+                //completionItem.docTooltip = this.getDocTooltip(eventData.filename, newpos, completionItem);
+                return completionItem;
+            });
+        }
+
+        port.postMessage(message);
+    }
+
+    /**
+     * Get the documentation popup information for the current completion item
+     * @param  {MessagePort} port
+     * @param  {WorkerProcessCommands.GetDocTooltipMessageData} eventData
+     * @return {[type]}
+     */
+    handleGetDocTooltip(port: MessagePort, eventData: WorkerProcessCommands.GetDocTooltipMessageData) {
+        let message: WorkerProcessCommands.GetDocTooltipResponseMessageData = {
+            command: WorkerProcessCommands.DocTooltipResponse
+        };
+        const details = this.languageService.getCompletionEntryDetails(eventData.filename, eventData.pos, eventData.completionItem.caption);
+        if (details) {
+            let docs = details.displayParts.map(part => part.text).join("");
+            if (details.documentation) {
+                docs += "<br/" + details.documentation.map(part => part.text).join("");
+            }
+
+            message.docHTML = docs;
+        }
+
+        port.postMessage(message);
+    }
+}

+ 38 - 0
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/workerprocess/workerLoader.ts

@@ -0,0 +1,38 @@
+/**
+ * Typescript Language Worker - Handles bringing down the source, providing completions, and compiling.
+ * since this is a shared web worker, each editor tab will be sharing the same data.
+ */
+"use strict";
+
+// Bring in the system js library so we can import the modules we need.  The path here is based upon the location
+// of this SharedWorker in the directory tree
+importScripts("../../../../../systemjs/system.js");
+System.config({
+    defaultJSExtensions: true,
+    meta: {
+        "../../../../modules/typescript.js": {
+            format: "global",
+            exports: "ts"
+        }
+    }
+});
+
+// hook into the first time a tab connects...need to do some set up and some async pulling down of required modules
+function firstTimeConnect(e: any) {
+    // Going low-level to use the systemjs import system.  import will return a promise that will resolve
+    // to the module being required in.  Once this is loaded in and instantiated, everything downstream can just
+    // use normal typescript style imports to bring in other modules.
+    System.import("./TypescriptLanguageServiceWebWorker").then((TypescriptLanguageServiceWebWorker) => {
+        const worker = new TypescriptLanguageServiceWebWorker.default();
+
+        // remove the event listener for the first connect
+        self.removeEventListener("connect", firstTimeConnect);
+
+        // hook up the permanent event listener
+        self.addEventListener("connect", worker.connect.bind(worker));
+
+        // forward the connect call
+        worker.connect(e);
+    });
+}
+self.addEventListener("connect", firstTimeConnect);

+ 49 - 0
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/workerprocess/workerProcessCommands.ts

@@ -0,0 +1,49 @@
+export interface WorkerProcessMessage<T> extends MessageEvent {
+    data: T;
+}
+
+export interface WorkerProcessMessageData {
+    command: string;
+}
+
+export const GetCompletions = "COMPLETIONS";
+export const CompletionResponse = "COMPLETION_RESPONSE";
+export interface WordCompletion {
+    caption: string;
+    meta: string;
+    score: number;
+    value: string;
+    pos: number;
+    snippet?: string;
+    docHTML?: string;
+    docText?: string;
+}
+
+export interface GetCompletionsMessageData extends WorkerProcessMessageData {
+    sourceText: string;
+    filename: string;
+    pos: { row: number, column: number };
+    prefix: string;
+}
+
+export interface GetCompletionsResponseMessageData extends WorkerProcessMessageData {
+    completions: Array<WordCompletion>;
+}
+
+export const GetDocTooltip = "DOC_TOOLTIP";
+export const DocTooltipResponse = "DOC_TOOLTIP_RESPONSE";
+export interface GetDocTooltipMessageData extends WorkerProcessMessageData {
+    completionItem: WordCompletion;
+    filename: string;
+    pos: number;
+}
+
+export interface GetDocTooltipResponseMessageData extends WorkerProcessMessageData {
+    docText?: string;
+    docHTML?: string;
+}
+
+export const Connect = "HELO";
+export const Disconnect = "CLOSE";
+export const Message = "MESSAGE";
+export const Alert = "ALERT";

+ 21 - 0
Script/AtomicWebViewEditor/typings/systemjs.d.ts

@@ -0,0 +1,21 @@
+// Type definitions for System.js 0.18.4
+// Project: https://github.com/systemjs/systemjs
+// Definitions by: Ludovic HENIN <https://github.com/ludohenin/>, Nathan Walker <https://github.com/NathanWalker/>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+interface System {
+  import(name: string): any;
+  defined: any;
+  amdDefine: () => void;
+  amdRequire: () => void;
+  baseURL: string;
+  paths: { [key: string]: string };
+  meta: { [key: string]: Object };
+  config: any;
+}
+
+declare var System: System;
+
+declare module "systemjs" {
+  export = System;
+}

+ 29 - 0
Script/AtomicWebViewEditor/typings/webworkers.d.ts

@@ -0,0 +1,29 @@
+// Type definitions for SharedWorker
+// Project: http://www.w3.org/TR/workers/
+// Definitions by: Toshiya Nakakura <https://github.com/nakakura>
+// Definitions: https://github.com/borisyankov/DefinitelyTyped
+
+declare module SharedWorker {
+    interface AbstractWorker extends EventTarget {
+        onerror: (ev: ErrorEvent) => any;
+    }
+
+    export interface SharedWorker extends AbstractWorker {
+        /**
+         * the value it was assigned by the object's constructor.
+         * It represents the MessagePort for communicating with the shared worker.
+         * @type {MessagePort}
+         */
+        port: MessagePort;
+    }
+}
+
+declare var SharedWorker: {
+    prototype: SharedWorker.SharedWorker;
+    /***
+     *
+     * @param {string} stringUrl    Pathname to JavaScript file
+     * @param {string} name         Name of the worker to execute
+     */
+    new (stringUrl: string, name?: string): SharedWorker.SharedWorker;
+};

+ 0 - 2
Script/tslint.json

@@ -23,9 +23,7 @@
     "no-duplicate-variable": true,
     "no-empty": false,
     "no-eval": true,
-    "no-imports": true,
     "no-string-literal": false,
-    "no-trailing-comma": true,
     "no-trailing-whitespace": true,
     "no-unused-variable": false,
     "no-unreachable": true,