Переглянути джерело

massive cleanup and implementation of the typescript services in the web view.
- converted system js to no longer bundle files, but to leave them in place and require them in as needed
- implemented first steps of a extension framework for webview extensions
- moved javascript editor configuration into the javascriptLanguageExtension
- implemented basic type completion that also shows parameters of functions for typescript.
- created a d.ts for the interfaces that are used by the editor and the extension system

Shaddock Heath 9 роки тому
батько
коміт
b3a3ab207c
20 змінених файлів з 969 додано та 631 видалено
  1. 1 0
      Script/AtomicEditor/editor/EditorEvents.ts
  2. 0 15
      Script/AtomicEditor/extensionServices/ServiceLocator.ts
  3. 0 272
      Script/AtomicEditor/extensionServices/resourceServices/TypscriptLanguageExtension.ts
  4. 28 122
      Script/AtomicEditor/hostExtensions/HostExtensionServices.ts
  5. 72 0
      Script/AtomicEditor/hostExtensions/ServiceLocator.ts
  6. 162 0
      Script/AtomicEditor/hostExtensions/languageExtensions/TypscriptLanguageExtension.ts
  7. 0 106
      Script/AtomicEditor/modules/metrics.ts
  8. 0 32
      Script/AtomicEditor/modules/usertiming.d.ts
  9. 1 1
      Script/AtomicEditor/ui/frames/MainFrame.ts
  10. 89 0
      Script/AtomicWebViewEditor/clientExtensions/ClientExtensionServices.ts
  11. 74 0
      Script/AtomicWebViewEditor/clientExtensions/ServiceLocator.ts
  12. 45 0
      Script/AtomicWebViewEditor/clientExtensions/languageExtensions/javascript/JavascriptLanguageExtension.ts
  13. 281 0
      Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/TypescriptLanguageExtension.ts
  14. 139 55
      Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/TypescriptLanguageService.ts
  15. 40 12
      Script/AtomicWebViewEditor/editor/editorConfig.ts
  16. 0 0
      Script/AtomicWebViewEditor/modules/typescript.d.ts
  17. 18 3
      Script/AtomicWebViewEditor/tsconfig.json
  18. 0 5
      Script/AtomicWebViewEditor/typings/AtomicQuery.d.ts
  19. 8 0
      Script/AtomicWebViewEditor/typings/WindowExt.d.ts
  20. 11 8
      Script/tsconfig.json

+ 1 - 0
Script/AtomicEditor/editor/EditorEvents.ts

@@ -20,6 +20,7 @@
 // THE SOFTWARE.
 //
 
