-
Notifications
You must be signed in to change notification settings - Fork 1
/
client.ts
165 lines (151 loc) · 4.3 KB
/
client.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import { Indexer } from "@lambdalisue/indexer";
import { buildMessage, type Message } from "./message.ts";
import {
buildCallCommand,
buildExCommand,
buildExprCommand,
buildNormalCommand,
buildRedrawCommand,
type Command,
} from "./command.ts";
const msgidThreshold = 2 ** 32;
type Session = {
send: (data: Command | Message) => Promise<void>;
recv: (msgid: number) => Promise<Message>;
};
/**
* Client is a wrapper of Session to send commands and messages.
*
* @example
* ```ts
* import { channel } from "https://deno.land/x/[email protected]/mod.ts";
* import { Session, Client } from "./mod.ts";
*
* const input = channel<Uint8Array>();
* const output = channel<Uint8Array>();
* const session = new Session(input.reader, output.writer);
* session.start();
*
* // Create a client
* const client = new Client(session);
*
* // Send a ex command
* client.ex("echo 'Hello, world!'");
*
* // Send a call command and wait for the result.
* console.log(await client.call("abs", -1)); // 1
* ```
*/
export class Client {
#session: Session;
#indexer: Indexer;
/**
* Constructs a new client.
*
* Note that the indexer must be unique for each session to avoid message ID conflicts.
* If multiple clients are created for a single session, specify a single indexer.
*
* @param session The session to communicate with.
* @param indexer The indexer to generate message IDs.
*/
constructor(session: Session, indexer?: Indexer) {
this.#session = session;
this.#indexer = indexer ?? new Indexer(msgidThreshold);
}
#nextMsgid(): number {
// The msgid must be a negative number to avoid confusion with message that Vim sends.
// https://github.com/vim/vim/blob/b848ce6b7e27f24aff47a4d63933e0f96663acfe/runtime/doc/channel.txt#L379-L381
return (this.#indexer.next() + 1) * -1;
}
async #recv(msgid: number): Promise<unknown> {
const [_, response] = await this.#session.recv(msgid);
return response;
}
/**
* Sends a message to Vim.
*
* @param msgid The message ID.
* @param value The value to send.
*/
reply(msgid: number, value: unknown): Promise<void> {
const message = buildMessage(msgid, value);
return this.#session.send(message);
}
/**
* Sends a redraw command to Vim.
*
* @param force Whether to force redraw.
*/
redraw(force = false): Promise<void> {
const command = buildRedrawCommand(force);
return this.#session.send(command);
}
/**
* Sends an ex command to Vim.
*
* @param expr The expression to evaluate.
*/
ex(expr: string): Promise<void> {
const command = buildExCommand(expr);
return this.#session.send(command);
}
/**
* Sends a normal command to Vim.
*
* @param expr The expression to evaluate.
*/
normal(expr: string): Promise<void> {
const command = buildNormalCommand(expr);
return this.#session.send(command);
}
/**
* Sends an expr command to Vim and wait for the result.
*
* @param expr The expression to evaluate.
* @returns The result of the expression.
*/
async expr(expr: string): Promise<unknown> {
const msgid = this.#nextMsgid();
const command = buildExprCommand(expr, msgid);
const [ret, _] = await Promise.all([
this.#recv(msgid),
this.#session.send(command),
]);
return ret;
}
/**
* Sends an expr command to Vim.
*
* @param expr The expression to evaluate.
*/
exprNoReply(expr: string): Promise<void> {
const command = buildExprCommand(expr);
return this.#session.send(command);
}
/**
* Sends a call command to Vim and wait for the result.
*
* @param fn The function name to call.
* @param args The arguments to pass to the function.
* @returns The result of the function.
*/
async call(fn: string, ...args: unknown[]): Promise<unknown> {
const msgid = this.#nextMsgid();
const command = buildCallCommand(fn, args, msgid);
const [ret, _] = await Promise.all([
this.#recv(msgid),
this.#session.send(command),
]);
return ret;
}
/**
* Sends a call command to Vim.
*
* @param fn The function name to call.
* @param args The arguments to pass to the function.
*/
callNoReply(fn: string, ...args: unknown[]): Promise<void> {
const command = buildCallCommand(fn, args);
return this.#session.send(command);
}
}