Skip to content

Commit 8c674a5

Browse files
committed
Remove lifecycle state from context and make it explicit
Also: add logging of stderr
1 parent 2f12c53 commit 8c674a5

File tree

8 files changed

+243
-30
lines changed

8 files changed

+243
-30
lines changed

deno.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"imports": {
1111
"@effection-contrib/test-adapter": "jsr:@effection-contrib/test-adapter@^0.1.0",
1212
"@std/expect": "jsr:@std/expect@^1.0.13",
13+
"@std/streams": "jsr:@std/streams@^1.0.9",
1314
"@std/testing": "jsr:@std/testing@^1.0.9",
1415
"deepmerge": "npm:deepmerge@^4.3.1",
1516
"effection": "jsr:@effection/effection@^4.0.0-alpha.7",

deno.lock

Lines changed: 32 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/debug.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { LSPXMiddleware } from "./types.ts";
2+
import { createLSPXMiddleware } from "./middleware.ts";
3+
4+
/**
5+
* This debug middleware prints handy diagnostics to stderr with
6+
* every request/notification in both directions
7+
*/
8+
export function debug(): LSPXMiddleware {
9+
return createLSPXMiddleware({
10+
client2server: {
11+
*request(req, next) {
12+
let [method, ...params] = req.params;
13+
console.error(
14+
`--> [client2server.request]`,
15+
req.agents.map((a) => a.name),
16+
method,
17+
params,
18+
);
19+
let result = yield* next(req);
20+
console.error(
21+
`<-- [client2server.request]`,
22+
req.agents.map((a) => a.name),
23+
method,
24+
result,
25+
);
26+
return result;
27+
},
28+
*notify(req, next) {
29+
let [method, ...params] = req.params;
30+
console.error(
31+
`--> [client2server.notify]`,
32+
req.agents.map((a) => a.name),
33+
method,
34+
params,
35+
);
36+
let result = yield* next(req);
37+
return result;
38+
},
39+
},
40+
server2client: {
41+
*request(req, next) {
42+
let [method, ...params] = req.params;
43+
console.error(
44+
`--> [server2client.request]`,
45+
req.agent.name,
46+
method,
47+
params,
48+
);
49+
let result = yield* next(req);
50+
console.error(
51+
`<-- [server2client.request]`,
52+
req.agent.name,
53+
method,
54+
result,
55+
);
56+
return result;
57+
},
58+
59+
*notify(req, next) {
60+
let [method, ...params] = req.params;
61+
console.error(
62+
`--> [server2client.notify]`,
63+
req.agent.name,
64+
method,
65+
params,
66+
);
67+
let result = yield* next(req);
68+
return result;
69+
},
70+
},
71+
});
72+
}

lib/dispatch.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { all, type Operation } from "effection";
22
import type {
33
LSPAgent,
4+
LSPXMiddleware,
45
RequestParams,
56
XClientNotification,
67
XClientRequest,
@@ -13,6 +14,16 @@ import {
1314
type CompletionParams,
1415
ErrorCodes,
1516
} from "vscode-languageserver-protocol";
17+
import { createLSPXMiddleware } from "./middleware.ts";
18+
19+
export function defaultHandler(): LSPXMiddleware {
20+
return createLSPXMiddleware({
21+
client2server: {
22+
request: defaultRequest,
23+
notify: defaultNotify,
24+
},
25+
});
26+
}
1627

1728
/**
1829
* Dispatch an incoming request from the client to a set of matching
@@ -46,6 +57,17 @@ export function* defaultRequest(options: XClientRequest): Operation<unknown> {
4657
export function* defaultNotify(options: XClientNotification): Operation<void> {
4758
let [method] = options.params;
4859
let handler = defaultMatch(options.agents, method);
60+
if (handler.agents.length === 0) {
61+
console.error(
62+
`no matching agents found for notification '${
63+
options.params[0]
64+
}. candidates are`,
65+
options.agents.map((a) => ({
66+
name: a.name,
67+
capabilities: a.capabilities,
68+
})),
69+
);
70+
}
4971

5072
yield* all(
5173
handler.agents.map((agent) => agent.notify(options.params)),

lib/lifecycle.ts

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,43 @@
1-
import { all, call, createContext } from "effection";
1+
import { all, call } from "effection";
22
import type { LSPAgent, LSPXMiddleware } from "./types.ts";
33
import { ErrorCodes } from "vscode-jsonrpc";
44
import type { InitializeResult } from "vscode-languageserver-protocol";
55
import { responseError } from "./json-rpc-connection.ts";
66
import * as merge from "./merge.ts";
77
import { createLSPXMiddleware } from "./middleware.ts";
88

9-
const State = createContext<LSPXMiddleware>("lspx.state", uninitialized());
10-
119
export function lifecycle(): LSPXMiddleware {
10+
let transition = (state: State) => {
11+
currentState = state;
12+
};
13+
let currentState = uninitialized(transition);
14+
1215
return createLSPXMiddleware({
1316
client2server: {
1417
*request(params, next) {
15-
let state = yield* State.expect();
16-
return yield* state.client2Server.request(params, next);
18+
let { middleware: { client2Server } } = currentState;
19+
return yield* client2Server.request(params, next);
1720
},
1821
*notify(params, next) {
19-
let state = yield* State.expect();
20-
return yield* state.client2Server.notify(params, next);
22+
let { middleware: { client2Server } } = currentState;
23+
return yield* client2Server.notify(params, next);
2124
},
2225
},
2326
server2client: {
2427
*request(params, next) {
25-
let state = yield* State.expect();
26-
return yield* state.server2Client.request(params, next);
28+
let { middleware: { server2Client } } = currentState;
29+
return yield* server2Client.request(params, next);
2730
},
2831
*notify(params, next) {
29-
let state = yield* State.expect();
30-
return yield* state.server2Client.notify(params, next);
32+
let { middleware: { server2Client } } = currentState;
33+
return yield* server2Client.notify(params, next);
3134
},
3235
},
3336
});
3437
}
3538

36-
function uninitialized(): LSPXMiddleware {
37-
return createLSPXMiddleware({
39+
function uninitialized(transition: (state: State) => void): State {
40+
let middleware = createLSPXMiddleware({
3841
client2server: {
3942
*request(options) {
4043
let [method] = options.params;
@@ -60,20 +63,19 @@ function uninitialized(): LSPXMiddleware {
6063
),
6164
);
6265

63-
yield* State.set(initialized(agents));
66+
transition(initialized(agents, transition));
6467

6568
return cast(merge.capabilities(agents));
6669
},
6770
},
6871
});
72+
return { name: "UNINITIALIZED", middleware };
6973
}
7074

71-
function initialized(agents: LSPAgent[]): LSPXMiddleware {
72-
return createLSPXMiddleware({
75+
function initialized(agents: LSPAgent[], transition: SetState): State {
76+
let middleware = createLSPXMiddleware({
7377
client2server: {
74-
notify(options, next) {
75-
return next({ ...options, agents });
76-
},
78+
notify: (options, next) => next({ ...options, agents }),
7779
*request(options, next) {
7880
let [method] = options.params;
7981
if (method === "initialize") {
@@ -82,7 +84,7 @@ function initialized(agents: LSPAgent[]): LSPXMiddleware {
8284
`initialize invoked twice`,
8385
);
8486
} else if (method === "shutdown") {
85-
yield* State.set(shutdown());
87+
transition(shutdown());
8688
yield* all(agents.map((agent) => agent.request(options.params)));
8789
return null;
8890
} else {
@@ -91,10 +93,12 @@ function initialized(agents: LSPAgent[]): LSPXMiddleware {
9193
},
9294
},
9395
});
96+
97+
return { name: "INITIALIZED", middleware };
9498
}
9599

96-
function shutdown(): LSPXMiddleware {
97-
return createLSPXMiddleware({
100+
function shutdown(): State {
101+
let middleware = createLSPXMiddleware({
98102
client2server: {
99103
*request() {
100104
return yield* responseError(
@@ -104,6 +108,16 @@ function shutdown(): LSPXMiddleware {
104108
},
105109
},
106110
});
111+
return { name: "SHUTDOWN", middleware };
107112
}
108113

109114
const cast = <T>(value: unknown) => value as T;
115+
116+
interface State {
117+
name: string;
118+
middleware: LSPXMiddleware;
119+
}
120+
121+
interface SetState {
122+
(state: State): void;
123+
}

lib/multiplexer.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import type {
1010
} from "./types.ts";
1111
import { lifecycle } from "./lifecycle.ts";
1212
import { combineLSPXMiddlewares } from "./middleware.ts";
13-
import { defaultNotify, defaultRequest } from "./dispatch.ts";
13+
import { defaultHandler } from "./dispatch.ts";
14+
import { responseError } from "./json-rpc-connection.ts";
15+
import { ErrorCodes } from "vscode-languageserver-protocol";
1416

1517
export interface MultiplexerOptions {
1618
agents: LSPAgent[];
@@ -24,6 +26,7 @@ export function useMultiplexer(
2426
let { client2Server, server2Client } = combineLSPXMiddlewares([
2527
lifecycle(),
2628
...options.middlewares,
29+
defaultHandler(),
2730
]);
2831

2932
let { agents } = options;
@@ -62,9 +65,18 @@ export function useMultiplexer(
6265
notifications,
6366
requests,
6467
notify: (params) =>
65-
client2Server.notify({ agents, params }, defaultNotify),
68+
client2Server.notify({ agents, params }, function* ({ params }) {
69+
let [method, ...rest] = params;
70+
console.error(`no handler found for notification '${method}'`, rest);
71+
}),
6672
request: <T>(params: RequestParams) =>
67-
client2Server.request({ agents, params }, defaultRequest) as Operation<
73+
client2Server.request({ agents, params }, function* ({ params }) {
74+
let [method] = params;
75+
return yield* responseError(
76+
ErrorCodes.MethodNotFound,
77+
`no handler found for '${method}'`,
78+
);
79+
}) as Operation<
6880
T
6981
>,
7082
};

0 commit comments

Comments
 (0)