Skip to content

Commit 5ebc7c5

Browse files
committed
WIP: eliminate "host" concept
Problem: The "remote plugin" concept is too complicated. neovim/neovim#27949 Solution: - Let the "client" also be the "host". Eliminate the separate "host" concept and related modules. - Let any node module be a "host". Any node module that imports the "neovim" package and defines method handler(s) is a "remote module". It is loaded by Nvim same as any "node client". TEST CASE / DEMO: const found = findNvim({ orderBy: 'desc', minVersion: '0.9.0' }) const nvim_proc = child_process.spawn(found.matches[0].path, ['--clean', '--embed'], {}); const nvim = attach({ proc: nvim_proc }); nvim.setHandler('foo', (ev, args) => { nvim.logger.info('handled from remote module: "%s": args:%O', ev.name, args); }); nvim.callFunction('rpcrequest', [(await nvim.channelId), 'foo', [42, true, 'bar']]); 2024-03-26 16:47:35 INF handleRequest: foo 2024-03-26 16:47:35 DBG request received: foo 2024-03-26 16:47:35 INF handled from remote module: "foo": args:[ [ 42, true, 'bar' ] ]
1 parent e03e409 commit 5ebc7c5

File tree

2 files changed

+81
-0
lines changed

2 files changed

+81
-0
lines changed

Diff for: packages/neovim/src/api/client.ts

+78
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,37 @@ import { Buffer } from './Buffer';
99

1010
const REGEX_BUF_EVENT = /nvim_buf_(.*)_event/;
1111

12+
export interface Response {
13+
send(resp: any, isError?: boolean): void;
14+
}
15+
16+
/** Handler shape for incoming requests. */
17+
// export interface Handler {
18+
// fn: Function;
19+
// }
20+
1221
export class NeovimClient extends Neovim {
1322
protected requestQueue: any[];
1423

24+
/**
25+
* Handlers for custom (non "nvim_") methods registered by the remote module.
26+
* These handle requests from the Nvim peer.
27+
*/
28+
public handlers: { [index: string]: (event: { name: string }, args: any[]) => any } = {};
29+
1530
private transportAttached: boolean;
1631

1732
private _channelId: number;
1833

1934
private attachedBuffers: Map<string, Map<string, Function[]>> = new Map();
2035

36+
/**
37+
* Defines a handler for incoming RPC request method/notification.
38+
*/
39+
setHandler(method: string, fn: (event: { name: string }, args: any[]) => any) {
40+
this.handlers[method] = fn;
41+
}
42+
2143
constructor(options: { transport?: Transport; logger?: Logger } = {}) {
2244
// Neovim has no `data` or `metadata`
2345
super({
@@ -45,6 +67,61 @@ export class NeovimClient extends Neovim {
4567
this.setupTransport();
4668
}
4769

70+
/**
71+
* The "client" is also the "host". https://github.com/neovim/neovim/issues/27949
72+
*/
73+
async handleRequest3(name: string, args: any[]) {
74+
const handler = this.handlers[name];
75+
if (!handler) {
76+
const msg = `node-client: missing handler for "${name}"`;
77+
this.logger.error(msg);
78+
throw new Error(msg);
79+
}
80+
81+
try {
82+
return await handler({ name: name }, args);
83+
} catch (err) {
84+
const msg = `node-client: failed to handle request: "${name}": ${err.message}`;
85+
this.logger.error(msg);
86+
throw new Error(msg);
87+
}
88+
}
89+
90+
/**
91+
* The "client" is also the "host". https://github.com/neovim/neovim/issues/27949
92+
*/
93+
async handleRequest2(method: string, args: any[], res: Response) {
94+
this.logger.debug('request received: %s', method);
95+
// 'poll' and 'specs' are requests by Nvim, otherwise we dispatch to registered remote module methods (if any).
96+
if (method === 'poll') {
97+
// Handshake for Nvim.
98+
res.send('ok');
99+
// Not needed:
100+
// } else if (method === 'specs') { this.handleRequestSpecs(method, args, res);
101+
} else {
102+
try {
103+
const plugResult = await this.handleRequest3(method, args);
104+
res.send(
105+
!plugResult || typeof plugResult === 'undefined' ? null : plugResult
106+
);
107+
} catch (err) {
108+
res.send(err.toString(), true);
109+
}
110+
}
111+
}
112+
113+
// async start({ proc }: { proc: NodeJS.Process }) {
114+
// // stdio is reversed since it's from the perspective of Neovim
115+
// const nvim = attach({ reader: proc.stdin, writer: proc.stdout });
116+
// this.nvim = nvim;
117+
// this.nvim.logger.debug('host.start');
118+
// nvim.on('request', this.handler);
119+
// nvim.on('notification', this.handlePlugin);
120+
// nvim.on('disconnect', () => {
121+
// this.nvim?.logger.debug('host.disconnected');
122+
// });
123+
// }
124+
48125
get isApiReady(): boolean {
49126
return this.transportAttached && typeof this._channelId !== 'undefined';
50127
}
@@ -79,6 +156,7 @@ export class NeovimClient extends Neovim {
79156
});
80157
} else {
81158
this.emit('request', method, args, resp);
159+
this.handleRequest2(method, args, resp);
82160
}
83161
}
84162

Diff for: packages/neovim/src/host/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export interface Response {
77
send(resp: any, isError?: boolean): void;
88
}
99

10+
/**
11+
* @deprecated Eliminate the "host" concept. https://github.com/neovim/neovim/issues/27949
12+
*/
1013
export class Host {
1114
public loaded: { [index: string]: NvimPlugin };
1215

0 commit comments

Comments
 (0)