Skip to content

Commit deae6e0

Browse files
authored
Merge pull request #348 from vim-denops/support-interrupt
👍 Add `denops#interrupt()`
2 parents 060da2e + 957d17a commit deae6e0

File tree

10 files changed

+123
-4
lines changed

10 files changed

+123
-4
lines changed

autoload/denops.vim

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ function! denops#request_async(name, method, params, success, failure) abort
2525
\)
2626
endfunction
2727

28+
function! denops#interrupt(...) abort
29+
let l:args = a:0 ? [a:1] : []
30+
call denops#server#wait_async({ -> denops#_internal#server#chan#notify(
31+
\ 'invoke',
32+
\ ['interrupt', l:args],
33+
\)})
34+
endfunction
35+
2836
" Configuration
2937
call denops#_internal#conf#define('denops#disabled', 0)
3038
call denops#_internal#conf#define('denops#deno', 'deno')

denops/@denops-private/denops.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ const isBatchReturn = is.TupleOf([is.Array, is.String] as const);
88

99
export type Host = Pick<HostOrigin, "redraw" | "call" | "batch">;
1010

11-
export type Service = Pick<ServiceOrigin, "dispatch" | "waitLoaded">;
11+
export type Service = Pick<
12+
ServiceOrigin,
13+
"dispatch" | "waitLoaded" | "interrupted"
14+
>;
1215

