Skip to content

Commit

Permalink
Extension Host: Send initialization data (#189)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
bryphe committed Mar 28, 2019
1 parent f4d53ca commit 2c40f2c
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 106 deletions.
2 changes: 2 additions & 0 deletions src/editor/Core/Filesystem.rei
Original file line number Diff line number Diff line change
Expand Up @@ -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);
135 changes: 45 additions & 90 deletions src/editor/Extensions/ExtensionHostClient.re
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ open Oni_Core;
open Reason_jsonrpc;
/* open Revery; */

module Protocol = ExtensionHostProtocol;

type t = {
process: NodeProcess.t,
rpc: Rpc.t,
Expand All @@ -19,118 +21,71 @@ 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,
) => {
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(
Expand All @@ -145,8 +100,6 @@ let start =

let onRequest = (_, _) => Ok(emptyJsonValue);

/* let send = */

let rpc =
Rpc.start(
~onNotification,
Expand All @@ -156,6 +109,8 @@ let start =
process.stdin,
);

rpcRef := Some(rpc);

{process, rpc};
};

Expand Down
53 changes: 53 additions & 0 deletions src/editor/Extensions/ExtensionHostInitData.re
Original file line number Diff line number Diff line change
@@ -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,
};
69 changes: 69 additions & 0 deletions src/editor/Extensions/ExtensionHostProtocol.re
Original file line number Diff line number Diff line change
@@ -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),
),
)
};
};
};
1 change: 1 addition & 0 deletions src/editor/Extensions/Oni_Extensions.re
Original file line number Diff line number Diff line change
Expand Up @@ -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;
5 changes: 4 additions & 1 deletion src/editor/bin/Oni2.re
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
20 changes: 6 additions & 14 deletions test/editor/Extensions/ExtensionClientTest.re
Original file line number Diff line number Diff line change
Expand Up @@ -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);
})
)
);
2 changes: 1 addition & 1 deletion test/editor/dune
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
(libraries
yojson
Oni_Core_Test
Oni_Model_Test
Oni_Extensions_Test
Oni_Model_Test
Oni_Neovim_Test
))

Expand Down

0 comments on commit 2c40f2c

Please sign in to comment.