Skip to content

Commit

Permalink
WIP: eliminate "host" concept
Browse files Browse the repository at this point in the history
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 mappings from a method name => function,
  is a "remote module". It can be loaded by Nvim as a regular "node
  client" which also responds to requests.

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' ] ]
  • Loading branch information
justinmk committed Mar 26, 2024
1 parent e03e409 commit a8d704f
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 0 deletions.
78 changes: 78 additions & 0 deletions packages/neovim/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,37 @@ import { Buffer } from './Buffer';

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

export interface Response {
send(resp: any, isError?: boolean): void;
}

/** Handler shape for incoming requests. */
// export interface Handler {
// fn: Function;
// }

export class NeovimClient extends Neovim {
protected requestQueue: any[];

/**
* Handlers for custom (non "nvim_") methods registered by the remote module.
* These handle requests from the Nvim peer.
*/
public handlers: { [index: string]: (event: { name: string }, args: any[]) => any } = {};

private transportAttached: boolean;

private _channelId: number;

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

/**
* Defines a handler for incoming RPC request method/notification.
*/
setHandler(method: string, fn: (event: { name: string }, args: any[]) => any) {
this.handlers[method] = fn;
}

constructor(options: { transport?: Transport; logger?: Logger } = {}) {
// Neovim has no `data` or `metadata`
super({
Expand Down Expand Up @@ -45,6 +67,61 @@ export class NeovimClient extends Neovim {
this.setupTransport();
}

/**
* The "client" is also the "host". https://github.com/neovim/neovim/issues/27949
*/
async handleRequest3(name: string, args: any[]) {
const handler = this.handlers[name];
if (!handler) {
const msg = `node-client: missing handler for "${name}"`;
this.logger.error(msg);
throw new Error(msg);
}

try {
return await handler({ name: name }, args);
} catch (err) {
const msg = `node-client: failed to handle request: "${name}": ${err.message}`;
this.logger.error(msg);
throw new Error(msg);
}
}

/**
* The "client" is also the "host". https://github.com/neovim/neovim/issues/27949
*/
async handleRequest2(method: string, args: any[], res: Response) {
this.logger.debug('request received: %s', method);
// 'poll' and 'specs' are requests by Nvim, otherwise we dispatch to registered remote module methods (if any).
if (method === 'poll') {
// Handshake for Nvim.
res.send('ok');
// Not needed:
// } else if (method === 'specs') { this.handleRequestSpecs(method, args, res);
} else {
try {
const plugResult = await this.handleRequest3(method, args);
res.send(
!plugResult || typeof plugResult === 'undefined' ? null : plugResult
);
} catch (err) {
res.send(err.toString(), true);
}
}
}

// async start({ proc }: { proc: NodeJS.Process }) {
// // stdio is reversed since it's from the perspective of Neovim
// const nvim = attach({ reader: proc.stdin, writer: proc.stdout });
// this.nvim = nvim;
// this.nvim.logger.debug('host.start');
// nvim.on('request', this.handler);
// nvim.on('notification', this.handlePlugin);
// nvim.on('disconnect', () => {
// this.nvim?.logger.debug('host.disconnected');
// });
// }

get isApiReady(): boolean {
return this.transportAttached && typeof this._channelId !== 'undefined';
}
Expand Down Expand Up @@ -79,6 +156,7 @@ export class NeovimClient extends Neovim {
});
} else {
this.emit('request', method, args, resp);
this.handleRequest2(method, args, resp);
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/neovim/src/host/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export interface Response {
send(resp: any, isError?: boolean): void;
}

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

Expand Down

0 comments on commit a8d704f

Please sign in to comment.