1316
export class DenopsImpl implements Denops {
1417
readonly name: string;
@@ -32,6 +35,10 @@ export class DenopsImpl implements Denops {
3235
this.#service = service;
3336
}
3437

38+
get interrupted(): AbortSignal {
39+
return this.#service.interrupted;
40+
}
41+
3542
redraw(force?: boolean): Promise<void> {
3643
return this.#host.redraw(force);
3744
}

denops/@denops-private/denops_test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Meta } from "jsr:@denops/[email protected]";
2-
import { assertEquals } from "jsr:@std/[email protected]";
2+
import { assertEquals, assertInstanceOf } from "jsr:@std/[email protected]";
33
import { assertSpyCall, stub } from "jsr:@std/[email protected]/mock";
44
import { promiseState } from "jsr:@lambdalisue/[email protected]";
55
import { unimplemented } from "jsr:@lambdalisue/[email protected]";
@@ -20,9 +20,14 @@ Deno.test("DenopsImpl", async (t) => {
2020
const service: Service = {
2121
dispatch: () => unimplemented(),
2222
waitLoaded: () => unimplemented(),
23+
interrupted: new AbortController().signal,
2324
};
2425
const denops = new DenopsImpl("dummy", meta, host, service);
2526

27+
await t.step("interrupted returns AbortSignal instance", () => {
28+
assertInstanceOf(denops.interrupted, AbortSignal);
29+
});
30+
2631
await t.step("redraw() calls host.redraw()", async () => {
2732
using s = stub(host, "redraw");
2833
await denops.redraw();

denops/@denops-private/host.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export type Service = {
5050
bind(host: Host): void;
5151
load(name: string, script: string): Promise<void>;
5252
reload(name: string): Promise<void>;
53+
interrupt(reason?: unknown): void;
5354
dispatch(name: string, fn: string, args: unknown[]): Promise<unknown>;
5455
dispatchAsync(
5556
name: string,
@@ -74,6 +75,11 @@ export function invoke(
7475
return service.reload(
7576
...ensure(args, is.TupleOf([is.String] as const)),
7677
);
78+
case "interrupt":
79+
service.interrupt(
80+
...ensure(args, is.ParametersOf([is.OptionalOf(is.Unknown)] as const)),
81+
);
82+
return Promise.resolve();
7783
case "dispatch":
7884
return service.dispatch(
7985
...ensure(args, is.TupleOf([is.String, is.String, is.Array] as const)),

denops/@denops-private/host/nvim_test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Deno.test("Neovim", async (t) => {
2424
bind: () => unimplemented(),
2525
load: () => unimplemented(),
2626
reload: () => unimplemented(),
27+
interrupt: () => unimplemented(),
2728
dispatch: () => unimplemented(),
2829
dispatchAsync: () => unimplemented(),
2930
};

denops/@denops-private/host/vim_test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Deno.test("Vim", async (t) => {
1919
bind: () => unimplemented(),
2020
load: () => unimplemented(),
2121
reload: () => unimplemented(),
22+
interrupt: () => unimplemented(),
2223
dispatch: () => unimplemented(),
2324
dispatchAsync: () => unimplemented(),
2425
};

denops/@denops-private/host_test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Deno.test("invoke", async (t) => {
1212
const service: Omit<Service, "bind"> = {
1313
load: () => unimplemented(),
1414
reload: () => unimplemented(),
15+
interrupt: () => unimplemented(),
1516
dispatch: () => unimplemented(),
1617
dispatchAsync: () => unimplemented(),
1718
};
@@ -46,6 +47,28 @@ Deno.test("invoke", async (t) => {
4647
});
4748
});
4849

50+
await t.step("calls 'interrupt'", async (t) => {
51+
await t.step("ok", async () => {
52+
using s = stub(service, "interrupt");
53+
await invoke(service, "interrupt", []);
54+
assertSpyCalls(s, 1);
55+
assertSpyCall(s, 0, { args: [] });
56+
});
57+
58+
await t.step("ok (with reason)", async () => {
59+
using s = stub(service, "interrupt");
60+
await invoke(service, "interrupt", ["reason"]);
61+
assertSpyCalls(s, 1);
62+
assertSpyCall(s, 0, { args: ["reason"] });
63+
});
64+
65+
await t.step("invalid args", () => {
66+
using s = stub(service, "interrupt");
67+
assertThrows(() => invoke(service, "interrupt", ["a", "b"]), AssertError);
68+
assertSpyCalls(s, 0);
69+
});
70+
});
71+
4972
await t.step("calls 'dispatch'", async (t) => {
5073
await t.step("ok", async () => {
5174
using s = stub(service, "dispatch");

denops/@denops-private/service.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ type Waiter = {
1313
* Service manage plugins and is visible from the host (Vim/Neovim) through `invoke()` function.
1414
*/
1515
export class Service implements Disposable {
16-
#plugins: Map<string, Plugin> = new Map();
17-
#waiters: Map<string, Waiter> = new Map();
16+
#interruptController = new AbortController();
17+
#plugins = new Map<string, Plugin>();
18+
#waiters = new Map<string, Waiter>();
1819
#meta: Meta;
1920
#host?: Host;
2021

@@ -29,6 +30,10 @@ export class Service implements Disposable {
2930
return this.#waiters.get(name)!;
3031
}
3132

33+
get interrupted(): AbortSignal {
34+
return this.#interruptController.signal;
35+
}
36+
3237
bind(host: Host): void {
3338
this.#host = host;
3439
}
@@ -77,6 +82,11 @@ export class Service implements Disposable {
7782
return this.#getWaiter(name).promise;
7883
}
7984

85+
interrupt(reason?: unknown): void {
86+
this.#interruptController.abort(reason);
87+
this.#interruptController = new AbortController();
88+
}
89+
8090
async #dispatch(name: string, fn: string, args: unknown[]): Promise<unknown> {
8191
const plugin = this.#plugins.get(name);
8292
if (!plugin) {

denops/@denops-private/service_test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
assertEquals,
44
assertMatch,
55
assertRejects,
6+
assertThrows,
67
} from "jsr:@std/[email protected]";
78
import {
89
assertSpyCall,
@@ -389,4 +390,24 @@ Deno.test("Service", async (t) => {
389390
});
390391
},
391392
);
393+
394+
await t.step(
395+
"interrupt() sends interrupt signal to `interrupted` attribute",
396+
() => {
397+
const signal = service.interrupted;
398+
signal.throwIfAborted(); // Should not throw
399+
service.interrupt();
400+
assertThrows(() => signal.throwIfAborted());
401+
},
402+
);
403+
404+
await t.step(
405+
"interrupt() sends interrupt signal to `interrupted` attribute with reason",
406+
() => {
407+
const signal = service.interrupted;
408+
signal.throwIfAborted(); // Should not throw
409+
service.interrupt("test");
410+
assertThrows(() => signal.throwIfAborted(), "test");
411+
},
412+
);
392413
});

doc/denops.txt

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,23 @@ API Reference: https://deno.land/x/denops_std/mod.ts
2828
=============================================================================
2929
USAGE *denops-usage*
3030

31+
-----------------------------------------------------------------------------
32+
RECOMMENDED *denops-recommended*
33+
34+
Add the following recommended settings to your |vimrc| or |init.vim|:
35+
>
36+
" Interrupt the process of plugins via <C-c>
37+
noremap <silent> <C-c> <Cmd>call denops#interrupt()<CR><C-c>
38+
inoremap <silent> <C-c> <Cmd>call denops#interrupt()<CR><C-c>
39+
cnoremap <silent> <C-c> <Cmd>call denops#interrupt()<CR><C-c>
40+
41+
" Restart Denops server
42+
command! DenopsRestart call denops#server#restart()
43+
44+
" Fix Deno module cache issue
45+
command! DenopsFixCache call denops#cache#update(#{reload: v:true})
46+
<
47+
3148
-----------------------------------------------------------------------------
3249
SHARED SERVER *denops-shared-server*
3350

@@ -199,6 +216,26 @@ denops#request_async({name}, {method}, {params}, {success}, {failure})
199216
\ { v -> s:success(v) },
200217
\ { e -> s:failure(e) },
201218
\)
219+
<
220+
*denops#interrupt()*
221+
denops#interrupt([{reason}])
222+
Interrupts the process of plugins. It is assumed to be used to
223+
interrupt the process of plugins when the user presses a key or
224+
issues a command.
225+
{reason} is an optional string to describe the reason for the
226+
interruption.
227+
Note that the interruption is not guaranteed to be immediate.
228+
229+
Use the following mapping to interrupt the process of plugins:
230+
>
231+
" For normal/visual/select mode
232+
noremap <silent> <C-c> <Cmd>call denops#interrupt()<CR><C-c>
233+
234+
" For insert mode
235+
inoremap <silent> <C-c> <Cmd>call denops#interrupt()<CR><C-c>
236+
237+
" For command mode
238+
cnoremap <silent> <C-c> <Cmd>call denops#interrupt()<CR><C-c>
202239
<
203240
*denops#cache#update()*
204241
denops#cache#update([{options}])

0 commit comments

Comments
 (0)