From 2c40f2c1fedab89233c1d8a75057341a9f2fc806 Mon Sep 17 00:00:00 2001 From: Bryan Phelps Date: Wed, 27 Mar 2019 18:32:37 -0700 Subject: [PATCH] Extension Host: Send initialization data (#189) * Initial integration * Revert vsync change * Start adding extension host client Reason API * Initial ExtensionHost client start-up * Get extension host running * Hook up entry point to process messages * Add basic test case to verify we get 'Ready' message from extension host * Formatting * Tweak runner * Factor protocol out * Fix ExtHostProtocol path * Formatting * Fix merge conflict * Start stubbing out InitDat * Get ExtensionHostInitData compiling * Formatting * Debugging exception on startup * Refactor interface, test for 'initialized' event * Formatting * Remove logging --- src/editor/Core/Filesystem.rei | 2 + src/editor/Extensions/ExtensionHostClient.re | 135 ++++++------------ .../Extensions/ExtensionHostInitData.re | 53 +++++++ .../Extensions/ExtensionHostProtocol.re | 69 +++++++++ src/editor/Extensions/Oni_Extensions.re | 1 + src/editor/bin/Oni2.re | 5 +- test/editor/Extensions/ExtensionClientTest.re | 20 +-- test/editor/dune | 2 +- 8 files changed, 181 insertions(+), 106 deletions(-) create mode 100644 src/editor/Extensions/ExtensionHostInitData.re create mode 100644 src/editor/Extensions/ExtensionHostProtocol.re diff --git a/src/editor/Core/Filesystem.rei b/src/editor/Core/Filesystem.rei index 349447292a..6baae75039 100644 --- a/src/editor/Core/Filesystem.rei +++ b/src/editor/Core/Filesystem.rei @@ -28,6 +28,8 @@ let mkdir: (string, ~perm: int=?, unit) => t(unit); let rmdir: string => t(unit); +let unsafeFindHome: unit => string; + let getOniDirectory: string => t(string); let createOniConfigFile: string => t(string); diff --git a/src/editor/Extensions/ExtensionHostClient.re b/src/editor/Extensions/ExtensionHostClient.re index 9128e8db14..a7519921be 100644 --- a/src/editor/Extensions/ExtensionHostClient.re +++ b/src/editor/Extensions/ExtensionHostClient.re @@ -9,6 +9,8 @@ open Oni_Core; open Reason_jsonrpc; /* open Revery; */ +module Protocol = ExtensionHostProtocol; + type t = { process: NodeProcess.t, rpc: Rpc.t, @@ -19,90 +21,14 @@ let emptyJsonValue = `Assoc([]); type simpleCallback = unit => unit; let defaultCallback: simpleCallback = () => (); -module Protocol = { - module MessageType = { - let initialized = 0; - let ready = 1; - let initData = 2; - let terminate = 3; - }; - - module LogLevel = { - let trace = 0; - let debug = 1; - let info = 2; - let warning = 3; - let error = 4; - let critical = 5; - let off = 6; - }; - - module Environment = { - type t = { - /* isExtensionDevelopmentDebug: bool, */ - appRootPath: string, - appSettingsHomePath: string, - /* extensionDevelopmentLocationPath: string, */ - /* extensionTestsLocationPath: string, */ - globalStorageHomePath: string, - }; - - let create = - (~appRootPath, ~appSettingsHomePath, ~globalStorageHomePath, ()) => { - appRootPath, - appSettingsHomePath, - globalStorageHomePath, - }; - }; - - /* module InitData { */ - /* [@deriving (show, yojson({strict: false, exn: true}))] */ - /* type t = { */ - /* parentPid: int, */ - /* environment: Environment.t, */ - /* logLevel: int, */ - /* logsLocationPath: string, */ - /* autoStart: bool, */ - /* }; */ - /* }; */ - - module Notification = { - exception NotificationParseException(string); - - type t = { - msgType: int, - reqId: int, - payload: Yojson.Safe.json, - }; - - let of_yojson = (json: Yojson.Safe.json) => { - switch (json) { - | `Assoc([ - ("type", `Int(t)), - ("reqId", `Int(reqId)), - ("payload", payload), - ]) => { - msgType: t, - reqId, - payload, - } - | _ => - raise( - NotificationParseException( - "Unable to parse: " ++ Yojson.Safe.to_string(json), - ), - ) - }; - }; - }; -}; - type messageHandler = (int, Yojson.Safe.json) => result(option(Yojson.Safe.json), string); let defaultMessageHandler = (_, _) => Ok(None); let start = ( + ~initData=ExtensionHostInitData.create(), + ~onInitialized=defaultCallback, ~onMessage=defaultMessageHandler, ~onClosed=defaultCallback, setup: Setup.t, @@ -110,27 +36,56 @@ let start = let args = ["--type=extensionHost"]; let env = [ "AMD_ENTRYPOINT=vs/workbench/services/extensions/node/extensionHostProcess", + "VSCODE_PARENT_PID=" ++ string_of_int(Unix.getpid()), ]; let process = NodeProcess.start(~args, ~env, setup, setup.extensionHostPath); - let handleMessage = (id: int, _reqId: int, payload: Yojson.Safe.json) => { - switch (onMessage(id, payload)) { - | Ok(None) => () - | Ok(Some(_)) => - /* TODO: Send response */ - () - | Error(_) => - /* TODO: Send error */ - () + let lastReqId = ref(0); + let rpcRef = ref(None); + + let send = (msgType: int, msg: Yojson.Safe.json) => { + switch (rpcRef^) { + | None => prerr_endline("RPC not initialized.") + | Some(v) => + incr(lastReqId); + let reqId = lastReqId^; + + let request = + `Assoc([ + ("type", `Int(msgType)), + ("reqId", `Int(reqId)), + ("payload", msg), + ]); + + Rpc.sendNotification(v, "ext/msg", request); }; }; + let handleMessage = (id: int, _reqId: int, payload: Yojson.Safe.json) => + if (id == Protocol.MessageType.ready) { + send( + Protocol.MessageType.initData, + ExtensionHostInitData.to_yojson(initData), + ); + } else if (id == Protocol.MessageType.initialized) { + onInitialized(); + } else { + switch (onMessage(id, payload)) { + | Ok(None) => () + | Ok(Some(_)) => + /* TODO: Send response */ + () + | Error(_) => + /* TODO: Send error */ + () + }; + }; + let onNotification = (n: Notification.t, _) => { switch (n.method, n.params) { | ("host/msg", json) => open Protocol.Notification; - print_endline("[Extension Host Client] Unknown message: " ++ n.method); print_endline("JSON: " ++ Yojson.Safe.to_string(json)); let parsedMessage = Protocol.Notification.of_yojson(json); handleMessage( @@ -145,8 +100,6 @@ let start = let onRequest = (_, _) => Ok(emptyJsonValue); - /* let send = */ - let rpc = Rpc.start( ~onNotification, @@ -156,6 +109,8 @@ let start = process.stdin, ); + rpcRef := Some(rpc); + {process, rpc}; }; diff --git a/src/editor/Extensions/ExtensionHostInitData.re b/src/editor/Extensions/ExtensionHostInitData.re new file mode 100644 index 0000000000..2adfaf35ad --- /dev/null +++ b/src/editor/Extensions/ExtensionHostInitData.re @@ -0,0 +1,53 @@ +/* + * ExtensionHostInitData.re + * + * Module documenting the initialization data required for spinning up + * and activating the extension host + */ + +open Oni_Core; + +module ExtensionInfo = { + [@deriving (show({with_path: false}), yojson({strict: false, exn: true}))] + type t = {identifier: string}; +}; + +module Workspace = { + [@deriving (show({with_path: false}), yojson({strict: false, exn: true}))] + type t = {__test: string}; +}; + +module Environment = { + [@deriving (show({with_path: false}), yojson({strict: false, exn: true}))] + type t = {globalStorageHomePath: string}; + + let create = (~globalStorageHomePath=Filesystem.unsafeFindHome(), ()) => { + let ret: t = {globalStorageHomePath: globalStorageHomePath}; + ret; + }; +}; + +[@deriving (show({with_path: false}), yojson({strict: false, exn: true}))] +type t = { + extensions: list(ExtensionInfo.t), + parentPid: int, + environment: Environment.t, + logsLocationPath: string, + autoStart: bool, +}; + +let create = + ( + ~parentPid=Unix.getpid(), + ~extensions=[], + ~environment=Environment.create(), + ~logsLocationPath=Filesystem.unsafeFindHome(), + ~autoStart=true, + (), + ) => { + parentPid, + extensions, + environment, + logsLocationPath, + autoStart, +}; diff --git a/src/editor/Extensions/ExtensionHostProtocol.re b/src/editor/Extensions/ExtensionHostProtocol.re new file mode 100644 index 0000000000..dc201ffba5 --- /dev/null +++ b/src/editor/Extensions/ExtensionHostProtocol.re @@ -0,0 +1,69 @@ +/* + * ExtensionHostProtocol.re + * + * This module documents & types the protocol for communicating with the VSCode Extension Host. + * + */ + +module MessageType = { + let initialized = 0; + let ready = 1; + let initData = 2; + let terminate = 3; + let requestJsonArgs = 4; +}; + +module LogLevel = { + let trace = 0; + let debug = 1; + let info = 2; + let warning = 3; + let error = 4; + let critical = 5; + let off = 6; +}; + +module Environment = { + type t = { + /* isExtensionDevelopmentDebug: bool, */ + /* appRootPath: string, */ + /* appSettingsHomePath: string, */ + /* extensionDevelopmentLocationPath: string, */ + /* extensionTestsLocationPath: string, */ + globalStorageHomePath: string, + }; + + let create = (~globalStorageHomePath, ()) => { + globalStorageHomePath; + }; +}; + +module Notification = { + exception NotificationParseException(string); + + type t = { + msgType: int, + reqId: int, + payload: Yojson.Safe.json, + }; + + let of_yojson = (json: Yojson.Safe.json) => { + switch (json) { + | `Assoc([ + ("type", `Int(t)), + ("reqId", `Int(reqId)), + ("payload", payload), + ]) => { + msgType: t, + reqId, + payload, + } + | _ => + raise( + NotificationParseException( + "Unable to parse: " ++ Yojson.Safe.to_string(json), + ), + ) + }; + }; +}; diff --git a/src/editor/Extensions/Oni_Extensions.re b/src/editor/Extensions/Oni_Extensions.re index 3fff74ecf7..9abd14ddb1 100644 --- a/src/editor/Extensions/Oni_Extensions.re +++ b/src/editor/Extensions/Oni_Extensions.re @@ -8,6 +8,7 @@ module ColorMap = ColorMap; module ColorizedToken = ColorizedToken; module ExtensionContributions = ExtensionContributions; module ExtensionHostClient = ExtensionHostClient; +module ExtensionHostInitData = ExtensionHostInitData; module ExtensionManifest = ExtensionManifest; module ExtensionScanner = ExtensionScanner; module TextmateClient = TextmateClient; diff --git a/src/editor/bin/Oni2.re b/src/editor/bin/Oni2.re index 439430c946..d881b09c82 100644 --- a/src/editor/bin/Oni2.re +++ b/src/editor/bin/Oni2.re @@ -104,7 +104,10 @@ let init = app => { grammars, ); - let extHostClient = Extensions.ExtensionHostClient.start(setup); + let onExtHostClosed = () => print_endline("ext host closed"); + + let extHostClient = + Extensions.ExtensionHostClient.start(~onClosed=onExtHostClosed, setup); Extensions.TextmateClient.setTheme(tmClient, defaultThemePath); diff --git a/test/editor/Extensions/ExtensionClientTest.re b/test/editor/Extensions/ExtensionClientTest.re index dd450d05c3..d950567b2c 100644 --- a/test/editor/Extensions/ExtensionClientTest.re +++ b/test/editor/Extensions/ExtensionClientTest.re @@ -5,28 +5,20 @@ open Oni_Extensions; open TestFramework; describe("Extension Client", ({test, _}) => - test("receive init message", ({expect}) => + test("gets initialized message", ({expect}) => Helpers.repeat(() => { let setup = Setup.init(); - let gotReadyMessage = ref(false); + let initialized = ref(false); - let onClosed = () => (); - let onMessage = (id, _) => { - if (id === ExtensionHostClient.Protocol.MessageType.ready) { - gotReadyMessage := true; - }; - - Ok(None); - }; - - let extClient = ExtensionHostClient.start(~onClosed, ~onMessage, setup); + let onInitialized = () => initialized := true; + let extClient = ExtensionHostClient.start(~onInitialized, setup); Oni_Core.Utility.waitForCondition(() => { ExtensionHostClient.pump(extClient); - gotReadyMessage^; + initialized^; }); - expect.bool(gotReadyMessage^).toBe(true); + expect.bool(initialized^).toBe(true); }) ) ); diff --git a/test/editor/dune b/test/editor/dune index 55c83e68d7..70dc1087d6 100644 --- a/test/editor/dune +++ b/test/editor/dune @@ -6,8 +6,8 @@ (libraries yojson Oni_Core_Test - Oni_Model_Test Oni_Extensions_Test + Oni_Model_Test Oni_Neovim_Test ))