+// TODO: migrate these interfaces out to the d.ts and migrate the static strings to some common location
 export const ModalError = "ModalError";
 export interface ModalErrorEvent {
 

+ 0 - 15
Script/AtomicEditor/extensionServices/ServiceLocator.ts

@@ -1,15 +0,0 @@
-//
-// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
-// LICENSE: Atomic Game Engine Editor and Tools EULA
-// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
-// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
-//
-import {ServiceLocatorType} from "./EditorExtensionServices";
-import TypescriptLanguageExtension from "./resourceServices/TypscriptLanguageExtension";
-
-// Singleton service locator that can be referenced
-const serviceLocator = new ServiceLocatorType();
-export default serviceLocator;
-
-// Load up all the internal services
-serviceLocator.loadService(new TypescriptLanguageExtension());

+ 0 - 272
Script/AtomicEditor/extensionServices/resourceServices/TypscriptLanguageExtension.ts

@@ -1,272 +0,0 @@
-//
-// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
-// LICENSE: Atomic Game Engine Editor and Tools EULA
-// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
-// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
-//
-// 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 ExtensionServices from "../EditorExtensionServices";
-import * as EditorEvents from "../../editor/EditorEvents";
-import * as ts from "modules/typescript";
-import {TypescriptLanguageService, FileSystemInterface} from "./TypescriptLanguageService";
-
-import * as metrics from "modules/metrics";
-
-/**
- * Class that provides access to the Atomic filesystem routines
- */
-class AtomicFileSystem implements FileSystemInterface {
-    /**
-     * Deterimine if the particular file exists in the resources
-     * @param  {string} filename
-     * @return {boolean}
-     */
-    fileExists(filename: string): boolean {
-        return Atomic.fileSystem.exists(filename);
-    }
-
-    /**
-     * Grab the contents of the file
-     * @param  {string} filename
-     * @return {string}
-     */
-    getFile(filename: string): string {
-        let script = new Atomic.File(filename, Atomic.FILE_READ);
-        try {
-            return script.readText();
-        } finally {
-            script.close();
-        }
-    }
-
-    /**
-     * Write the contents to the file specified
-     * @param  {string} filename
-     * @param  {string} contents
-     */
-    writeFile(filename: string, contents: string) {
-        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 ToolCore.toolSystem.project.resourcePath;
-    }
-
-}
-
-/**
- * Resource extension that handles compiling or transpling typescript on file save.
- */
-export default class TypescriptLanguageExtension implements ExtensionServices.ResourceService, ExtensionServices.ProjectService {
-    name: string = "TypeScriptResourceService";
-    description: string = "This service transpiles TypeScript into JavaScript on save.";
-
-    /**
-     * 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;
-
-    /**
-     * Determines if the file name/path provided is something we care about
-     * @param  {string} path
-     * @return {boolean}
-     */
-    private isValidFiletype(path: string): boolean {
-        const ext = Atomic.getExtension(path);
-        if (ext == ".ts") {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Seed the language service with all of the relevant files in the project
-     */
-    private loadProjectFiles() {
-        // First we need to load in a copy of the lib.core.d.ts that is necessary for the hosted typescript compiler
-        this.languageService.addProjectFile(Atomic.addTrailingSlash(Atomic.addTrailingSlash(ToolCore.toolEnvironment.toolDataDir) + "TypeScriptSupport") + "lib.core.d.ts");
-
-        // Load up a copy of the duktape.d.ts
-        this.languageService.addProjectFile(Atomic.addTrailingSlash(Atomic.addTrailingSlash(ToolCore.toolEnvironment.toolDataDir) + "TypeScriptSupport") + "duktape.d.ts");
-
-        //scan all the files in the project
-        Atomic.fileSystem.scanDir(ToolCore.toolSystem.project.resourcePath, "*.ts", Atomic.SCAN_FILES, true).forEach(filename => {
-            this.languageService.addProjectFile(Atomic.addTrailingSlash(ToolCore.toolSystem.project.resourcePath) + filename);
-        });
-
-        // Look in a 'typings' directory for any typescript definition files
-        const typingsDir = Atomic.addTrailingSlash(ToolCore.toolSystem.project.projectPath) + "typings";
-        Atomic.fileSystem.scanDir(typingsDir, "*.d.ts", Atomic.SCAN_FILES, true).forEach(filename => {
-            this.languageService.addProjectFile(Atomic.addTrailingSlash(typingsDir) + filename);
-        });
-    }
-
-    /**
-     * Inject this language service into the registry
-     * @return {[type]}             True if successful
-     */
-    initialize(serviceRegistry: ExtensionServices.ServiceLocatorType) {
-        // initialize the language service
-        this.languageService = new TypescriptLanguageService(new AtomicFileSystem());
-
-        // We care about both resource events as well as project events
-        serviceRegistry.resourceServices.register(this);
-        serviceRegistry.projectServices.register(this);
-    }
-
-    /*** ResourceService implementation ****/
-    /**
-     * Can this service extension handle the save event for the resource?
-     * @param  {EditorEvents.SaveResourceEvent} ev the
-     * @return {boolean}                return true if this service can handle the resource
-     */
-    canSave(ev: EditorEvents.SaveResourceEvent): boolean {
-        return this.isValidFiletype(ev.path);
-    }
-
-    /**
-     * Called once a resource has been saved
-     * @param  {EditorEvents.SaveResourceEvent} ev
-     */
-    save(ev: EditorEvents.SaveResourceEvent) {
-        console.log(`${this.name}: received a save resource event for ${ev.path}`);
-        if (this.fullCompile) {
-            this.languageService.compile([ev.path], {
-                noEmitOnError: true,
-                noImplicitAny: false,
-                target: ts.ScriptTarget.ES5,
-                module: ts.ModuleKind.CommonJS,
-                noLib: true
-            });
-        } else {
-            this.languageService.transpile([ev.path], {
-                noEmitOnError: false,
-                noImplicitAny: false,
-                target: ts.ScriptTarget.ES5,
-                module: ts.ModuleKind.CommonJS,
-                noLib: true
-            });
-        }
-        metrics.logMetrics();
-    }
-
-    /**
-     * Determine if we care if an asset has been deleted
-     * @param  {EditorEvents.DeleteResourceEvent} ev
-     * @return {boolean} true if we care
-     */
-    canDelete(ev: EditorEvents.DeleteResourceEvent): boolean {
-        return this.isValidFiletype(ev.path);
-    }
-
-    /**
-     * Handle the delete.  This should delete the corresponding javascript file
-     * @param  {EditorEvents.DeleteResourceEvent} ev
-     */
-    delete(ev: EditorEvents.DeleteResourceEvent) {
-        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 the corresponding js file
-        let jsFile = ev.path.replace(/\.ts$/, ".js");
-        let jsFileAsset = ToolCore.assetDatabase.getAssetByPath(jsFile);
-        if (jsFileAsset) {
-            console.log(`${this.name}: deleting corresponding .js file`);
-            ToolCore.assetDatabase.deleteAsset(jsFileAsset);
-        }
-
-    }
-
-    /**
-     * Determine if we want to respond to resource renames
-     * @param  {EditorEvents.RenameResourceEvent} ev
-     * @return {boolean} true if we care
-     */
-    canRename(ev: EditorEvents.RenameResourceEvent): boolean {
-        return this.isValidFiletype(ev.path);
-    }
-
-    /**
-     * Handle the rename.  Should rename the corresponding .js file
-     * @param  {EditorEvents.RenameResourceEvent} ev
-     */
-    rename(ev: EditorEvents.RenameResourceEvent) {
-        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 the corresponding js file
-        let jsFile = ev.path.replace(/\.ts$/, ".js");
-        let jsFileNew = ev.newPath.replace(/\.ts$/, ".js");
-        let jsFileAsset = ToolCore.assetDatabase.getAssetByPath(jsFile);
-        if (jsFileAsset) {
-            console.log(`${this.name}: renaming corresponding .js file`);
-            jsFileAsset.rename(jsFileNew);
-        }
-    }
-
-    /*** ProjectService implementation ****/
-
-    /**
-     * Called when the project is being unloaded to allow the typscript language service to reset
-     */
-    projectUnloaded() {
-        // got an unload, we need to reset the language service
-        console.log(`${this.name}: received a project unloaded event`);
-        this.languageService.reset();
-    }
-
-    /**
-     * Called when the project is being loaded to allow the typscript language service to reset and
-     * possibly compile
-     */
-    projectLoaded(ev: EditorEvents.LoadProjectEvent) {
-        // got a load, we need to reset the language service
-        console.log(`${this.name}: received a project loaded event for project at ${ev.path}`);
-        this.languageService.reset();
-        this.loadProjectFiles();
-
-        //TODO: do we want to run through and compile at this point?
-    }
-
-
-    /**
-     * Called when the player is launched
-     */
-    playerStarted() {
-        console.log(`${this.name}: received a player started event for project`);
-
-        // Make sure the project has the latest files
-        this.loadProjectFiles();
-        if (this.fullCompile) {
-            this.languageService.compile(null, {
-                noEmitOnError: true,
-                noImplicitAny: false,
-                target: ts.ScriptTarget.ES5,
-                module: ts.ModuleKind.CommonJS,
-                noLib: true
-            });
-        }
-    }
-}

+ 28 - 122
Script/AtomicEditor/extensionServices/EditorExtensionServices.ts → Script/AtomicEditor/hostExtensions/HostExtensionServices.ts

@@ -8,55 +8,11 @@
 import * as EditorEvents from "../editor/EditorEvents";
 import * as EditorUI from "../ui/EditorUI";
 
-/**
- * Base interface for any editor services.
- */
-export interface EditorService {
-    /**
-     * Called by the service locator at load time
-     */
-    initialize(serviceLocator: ServiceLocatorType);
-
-    /**
-     * Unique name of this service
-     * @type {string}
-     */
-    name: string;
-
-    /**
-     * Description of this service
-     * @type {string}
-     */
-    description: string;
-}
-
-export interface ResourceService extends EditorService {
-    save?(ev: EditorEvents.SaveResourceEvent);
-    canSave?(ev: EditorEvents.SaveResourceEvent);
-    canDelete?(ev: EditorEvents.DeleteResourceEvent);
-    delete?(ev: EditorEvents.DeleteResourceEvent);
-    canRename?(ev: EditorEvents.RenameResourceEvent);
-    rename?(ev: EditorEvents.RenameResourceEvent);
-}
-
-export interface ProjectService extends EditorService {
-    projectUnloaded?();
-    projectLoaded?(ev: EditorEvents.LoadProjectEvent);
-    playerStarted?();
-}
-
-interface ServiceEventSubscriber {
-    /**
-     * Allow this service registry to subscribe to events that it is interested in
-     * @param  {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
-     */
-    subscribeToEvents(topLevelWindow: Atomic.UIWidget);
-}
 
 /**
  * Generic registry for storing Editor Extension Services
  */
-class ServiceRegistry<T extends EditorService> {
+class ServiceRegistry<T extends Editor.Extensions.EditorService> implements Editor.Extensions.ServiceRegistry<T> {
     registeredServices: T[] = [];
 
     /**
@@ -69,10 +25,18 @@ class ServiceRegistry<T extends EditorService> {
 
 }
 
+interface ServiceEventSubscriber {
+    /**
+     * Allow this service registry to subscribe to events that it is interested in
+     * @param  {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
+     */
+    subscribeToEvents(topLevelWindow: Atomic.UIWidget);
+}
+
 /**
  * Registry for service extensions that are concerned about project events
  */
-class ProjectServiceRegistry extends ServiceRegistry<ProjectService> implements ServiceEventSubscriber {
+export class ProjectServiceRegistry extends ServiceRegistry<Editor.HostExtensions.ProjectService> implements ServiceEventSubscriber {
     constructor() {
         super();
     }
@@ -81,10 +45,10 @@ class ProjectServiceRegistry extends ServiceRegistry<ProjectService> implements
      * Allow this service registry to subscribe to events that it is interested in
      * @param  {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
      */
-    subscribeToEvents(topLevelWindow: Atomic.UIWidget) {
-        topLevelWindow.subscribeToEvent(EditorEvents.LoadProjectNotification, (ev) => this.projectLoaded(ev));
-        topLevelWindow.subscribeToEvent(EditorEvents.CloseProject, (ev) => this.projectUnloaded(ev));
-        topLevelWindow.subscribeToEvent(EditorEvents.PlayerStartRequest, () => this.playerStarted());
+    subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
+        eventDispatcher.subscribeToEvent(EditorEvents.LoadProjectNotification, (ev) => this.projectLoaded(ev));
+        eventDispatcher.subscribeToEvent(EditorEvents.CloseProject, (ev) => this.projectUnloaded(ev));
+        eventDispatcher.subscribeToEvent(EditorEvents.PlayerStartRequest, () => this.playerStarted());
     }
 
     /**
@@ -108,7 +72,7 @@ class ProjectServiceRegistry extends ServiceRegistry<ProjectService> implements
      * Called when the project is loaded
      * @param  {[type]} data Event info from the project unloaded event
      */
-    projectLoaded(ev: EditorEvents.LoadProjectEvent) {
+    projectLoaded(ev: Editor.EditorEvents.LoadProjectEvent) {
         this.registeredServices.forEach((service) => {
             try {
                 // Notify services that the project has just been loaded
@@ -138,7 +102,7 @@ class ProjectServiceRegistry extends ServiceRegistry<ProjectService> implements
 /**
  * Registry for service extensions that are concerned about Resources
  */
-class ResourceServiceRegistry extends ServiceRegistry<ResourceService> {
+export class ResourceServiceRegistry extends ServiceRegistry<Editor.HostExtensions.ResourceService> {
     constructor() {
         super();
     }
@@ -147,22 +111,22 @@ class ResourceServiceRegistry extends ServiceRegistry<ResourceService> {
      * Allow this service registry to subscribe to events that it is interested in
      * @param  {Atomic.UIWidget} topLevelWindow The top level window that will be receiving these events
      */
-    subscribeToEvents(topLevelWindow: Atomic.UIWidget) {
-        topLevelWindow.subscribeToEvent(EditorEvents.SaveResourceNotification, (ev) => this.saveResource(ev));
-        topLevelWindow.subscribeToEvent(EditorEvents.DeleteResourceNotification, (ev) => this.deleteResource(ev));
-        topLevelWindow.subscribeToEvent(EditorEvents.RenameResourceNotification, (ev) => this.renameResource(ev));
+    subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
+        eventDispatcher.subscribeToEvent(EditorEvents.SaveResourceNotification, (ev) => this.saveResource(ev));
+        eventDispatcher.subscribeToEvent(EditorEvents.DeleteResourceNotification, (ev) => this.deleteResource(ev));
+        eventDispatcher.subscribeToEvent(EditorEvents.RenameResourceNotification, (ev) => this.renameResource(ev));
     }
 
     /**
      * Called after a resource has been saved
-     * @param  {EditorEvents.SaveResourceEvent} ev
+     * @param  {Editor.EditorEvents.SaveResourceEvent} ev
      */
-    saveResource(ev: EditorEvents.SaveResourceEvent) {
+    saveResource(ev: Editor.EditorEvents.SaveResourceEvent) {
         // run through and find any services that can handle this.
         this.registeredServices.forEach((service) => {
             try {
                 // Verify that the service contains the appropriate methods and that it can save
-                if (service.canSave && service.save && service.canSave(ev)) {
+                if (service.save) {
                     service.save(ev);
                 }
             } catch (e) {
@@ -173,13 +137,12 @@ class ResourceServiceRegistry extends ServiceRegistry<ResourceService> {
 
     /**
      * Called when a resource has been deleted
-     * @return {[type]} [description]
      */
-    deleteResource(ev: EditorEvents.DeleteResourceEvent) {
+    deleteResource(ev: Editor.EditorEvents.DeleteResourceEvent) {
         this.registeredServices.forEach((service) => {
             try {
                 // Verify that the service contains the appropriate methods and that it can delete
-                if (service.canDelete && service.delete && service.canDelete(ev)) {
+                if (service.delete) {
                     service.delete(ev);
                 }
             } catch (e) {
@@ -190,13 +153,13 @@ class ResourceServiceRegistry extends ServiceRegistry<ResourceService> {
 
     /**
      * Called when a resource has been renamed
-     * @param  {EditorEvents.RenameResourceEvent} ev
+     * @param  {Editor.EditorEvents.RenameResourceEvent} ev
      */
-    renameResource(ev: EditorEvents.RenameResourceEvent) {
+    renameResource(ev: Editor.EditorEvents.RenameResourceEvent) {
         this.registeredServices.forEach((service) => {
             try {
                 // Verify that the service contains the appropriate methods and that it can handle the rename
-                if (service.canRename && service.rename && service.canRename(ev)) {
+                if (service.rename) {
                     service.rename(ev);
                 }
             } catch (e) {
@@ -206,60 +169,3 @@ class ResourceServiceRegistry extends ServiceRegistry<ResourceService> {
     }
 
 }
-
-/**
- * Generic service locator of editor services that may be injected by either a plugin
- * or by the editor itself.
- */
-export class ServiceLocatorType {
-
-    constructor() {
-        this.resourceServices = new ResourceServiceRegistry();
-        this.projectServices = new ProjectServiceRegistry();
-    }
-
-    private eventDispatcher: Atomic.UIWidget = null;
-
-    resourceServices: ResourceServiceRegistry;
-    projectServices: ProjectServiceRegistry;
-
-    loadService(service: EditorService) {
-        try {
-            service.initialize(this);
-        } catch (e) {
-            EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}\n \n ${e.stack}`);
-        }
-    }
-
-    /**
-     * This is where the top level window will allow the service locator to listen for events and act on them.
-     * @param  {Atomic.UIWidget} frame
-     */
-    subscribeToEvents(frame: Atomic.UIWidget) {
-        this.eventDispatcher = frame;
-        this.resourceServices.subscribeToEvents(this.eventDispatcher);
-        this.projectServices.subscribeToEvents(this.eventDispatcher);
-    }
-
-    /**
-     * Send a custom event.  This can be used by services to publish custom events
-     * @param  {string} eventType
-     * @param  {any} data
-     */
-    sendEvent(eventType: string, data: any) {
-        if (this.eventDispatcher) {
-            this.eventDispatcher.sendEvent(eventType, data);
-        }
-    }
-
-    /**
-     * Subscribe to an event and provide a callback.  This can be used by services to subscribe to custom events
-     * @param  {string} eventType
-     * @param  {any} callback
-     */
-    subscribeToEvent(eventType: string, callback: (data: any) => void) {
-        if (this.eventDispatcher) {
-            this.eventDispatcher.subscribeToEvent(eventType, callback);
-        }
-    }
-}

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

@@ -0,0 +1,72 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+import * as HostExtensionServices from "./HostExtensionServices";
+import * as EditorUI from "../ui/EditorUI";
+import TypescriptLanguageExtension from "./languageExtensions/TypscriptLanguageExtension";
+
+/**
+ * Generic service locator of editor services that may be injected by either a plugin
+ * or by the editor itself.
+ */
+export class ServiceLocatorType implements Editor.HostExtensions.HostServiceLocator {
+
+    constructor() {
+        this.resourceServices = new HostExtensionServices.ResourceServiceRegistry();
+        this.projectServices = new HostExtensionServices.ProjectServiceRegistry();
+    }
+
+    private eventDispatcher: Atomic.UIWidget = null;
+
+    resourceServices: HostExtensionServices.ResourceServiceRegistry;
+    projectServices: HostExtensionServices.ProjectServiceRegistry;
+
+    loadService(service: Editor.HostExtensions.HostEditorService) {
+        try {
+            service.initialize(this);
+        } catch (e) {
+            EditorUI.showModalError("Extension Error", `Error detected in extension ${service.name}\n \n ${e.stack}`);
+        }
+    }
+
+    /**
+     * This is where the top level window will allow the service locator to listen for events and act on them.
+     * @param  {Atomic.UIWidget} frame
+     */
+    subscribeToEvents(frame: Atomic.UIWidget) {
+        this.eventDispatcher = frame;
+        this.resourceServices.subscribeToEvents(this);
+        this.projectServices.subscribeToEvents(this);
+    }
+
+    /**
+     * Send a custom event.  This can be used by services to publish custom events
+     * @param  {string} eventType
+     * @param  {any} data
+     */
+    sendEvent(eventType: string, data: any) {
+        if (this.eventDispatcher) {
+            this.eventDispatcher.sendEvent(eventType, data);
+        }
+    }
+
+    /**
+     * Subscribe to an event and provide a callback.  This can be used by services to subscribe to custom events
+     * @param  {string} eventType
+     * @param  {any} callback
+     */
+    subscribeToEvent(eventType: string, callback: (data: any) => void) {
+        if (this.eventDispatcher) {
+            this.eventDispatcher.subscribeToEvent(eventType, callback);
+        }
+    }
+}
+// Singleton service locator that can be referenced
+const serviceLocator = new ServiceLocatorType();
+export default serviceLocator;
+
+// Load up all the internal services
+serviceLocator.loadService(new TypescriptLanguageExtension());

+ 162 - 0
Script/AtomicEditor/hostExtensions/languageExtensions/TypscriptLanguageExtension.ts

@@ -0,0 +1,162 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+
+/**
+ * Resource extension that supports the web view typescript extension
+ */
+export default class TypescriptLanguageExtension implements Editor.HostExtensions.ResourceService, Editor.HostExtensions.ProjectService {
+    name: string = "HostTypeScriptLanguageExtension";
+    description: string = "This service supports the typscript webview extension.";
+
+    /**
+     * Indicates if this project contains typescript files.
+     * @type {Boolean}
+     */
+    private isTypescriptProject = false;
+
+    /**
+     * Determines if the file name/path provided is something we care about
+     * @param  {string} path
+     * @return {boolean}
+     */
+    private isValidFiletype(path: string): boolean {
+        if (this.isTypescriptProject) {
+            const ext = Atomic.getExtension(path);
+            if (ext == ".ts") {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Seed the language service with all of the relevant files in the project.  This updates the tsconifg.atomic file in
+     * the root of the resources directory.
+     */
+    private loadProjectFiles() {
+        let projectFiles: Array<string> = [];
+
+        //scan all the files in the project for any typescript files so we can determine if this is a typescript project
+        Atomic.fileSystem.scanDir(ToolCore.toolSystem.project.resourcePath, "*.ts", Atomic.SCAN_FILES, true).forEach(filename => {
+            projectFiles.push(Atomic.addTrailingSlash(ToolCore.toolSystem.project.resourcePath) + filename);
+            this.isTypescriptProject = true;
+        });
+
+        // only build out a tsconfig.atomic if we actually have typescript files in the project
+        if (this.isTypescriptProject) {
+            // First we need to load in a copy of the lib.core.d.ts that is necessary for the hosted typescript compiler
+            projectFiles.push(Atomic.addTrailingSlash(Atomic.addTrailingSlash(ToolCore.toolEnvironment.toolDataDir) + "TypeScriptSupport") + "lib.core.d.ts");
+
+            // Load up a copy of the duktape.d.ts
+            projectFiles.push(Atomic.addTrailingSlash(Atomic.addTrailingSlash(ToolCore.toolEnvironment.toolDataDir) + "TypeScriptSupport") + "duktape.d.ts");
+
+
+            // Look in a 'typings' directory for any typescript definition files
+            const typingsDir = Atomic.addTrailingSlash(ToolCore.toolSystem.project.projectPath) + "typings";
+            Atomic.fileSystem.scanDir(typingsDir, "*.d.ts", Atomic.SCAN_FILES, true).forEach(filename => {
+                projectFiles.push(Atomic.addTrailingSlash(typingsDir) + filename);
+            });
+
+            let files = projectFiles.map((f: string) => {
+                if (f.indexOf(ToolCore.toolSystem.project.resourcePath) != -1) {
+                    // if we are in the resources directory, just pass back the path from resources down
+                    return f.replace(Atomic.addTrailingSlash(ToolCore.toolSystem.project.projectPath), "");
+                } else {
+                    // otherwise return the full path
+                    return f;
+                }
+            });
+
+            let tsConfig = {
+                files: files
+            };
+
+            let filename = Atomic.addTrailingSlash(ToolCore.toolSystem.project.resourcePath) + "tsconfig.atomic";
+            let script = new Atomic.File(filename, Atomic.FILE_WRITE);
+            try {
+                script.writeString(JSON.stringify(tsConfig));
+                script.flush();
+            } finally {
+                script.close();
+            }
+        }
+    }
+
+    /**
+     * Inject this language service into the registry
+     * @return {[type]}             True if successful
+     */
+    initialize(serviceRegistry: Editor.HostExtensions.HostServiceLocator) {
+        // We care about both resource events as well as project events
+        serviceRegistry.resourceServices.register(this);
+        serviceRegistry.projectServices.register(this);
+    }
+
+    /**
+     * 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`);
+
+            // Delete the corresponding js file
+            let jsFile = ev.path.replace(/\.ts$/, ".js");
+            let jsFileAsset = ToolCore.assetDatabase.getAssetByPath(jsFile);
+            if (jsFileAsset) {
+                console.log(`${this.name}: deleting corresponding .js file`);
+                ToolCore.assetDatabase.deleteAsset(jsFileAsset);
+            }
+        }
+    }
+
+    /**
+     * 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`);
+
+            // Rename the corresponding js file
+            let jsFile = ev.path.replace(/\.ts$/, ".js");
+            let jsFileNew = ev.newPath.replace(/\.ts$/, ".js");
+            let jsFileAsset = ToolCore.assetDatabase.getAssetByPath(jsFile);
+            if (jsFileAsset) {
+                console.log(`${this.name}: renaming corresponding .js file`);
+                jsFileAsset.rename(jsFileNew);
+            }
+        }
+    }
+
+    /**
+     * Handles the save event and detects if a typescript file has been added to a non-typescript project
+     * @param  {Editor.EditorEvents.SaveResourceEvent} ev
+     * @return {[type]}
+     */
+    save(ev: Editor.EditorEvents.SaveResourceEvent) {
+        // let's check to see if we have created a typescript file
+        if (!this.isTypescriptProject) {
+            if (Atomic.getExtension(ev.path) == ".ts") {
+                this.isTypescriptProject = true;
+                this.loadProjectFiles();
+            }
+        }
+    }
+
+    /*** ProjectService implementation ****/
+
+    /**
+     * Called when the project is being loaded to allow the typscript language service to reset and
+     * possibly compile
+     */
+    projectLoaded(ev: Editor.EditorEvents.LoadProjectEvent) {
+        // got a load, we need to reset the language service
+        console.log(`${this.name}: received a project loaded event for project at ${ev.path}`);
+        this.loadProjectFiles();
+    }
+}

+ 0 - 106
Script/AtomicEditor/modules/metrics.ts

@@ -1,106 +0,0 @@
-//
-// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
-// LICENSE: Atomic Game Engine Editor and Tools EULA
-// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
-// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
-//
-// Wraps the performance API / performance API polyfill
-import performance = require("modules/usertiming");
-
-const METRICS_ENABLED = true;
-
-/**
- * Decorator to wrap a function with enter/exit profile
- * @param  {Object} target
- * @param  {string} propertyKey
- * @param  {TypedPropertyDescriptor<any>} descriptor
- * @return {TypedPropertyDesciptor<any>}
- */
-export function profileDecorator(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
-    const originalMethod = descriptor.value;
-
-    descriptor.value = function(...args: any[]) {
-        let result;
-        start(propertyKey);
-        try {
-            result = originalMethod.apply(this, args);
-        } finally {
-            stop(propertyKey);
-            return result;
-        }
-    };
-
-    return descriptor;
-}
-
-/**
- * add a start mark to the profiler
- * @param  {string} key
- */
-export function start(key: string) {
-    if (METRICS_ENABLED) {
-        performance.mark(key + "_start");
-    }
-}
-
-/**
- * Add a stop mark to the profiler and capture a measurement
- * @param  {string} key
- */
-export function stop(key: string) {
-    if (METRICS_ENABLED) {
-        performance.mark(key + "_stop");
-        performance.measure(key, key + "_start", key + "_stop");
-    }
-
-}
-
-export interface MetricRollup {
-    key: string;
-    timesCalled: number;
-    avgDuration: number;
-    totalDuration: number;
-}
-
-/**
- * Returns an array of metric rollups
- * @return {[MetricRollup]}
- */
-export function getMetricRollups(): MetricRollup[] {
-    if (METRICS_ENABLED) {
-        const metrics = {};
-
-        performance.getEntriesByType("measure").forEach((entry) => {
-            if (!metrics[entry.name]) {
-                metrics[entry.name] = [];
-            }
-            metrics[entry.name].push(entry);
-        });
-        const vals = [];
-        for (let mname in metrics) {
-            let totalduration = 0;
-            let metricBlock = metrics[mname];
-            for (let i = 0; i < metricBlock.length; i++) {
-                totalduration += metricBlock[i].duration;
-            }
-            vals.push({
-                key: mname,
-                timesCalled: metricBlock.length,
-                avgDuration: totalduration / metricBlock.length,
-                totalDuration: totalduration
-            });
-        }
-        return vals;
-    } else {
-        return [];
-    }
-}
-
-/**
- * Write metrics to the console
- */
-export function logMetrics() {
-    getMetricRollups().forEach((metric) => {
-        console.log(`${metric.key}  Called: ${metric.timesCalled}, Average Duration:${metric.avgDuration}, Total Duration: ${metric.totalDuration}`);
-    });
-}

+ 0 - 32
Script/AtomicEditor/modules/usertiming.d.ts

@@ -1,32 +0,0 @@
-declare interface PerformanceEntryFilterOptions {
-    // the name of a performance entry.
-    name?: string;
-    // the entry type. The valid entry types are listed in the PerformanceEntry.entryType method.
-    entryType?: string;
-
-    // the type of the initiating resource (for example an HTML element). The values are defined by the
-    initiatorType?: string;
-}
-
-declare interface PerformanceEntry {
-    // A DOMString representing the name of a performance entry when the metric was created.
-    name: string;
-    // A DOMString representing the type of performance metric such as "mark". See entryType for a list of valid values.
-    entryType: string;
-    // A DOMHighResTimeStamp representing the starting time for the performance metric.
-    startTime: Date;
-    // A DOMHighResTimeStamp representing the time value of the duration of the performance event.
-    duration: Number;
-}
-
-declare interface Performance {
-    mark(name: string);
-    measure(name: string, startMark: string, endMark: string);
-    getEntries();
-    getEntries(performanceEntryFilterOptions: PerformanceEntryFilterOptions): PerformanceEntry[];
-    getEntriesByType(entryType:string);
-}
-
-
-declare const performance: Performance;
-export = performance;

+ 1 - 1
Script/AtomicEditor/ui/frames/MainFrame.ts

@@ -33,7 +33,7 @@ import ScriptWidget = require("ui/ScriptWidget");
 import MainFrameMenu = require("./menus/MainFrameMenu");
 
 import MenuItemSources = require("./menus/MenuItemSources");
-import ServiceLocator from "../../extensionServices/ServiceLocator";
+import ServiceLocator from "../../hostExtensions/ServiceLocator";
 
 class MainFrame extends ScriptWidget {
 

+ 89 - 0
Script/AtomicWebViewEditor/clientExtensions/ClientExtensionServices.ts

@@ -0,0 +1,89 @@
+// Entry point for web view extensions -- extensions that live inside the web view
+
+interface EventSubscription {
+    eventName: string;
+    callback: (data: any) => any;
+}
+
+/**
+ * Implements an event dispatcher for the client services
+ */
+export class EventDispatcher implements Editor.Extensions.EventDispatcher {
+    private subscriptions: EventSubscription[] = [];
+
+    sendEvent(eventType: string, data: any) {
+        this.subscriptions.forEach(sub => {
+            if (sub.eventName == eventType) {
+                sub.callback(data);
+            }
+        });
+    }
+
+    subscribeToEvent(eventType, callback) {
+        this.subscriptions.push({
+            eventName: eventType,
+            callback: callback
+        });
+    }
+}
+
+/**
+ * Generic registry for storing Editor Extension Services
+ */
+class ServiceRegistry<T extends Editor.Extensions.EditorService> implements Editor.Extensions.ServiceRegistry<T> {
+    registeredServices: T[] = [];
+
+    /**
+     * Adds a service to the registered services list for this type of service
+     * @param  {T}      service the service to register
+     */
+    register(service: T) {
+        this.registeredServices.push(service);
+    }
+}
+
+export class ExtensionServiceRegistry extends ServiceRegistry<Editor.ClientExtensions.WebViewService> {
+
+    /**
+     * Allow this service registry to subscribe to events that it is interested in
+     * @param  {EventDispatcher} eventDispatcher The global event dispatcher
+     */
+    subscribeToEvents(eventDispatcher: Editor.Extensions.EventDispatcher) {
+        eventDispatcher.subscribeToEvent("CodeLoadedNotification", (ev) => this.codeLoaded(ev));
+        eventDispatcher.subscribeToEvent("ConfigureEditor", (ev) => this.configureEditor(ev));
+    }
+
+    /**
+     * Called when code is loaded
+     * @param  {Editor.EditorEvents.CodeLoadedEvent} ev Event info about the file that is being loaded
+     */
+    codeLoaded(ev: Editor.EditorEvents.CodeLoadedEvent) {
+        this.registeredServices.forEach((service) => {
+            try {
+                // Notify services that the project has just been loaded
+                if (service.codeLoaded) {
+                    service.codeLoaded(ev);
+                }
+            } catch (e) {
+                alert(`Extension Error:\n Error detected in extension ${service.name}\n \n ${e.stack}`);
+            }
+        });
+    }
+
+    /**
+     * Called when the editor is requesting to be configured for a particular file
+     * @param  {Editor.EditorEvents.EditorFileEvent} ev
+     */
+    configureEditor(ev: Editor.EditorEvents.EditorFileEvent) {
+        this.registeredServices.forEach((service) => {
+            try {
+                // Notify services that the project has just been loaded
+                if (service.configureEditor) {
+                    service.configureEditor(ev);
+                }
+            } catch (e) {
+                alert(`Extension Error:\n Error detected in extension ${service.name}\n \n ${e.stack}`);
+            }
+        });
+    }
+}

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

@@ -0,0 +1,74 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+import HostInteropType from "../interop";
+import * as ClientExtensionServices from "./ClientExtensionServices";
+
+// Initialize and configure the extensions
+import tsExtension from "./languageExtensions/typescript/TypescriptLanguageExtension";
+import jsExtension from "./languageExtensions/javascript/JavascriptLanguageExtension";
+
+/**
+ * Generic service locator of editor services that may be injected by either a plugin
+ * or by the editor itself.
+ */
+export class ClientServiceLocatorType implements Editor.ClientExtensions.ClientServiceLocator {
+
+    constructor() {
+        this.services = new ClientExtensionServices.ExtensionServiceRegistry();
+        this.services.subscribeToEvents(this);
+    }
+
+    private services: ClientExtensionServices.ExtensionServiceRegistry;
+    private eventDispatcher: Editor.Extensions.EventDispatcher = new ClientExtensionServices.EventDispatcher();
+
+    /**
+     * Returns the Host Interop module
+     * @return {Editor.ClientExtensions.HostInterop}
+     */
+    getHostInterop(): Editor.ClientExtensions.HostInterop {
+        return HostInteropType.getInstance();
+    }
+
+    loadService(service: Editor.ClientExtensions.ClientEditorService) {
+        try {
+            this.services.register(service);
+            service.initialize(this);
+        } catch (e) {
+            alert(`Extension Error:\n Error detected in extension ${service.name}\n \n ${e.stack}`);
+        }
+    }
+
+    /**
+     * Send a custom event.  This can be used by services to publish custom events
+     * @param  {string} eventType
+     * @param  {any} data
+     */
+    sendEvent(eventType: string, data: any) {
+        if (this.eventDispatcher) {
+            this.eventDispatcher.sendEvent(eventType, data);
+        }
+    }
+
+    /**
+     * Subscribe to an event and provide a callback.  This can be used by services to subscribe to custom events
+     * @param  {string} eventType
+     * @param  {any} callback
+     */
+    subscribeToEvent(eventType: string, callback: (data: any) => void) {
+        if (this.eventDispatcher) {
+            this.eventDispatcher.subscribeToEvent(eventType, callback);
+        }
+    }
+}
+
+// Singleton service locator that can be referenced
+const serviceLocator = new ClientServiceLocatorType();
+export default serviceLocator;
+
+// Load up all the internal services
+serviceLocator.loadService(new tsExtension());
+serviceLocator.loadService(new jsExtension());

+ 45 - 0
Script/AtomicWebViewEditor/clientExtensions/languageExtensions/javascript/JavascriptLanguageExtension.ts

@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+
+/**
+ * Resource extension that handles configuring the editor for Javascript
+ */
+export default class JavascriptLanguageExtension implements Editor.ClientExtensions.WebViewService {
+    name: string = "ClientJavascriptLanguageExtension";
+    description: string = "Javascript language services for the editor.";
+
+    private serviceLocator: Editor.ClientExtensions.ClientServiceLocator;
+
+    /**
+    * Initialize the language service
+     * @param  {Editor.ClientExtensions.ClientServiceLocator} serviceLocator
+     */
+    initialize(serviceLocator: Editor.ClientExtensions.ClientServiceLocator) {
+        // initialize the language service
+        this.serviceLocator = serviceLocator;
+    }
+
+    /**
+     * Determines if the file name/path provided is something we care about
+     * @param  {string} path
+     * @return {boolean}
+     */
+    private isValidFiletype(path: string): boolean {
+        let ext = path.split(".").pop();
+        return ext == "ts";
+    }
+
+    /**
+     * Called when the editor needs to be configured for a particular file
+     * @param  {Editor.EditorEvents.EditorFileEvent} ev
+     */
+    configureEditor(ev: Editor.EditorEvents.EditorFileEvent) {
+        if (this.isValidFiletype(ev.filename)) {
+            let editor = <AceAjax.Editor>ev.editor;
+            editor.session.setMode("ace/mode/javascript");
+        }
+    }
+}

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

@@ -0,0 +1,281 @@
+//
+// Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
+// LICENSE: Atomic Game Engine Editor and Tools EULA
+// Please see LICENSE_ATOMIC_EDITOR_AND_TOOLS.md in repository root for
+// license information: https://github.com/AtomicGameEngine/AtomicGameEngine
+//
+// 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 "";
+    }
+
+}
+
+/**
+ * Resource extension that handles compiling or transpling typescript on file save.
+ */
+export default class TypescriptLanguageExtension implements Editor.ClientExtensions.WebViewService {
+    name: string = "ClientTypescriptLanguageExtension";
+    description: string = "This extension handles typescript language features such as completion, compilation, etc.";
+
+    /**
+     * current filename
+     * @type {string}
+     */
+    private filename: string;
+
+    private serviceLocator: Editor.ClientExtensions.ClientServiceLocator;
+    /**
+     * 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
+     */
+    initialize(serviceLocator: Editor.ClientExtensions.ClientServiceLocator) {
+        // initialize the language service
+        this.serviceLocator = serviceLocator;
+        this.fs = new WebFileSystem();
+        this.languageService = new TypescriptLanguageService(this.fs);
+    }
+
+    /**
+     * Determines if the file name/path provided is something we care about
+     * @param  {string} path
+     * @return {boolean}
+     */
+    private isValidFiletype(path: string): boolean {
+        let ext = path.split(".").pop();
+        return ext == "ts";
+    }
+
+
+    /**
+     * Seed the language service with all of the relevant files in the project
+     */
+    private loadProjectFiles() {
+        this.serviceLocator.getHostInterop().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(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]);
+        });
+    }
+
+    /**
+     * Called when the editor needs to be configured for a particular file
+     * @param  {Editor.EditorEvents.EditorFileEvent} ev
+     */
+    configureEditor(ev: Editor.EditorEvents.EditorFileEvent) {
+        if (this.isValidFiletype(ev.filename)) {
+            let editor = <AceAjax.Editor>ev.editor;
+            editor.session.setMode("ace/mode/typescript");
+        }
+    }
+
+    /**
+     * Called when code is first loaded into the editor
+     * @param  {CodeLoadedEvent} ev
+     * @return {[type]}
+     */
+    codeLoaded(ev: Editor.EditorEvents.CodeLoadedEvent) {
+        if (this.isValidFiletype(ev.filename)) {
+            this.filename = ev.filename;
+
+            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)];
+
+            // 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();
+        }
+    }
+
+    /**
+     * 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
+     * @param  {TypescriptLanguageService} langService The language service that handles compilation, completion resolution, etc.
+     * @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 },
+        getCompletions: (editor, session, pos, prefix, callback) => void
+    } {
+        //let langTools = ace.require("ace/ext/language_tools");
+        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;
+                }
+            },
+
+            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);
+                }
+            }
+        };
+        return wordCompleter;
+    }
+
+    /*** ResourceService implementation ****/
+
+    /**
+     * 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]);
+            }
+        }
+    }
+
+    /**
+     * 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);
+        }
+    }
+
+    /**
+     * 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);
+        }
+    }
+}

+ 139 - 55
Script/AtomicEditor/extensionServices/resourceServices/TypescriptLanguageService.ts → Script/AtomicWebViewEditor/clientExtensions/languageExtensions/typescript/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
@@ -45,10 +45,23 @@ export class TypescriptLanguageService {
 
     constructor(fs: FileSystemInterface) {
         this.fs = fs;
+
+        // Create the language service files
+        this.documentRegistry = ts.createDocumentRegistry();
+        this.createLanguageService(this.documentRegistry);
     }
 
     private fs: FileSystemInterface = null;
     private languageService: ts.LanguageService = null;
+    private documentRegistry: ts.DocumentRegistry = null;
+
+    public compilerOptions: ts.CompilerOptions = {
+        noEmitOnError: true,
+        noImplicitAny: false,
+        target: ts.ScriptTarget.ES5,
+        module: ts.ModuleKind.CommonJS,
+        noLib: true
+    };
 
     name: string = "TypescriptLanguageService";
 
@@ -61,24 +74,98 @@ export class TypescriptLanguageService {
     private projectFiles: string[] = [];
     private versionMap: ts.Map<{ version: number, snapshot?: ts.IScriptSnapshot }> = {};
 
+    private createLanguageService(documentRegistry: ts.DocumentRegistry) {
+
+        // Create the language service host to allow the LS to communicate with the host
+        const servicesHost: ts.LanguageServiceHost = {
+            getScriptFileNames: () => this.projectFiles,
+            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(),
+            getCompilationSettings: () => this.compilerOptions,
+            getDefaultLibFileName: (options) => undefined
+        };
+
+        this.languageService = ts.createLanguageService(servicesHost, documentRegistry);
+    }
+
     /**
      * Adds a file to the internal project cache
-     * @param  {string} file the full path of the file to add
+     * @param  {string} filename the full path of the file to add
+     * @param [{sring}] fileContents optional file contents.  If not provided, the filesystem object will be queried
      */
-    addProjectFile(file: string) {
-        if (this.projectFiles.indexOf(file) == -1) {
-            console.log("ADDED: " + file);
-            this.versionMap[file] = { version: 0 };
-            this.projectFiles.push(file);
+    addProjectFile(filename: string, fileContents?: string) {
+        if (this.projectFiles.indexOf(filename) == -1) {
+            console.log("Added project file: " + filename);
+            this.versionMap[filename] = {
+                version: 0,
+                snapshot: ts.ScriptSnapshot.fromString(fileContents || this.fs.getFile(filename))
+            };
+            this.projectFiles.push(filename);
+            this.documentRegistry.acquireDocument(
+                filename,
+                this.compilerOptions,
+                this.versionMap[filename].snapshot,
+                "0");
         }
     }
 
+    /**
+     * Updates the internal file representation.
+     * @param  {string} filename name of the file
+     * @param  {string} fileContents optional contents of the file.  If not provided, the file system object will be queried
+     * @return {ts.SourceFile}
+     */
+    updateProjectFile(filename: string, fileContents?: string): ts.SourceFile {
+        console.log("Updated project file: " + filename);
+        this.versionMap[filename].version++;
+        this.versionMap[filename].snapshot = ts.ScriptSnapshot.fromString(fileContents || this.fs.getFile(filename));
+        return this.documentRegistry.updateDocument(
+            filename,
+            this.compilerOptions,
+            this.versionMap[filename].snapshot,
+            this.versionMap[filename].version.toString());
+    }
+
     /**
      * Simply transpile the typescript file.  This is much faster and only checks for syntax errors
      * @param {string[]}           fileNames array of files to transpile
      * @param {ts.CompilerOptions} options   compiler options
      */
-    transpile(fileNames: string[], options: ts.CompilerOptions): void {
+    transpile(fileNames: string[], options?: ts.CompilerOptions): void {
+        options = options || this.compilerOptions;
+        this.compilerOptions = options;
+
         fileNames.forEach((fileName) => {
             console.log(`${this.name}:  Transpiling ${fileName}`);
             let script = this.fs.getFile(fileName);
@@ -87,21 +174,53 @@ export class TypescriptLanguageService {
             if (diagnostics.length) {
                 this.logErrors(diagnostics);
             }
-
             if (diagnostics.length == 0) {
                 this.fs.writeFile(fileName.replace(".ts", ".js"), result);
             }
         });
     }
 
+    /**
+     * Converts the line and character offset to a position
+     * @param  {ts.SourceFile} file
+     * @param  {number} line
+     * @param  {number} character
+     * @return {number}
+     */
+    getPositionOfLineAndCharacter(file: ts.SourceFile, line: number, character: number): number {
+        return ts.getPositionOfLineAndCharacter(file, line, character);
+    }
+
+    /**
+     * Returns the completions available at the provided position
+     * @param  {string} filename
+     * @param  {number} position
+     * @return {ts.CompletionInfo}
+     */
+    getCompletions(filename: string, pos: number): ts.CompletionInfo {
+        return this.languageService.getCompletionsAtPosition(filename, pos);
+    }
+
+    /**
+     * Returns details of this completion entry
+     * @param  {string} filename
+     * @param  {number} pos
+     * @param  {string} entryname
+     * @return {ts.CompletionEntryDetails}
+     */
+    getCompletionEntryDetails(filename: string, pos: number, entryname: string): ts.CompletionEntryDetails {
+        return this.languageService.getCompletionEntryDetails(filename, pos, entryname);
+    }
     /**
      * Compile the provided file to javascript with full type checking etc
      * @param  {string}  a list of file names to compile
      * @param  {ts.CompilerOptions} options for the compiler
      */
-    compile(files: string[], options: ts.CompilerOptions): void {
+    compile(files: string[], options?: ts.CompilerOptions): void {
         let start = new Date().getTime();
 
+        options = options || this.compilerOptions;
+
         //Make sure we have these files in the project
         files.forEach((file) => {
             this.addProjectFile(file);
@@ -109,49 +228,8 @@ export class TypescriptLanguageService {
 
         let errors: ts.Diagnostic[] = [];
 
-        if (!this.languageService) {
-            // This is the first time in.  Need to create a language service
-
-            // Create the language service host to allow the LS to communicate with the host
-            const servicesHost: ts.LanguageServiceHost = {
-                getScriptFileNames: () => this.projectFiles,
-                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);
-                            }
-                        }
-                        return undefined;
-                    }
-
-                    // Grab the cached version
-                    if (scriptVersion) {
-                        if (scriptVersion.snapshot) {
-                            console.log(`cache hit snapshot for ${fileName}`);
-                            return scriptVersion.snapshot;
-                        } else {
-                            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(),
-                getCompilationSettings: () => options,
-                getDefaultLibFileName: (options) => undefined
-            };
-
-            // Create the language service files
-            this.languageService = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
-
-            // This is the first time in, let's compile all the files
+        if (files.length == 0) {
+            // We haven't passed any files in, so let's compile them all
             this.projectFiles.forEach(filename => {
                 errors = errors.concat(this.compileFile(filename));
             });
@@ -209,7 +287,7 @@ export class TypescriptLanguageService {
     reset() {
         this.projectFiles = [];
         this.versionMap = {};
-        this.languageService = null;
+        //this.languageService = null;
     }
 
     /**
@@ -262,7 +340,13 @@ export class TypescriptLanguageService {
         diagnostics.forEach(diagnostic => {
             let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
             if (diagnostic.file) {
-                let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
+                let d = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
+                let line = d.line;
+                let character = d.character;
+
+                // NOTE: Destructuring throws an error in chrome web view
+                //let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
+
                 msg.push(`${this.name}:  Error ${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`);
             }
             else {

+ 40 - 12
Script/AtomicWebViewEditor/editor/editorConfig.ts

@@ -1,20 +1,48 @@
 import editor from "./editor";
+import serviceLocator from "../clientExtensions/ServiceLocator";
+import HostInterop from "../interop";
 
+const CONFIGURE_EDITOR_EVENT = "ConfigureEditor";
+const CODE_LOADED_NOTIFICATION = "CodeLoadedNotification";
 /**
  * Set the editor theme and configuration based upon the file extension
  * @param  {string} fileExt
  */
-export function configure(fileExt: string) {
+export function configure(fileExt: string, filename: string) {
+
+    // set a default theme
     editor.setTheme("ace/theme/monokai");
-    switch (fileExt.toLowerCase()) {
-        case "ts":
-            editor.session.setMode("ace/mode/typescript");
-            break;
-        case "js":
-            editor.session.setMode("ace/mode/javascript");
-            break;
-        default:
-            editor.session.setMode("ace/mode/javascript");
-            break;
-    }
+
+    // set a default mode
+    editor.session.setMode("ace/mode/javascript");
+
+    // give the language extensions the opportunity to configure the editor based upon the file type
+    serviceLocator.sendEvent(CONFIGURE_EDITOR_EVENT, {
+        fileExt: fileExt,
+        filename: filename,
+        editor: editor
+    });
+}
+
+/**
+ * Loads a file of code into the editor and wires up the change events
+ * @param  {string} code
+ * @param {string} filename
+ * @param  {string} fileExt
+ */
+export function loadCodeIntoEditor(code: string, filename: string, fileExt: string) {
+    editor.session.setValue(code);
+    editor.gotoLine(0);
+
+    editor.getSession().on("change", function(e) {
+        HostInterop.getInstance().notifyEditorChange();
+    });
+
+    serviceLocator.sendEvent(CODE_LOADED_NOTIFICATION, {
+        code: code,
+        filename: filename,
+        fileExt: fileExt,
+        editor: editor
+    });
+
 }

+ 0 - 0
Script/AtomicEditor/modules/typescript.d.ts → Script/AtomicWebViewEditor/modules/typescript.d.ts


+ 18 - 3
Script/AtomicWebViewEditor/tsconfig.json

@@ -6,20 +6,35 @@
         "noImplicitAny": false,
         "removeComments": true,
         "noLib": false,
-        "outFile": "../../Data/AtomicEditor/CodeEditor/editor.bundle.js",
+        "outDir": "../../Data/AtomicEditor/CodeEditor/source/editorCore",
         "sourceMap": true
     },
     "filesGlob": [
-        "./**/*.ts"
+        "./**/*.ts",
+        "../TypeScript/*.ts"
     ],
     "atom": {
         "rewriteTsconfig": true
     },
     "files": [
+        "./clientExtensions/ClientExtensionServices.ts",
+        "./clientExtensions/languageExtensions/javascript/JavascriptLanguageExtension.ts",
+        "./clientExtensions/languageExtensions/typescript/TypescriptLanguageExtension.ts",
+        "./clientExtensions/languageExtensions/typescript/TypescriptLanguageService.ts",
+        "./clientExtensions/ServiceLocator.ts",
         "./editor/editor.ts",
         "./editor/editorConfig.ts",
         "./interop.ts",
+        "./modules/typescript.d.ts",
         "./typings/ace.d.ts",
-        "./typings/AtomicQuery.d.ts"
+        "./typings/AtomicQuery.d.ts",
+        "./typings/WindowExt.d.ts",
+        "../TypeScript/Atomic.d.ts",
+        "../TypeScript/AtomicNET.d.ts",
+        "../TypeScript/AtomicPlayer.d.ts",
+        "../TypeScript/AtomicWork.d.ts",
+        "../TypeScript/Editor.d.ts",
+        "../TypeScript/ToolCore.d.ts",
+        "../TypeScript/WebView.d.ts"
     ]
 }

+ 0 - 5
Script/AtomicWebViewEditor/typings/AtomicQuery.d.ts

@@ -1,5 +0,0 @@
-interface Window {
-    atomicQuery: any;
-    loadCode: (codeUrl) => void;
-    saveCode: () => void;
-}

+ 8 - 0
Script/AtomicWebViewEditor/typings/WindowExt.d.ts

@@ -0,0 +1,8 @@
+/**
+ * Defines the interface to what is available for the host to call or for the client to call on the window object
+ */
+interface Window {
+    atomicQuery: any;
+    HOST_loadCode: (codeUrl) => void;
+    HOST_saveCode: () => void;
+}

+ 11 - 8
Script/tsconfig.json

@@ -25,14 +25,10 @@
         "./AtomicEditor/editor/EditorEvents.ts",
         "./AtomicEditor/editor/EditorLicense.ts",
         "./AtomicEditor/editor/Preferences.ts",
-        "./AtomicEditor/extensionServices/EditorExtensionServices.ts",
-        "./AtomicEditor/extensionServices/resourceServices/TypescriptLanguageService.ts",
-        "./AtomicEditor/extensionServices/resourceServices/TypscriptLanguageExtension.ts",
-        "./AtomicEditor/extensionServices/ServiceLocator.ts",
+        "./AtomicEditor/hostExtensions/HostExtensionServices.ts",
+        "./AtomicEditor/hostExtensions/languageExtensions/TypscriptLanguageExtension.ts",
+        "./AtomicEditor/hostExtensions/ServiceLocator.ts",
         "./AtomicEditor/main.ts",
-        "./AtomicEditor/modules/metrics.ts",
-        "./AtomicEditor/modules/typescript.d.ts",
-        "./AtomicEditor/modules/usertiming.d.ts",
         "./AtomicEditor/resources/ProjectTemplates.ts",
         "./AtomicEditor/resources/ResourceOps.ts",
         "./AtomicEditor/ui/EditorStrings.ts",
@@ -97,6 +93,13 @@
         "./AtomicEditor/ui/ScriptWidget.ts",
         "./AtomicEditor/ui/Shortcuts.ts",
         "./AtomicEditor/ui/UIEvents.ts",
-        "./TypeScript/AtomicWork.d.ts"
+        "./TypeScript/Atomic.d.ts",
+        "./TypeScript/AtomicNET.d.ts",
+        "./TypeScript/AtomicPlayer.d.ts",
+        "./TypeScript/AtomicWork.d.ts",
+        "./TypeScript/Editor.d.ts",
+        "./TypeScript/EditorWork.d.ts",
+        "./TypeScript/ToolCore.d.ts",
+        "./TypeScript/WebView.d.ts"
     ]
 }