Skip to content

Commit

Permalink
Merge pull request #17 from vim-denops/fix-send
Browse files Browse the repository at this point in the history
💥  `Session.send` should return a promise
  • Loading branch information
lambdalisue authored Dec 31, 2023
2 parents e15221b + 453e728 commit 6729a74
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 87 deletions.
75 changes: 40 additions & 35 deletions client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
const msgidThreshold = 2 ** 32;

type Session = {
send: (data: Command | Message) => void;
send: (data: Command | Message) => Promise<void>;
recv: (msgid: number) => Promise<Message>;
};

Expand All @@ -21,7 +21,6 @@ type Session = {
*
* @example
* ```ts
* import { assertNumber } from "https://deno.land/x/unknownutil/mod.ts";
* import { channel } from "https://deno.land/x/streamtools/mod.ts";
* import { Session, Client } from "./mod.ts";
*
Expand Down Expand Up @@ -50,8 +49,8 @@ export class 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} session The session to communicate with.
* @param {Indexer} indexer The indexer to generate message IDs.
* @param session The session to communicate with.
* @param indexer The indexer to generate message IDs.
*/
constructor(session: Session, indexer?: Indexer) {
this.#session = session;
Expand All @@ -72,89 +71,95 @@ export class Client {
/**
* Sends a message to Vim.
*
* @param {number} msgid The message ID.
* @param {unknown} value The value to send.
* @param msgid The message ID.
* @param value The value to send.
*/
reply(msgid: number, value: unknown): void {
reply(msgid: number, value: unknown): Promise<void> {
const message = buildMessage(msgid, value);
this.#session.send(message);
return this.#session.send(message);
}

/**
* Sends a redraw command to Vim.
*
* @param {boolean} force Whether to force redraw.
* @param force Whether to force redraw.
*/
redraw(force = false): void {
redraw(force = false): Promise<void> {
const command = buildRedrawCommand(force);
this.#session.send(command);
return this.#session.send(command);
}

/**
* Sends an ex command to Vim.
*
* @param {string} expr The expression to evaluate.
* @param expr The expression to evaluate.
*/
ex(expr: string): void {
ex(expr: string): Promise<void> {
const command = buildExCommand(expr);
this.#session.send(command);
return this.#session.send(command);
}

/**
* Sends a normal command to Vim.
*
* @param {string} expr The expression to evaluate.
* @param expr The expression to evaluate.
*/
normal(expr: string): void {
normal(expr: string): Promise<void> {
const command = buildNormalCommand(expr);
this.#session.send(command);
return this.#session.send(command);
}

/**
* Sends an expr command to Vim and wait for the result.
*
* @param {string} expr The expression to evaluate.
* @returns {Promise<unknown>} The result of the expression.
* @param expr The expression to evaluate.
* @returns The result of the expression.
*/
expr(expr: string): Promise<unknown> {
async expr(expr: string): Promise<unknown> {
const msgid = this.#nextMsgid();
const command = buildExprCommand(expr, msgid);
this.#session.send(command);
return this.#recv(msgid);
const [ret, _] = await Promise.all([
this.#recv(msgid),
this.#session.send(command),
]);
return ret;
}

/**
* Sends an expr command to Vim.
*
* @param {string} expr The expression to evaluate.
* @param expr The expression to evaluate.
*/
exprNoReply(expr: string): void {
exprNoReply(expr: string): Promise<void> {
const command = buildExprCommand(expr);
this.#session.send(command);
return this.#session.send(command);
}

/**
* Sends a call command to Vim and wait for the result.
*
* @param {string} fn The function name to call.
* @param {unknown[]} args The arguments to pass to the function.
* @returns {Promise<unknown>} The result of the function.
* @param fn The function name to call.
* @param args The arguments to pass to the function.
* @returns The result of the function.
*/
call(fn: string, ...args: unknown[]): Promise<unknown> {
async call(fn: string, ...args: unknown[]): Promise<unknown> {
const msgid = this.#nextMsgid();
const command = buildCallCommand(fn, args, msgid);
this.#session.send(command);
return this.#recv(msgid);
const [ret, _] = await Promise.all([
this.#recv(msgid),
this.#session.send(command),
]);
return ret;
}

/**
* Sends a call command to Vim.
*
* @param {string} fn The function name to call.
* @param {unknown[]} args The arguments to pass to the function.
* @param fn The function name to call.
* @param args The arguments to pass to the function.
*/
callNoReply(fn: string, ...args: unknown[]): void {
callNoReply(fn: string, ...args: unknown[]): Promise<void> {
const command = buildCallCommand(fn, args);
this.#session.send(command);
return this.#session.send(command);
}
}
48 changes: 29 additions & 19 deletions client_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,25 @@ import {
assertEquals,
assertRejects,
assertThrows,
} from "https://deno.land/std@0.186.0/testing/asserts.ts";
} from "https://deno.land/std@0.210.0/assert/mod.ts";
import { buildMessage } from "./message.ts";
import { Client } from "./client.ts";

Deno.test("Client.reply", async (t) => {
await t.step("sends a message", () => {
await t.step("sends a message", async () => {
const receives: unknown[] = [];
const session = {
send: (message: unknown) => {
receives.push(message);
return Promise.resolve();
},
recv: () => {
throw new Error("should not be called");
},
};
const client = new Client(session);
client.reply(1, "Hello");
client.reply(2, "World");
await client.reply(1, "Hello");
await client.reply(2, "World");
assertEquals(receives, [
[1, "Hello"],
[2, "World"],
Expand All @@ -41,19 +42,20 @@ Deno.test("Client.reply", async (t) => {
});

Deno.test("Client.redraw", async (t) => {
await t.step("sends a redraw command", () => {
await t.step("sends a redraw command", async () => {
const receives: unknown[] = [];
const session = {
send: (message: unknown) => {
receives.push(message);
return Promise.resolve();
},
recv: () => {
throw new Error("should not be called");
},
};
const client = new Client(session);
client.redraw();
client.redraw(true);
await client.redraw();
await client.redraw(true);
assertEquals(receives, [
["redraw", ""],
["redraw", "force"],
Expand All @@ -75,18 +77,19 @@ Deno.test("Client.redraw", async (t) => {
});

Deno.test("Client.ex", async (t) => {
await t.step("sends a ex command", () => {
await t.step("sends a ex command", async () => {
const receives: unknown[] = [];
const session = {
send: (message: unknown) => {
receives.push(message);
return Promise.resolve();
},
recv: () => {
throw new Error("should not be called");
},
};
const client = new Client(session);
client.ex("echo 'Hello'");
await client.ex("echo 'Hello'");
assertEquals(receives, [
["ex", "echo 'Hello'"],
]);
Expand All @@ -107,18 +110,19 @@ Deno.test("Client.ex", async (t) => {
});

Deno.test("Client.normal", async (t) => {
await t.step("sends a normal command", () => {
await t.step("sends a normal command", async () => {
const receives: unknown[] = [];
const session = {
send: (message: unknown) => {
receives.push(message);
return Promise.resolve();
},
recv: () => {
throw new Error("should not be called");
},
};
const client = new Client(session);
client.normal("zO");
await client.normal("zO");
assertEquals(receives, [
["normal", "zO"],
]);
Expand All @@ -144,6 +148,7 @@ Deno.test("Client.expr", async (t) => {
const session = {
send: (message: unknown) => {
receives.push(message);
return Promise.resolve();
},
recv: (msgid: number) =>
Promise.resolve(buildMessage(msgid, `response:${msgid}`)),
Expand All @@ -163,7 +168,7 @@ Deno.test("Client.expr", async (t) => {
]);
});

await t.step("throws an error when send fails", () => {
await t.step("rejects with an error when send fails", async () => {
const session = {
send: () => {
throw new Error("send error");
Expand All @@ -172,7 +177,7 @@ Deno.test("Client.expr", async (t) => {
Promise.resolve(buildMessage(msgid, `response:${msgid}`)),
};
const client = new Client(session);
assertThrows(
await assertRejects(
() => client.expr("g:vim_deno_channel_command"),
Error,
"send error",
Expand All @@ -183,6 +188,7 @@ Deno.test("Client.expr", async (t) => {
const session = {
send: () => {
// Do NOTHING
return Promise.resolve();
},
recv: () => {
throw new Error("recv error");
Expand All @@ -198,18 +204,19 @@ Deno.test("Client.expr", async (t) => {
});

Deno.test("Client.exprNoReply", async (t) => {
await t.step("sends a expr command", () => {
await t.step("sends a expr command", async () => {
const receives: unknown[] = [];
const session = {
send: (message: unknown) => {
receives.push(message);
return Promise.resolve();
},
recv: () => {
throw new Error("should not be called");
},
};
const client = new Client(session);
client.exprNoReply("g:vim_deno_channel_command");
await client.exprNoReply("g:vim_deno_channel_command");
assertEquals(receives, [
["expr", "g:vim_deno_channel_command"],
]);
Expand Down Expand Up @@ -239,6 +246,7 @@ Deno.test("Client.call", async (t) => {
const session = {
send: (message: unknown) => {
receives.push(message);
return Promise.resolve();
},
recv: (msgid: number) =>
Promise.resolve(buildMessage(msgid, `response:${msgid}`)),
Expand All @@ -252,7 +260,7 @@ Deno.test("Client.call", async (t) => {
]);
});

await t.step("throws an error when send fails", () => {
await t.step("rejects with an error when send fails", async () => {
const session = {
send: () => {
throw new Error("send error");
Expand All @@ -261,7 +269,7 @@ Deno.test("Client.call", async (t) => {
Promise.resolve(buildMessage(msgid, `response:${msgid}`)),
};
const client = new Client(session);
assertThrows(
await assertRejects(
() => client.call("foo", "bar"),
Error,
"send error",
Expand All @@ -272,6 +280,7 @@ Deno.test("Client.call", async (t) => {
const session = {
send: () => {
// Do NOTHING
return Promise.resolve();
},
recv: () => {
throw new Error("recv error");
Expand All @@ -287,18 +296,19 @@ Deno.test("Client.call", async (t) => {
});

Deno.test("Client.callNoReply", async (t) => {
await t.step("sends a call command", () => {
await t.step("sends a call command", async () => {
const receives: unknown[] = [];
const session = {
send: (message: unknown) => {
receives.push(message);
return Promise.resolve();
},
recv: () => {
throw new Error("should not be called");
},
};
const client = new Client(session);
client.callNoReply("foo", "bar");
await client.callNoReply("foo", "bar");
assertEquals(receives, [
["call", "foo", ["bar"]],
]);
Expand Down
2 changes: 1 addition & 1 deletion command_test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
assertEquals,
assertThrows,
} from "https://deno.land/std@0.186.0/testing/asserts.ts";
} from "https://deno.land/std@0.210.0/assert/mod.ts";
import {
buildCallCommand,
buildExCommand,
Expand Down
4 changes: 2 additions & 2 deletions json_streams_test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts";
import { assertEquals } from "https://deno.land/std@0.210.0/assert/mod.ts";
import {
channel,
collect,
} from "https://deno.land/x/streamtools@v0.4.0/mod.ts";
} from "https://deno.land/x/streamtools@v0.5.0/mod.ts";
import { DecodeStream, EncodeStream } from "./json_streams.ts";

const encoder = new TextEncoder();
Expand Down
2 changes: 1 addition & 1 deletion message_test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertEquals } from "https://deno.land/std@0.186.0/testing/asserts.ts";
import { assertEquals } from "https://deno.land/std@0.210.0/assert/mod.ts";
import { buildMessage, isMessage } from "./message.ts";

const isMessageTestCases = [
Expand Down
Loading

0 comments on commit 6729a74

Please sign in to comment.