-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathrunner.ts
141 lines (134 loc) · 3.66 KB
/
runner.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
import { mergeReadableStreams } from "jsr:@std/streams@^1.0.0/merge-readable-streams";
import { is } from "jsr:@core/unknownutil@^4.0.0";
import { unreachable } from "jsr:@lambdalisue/errorutil@^1.0.0";
import { type Config, getConfig } from "./conf.ts";
/**
* Represents the mode in which the runner operates.
*/
export type RunMode = "vim" | "nvim";
/**
* Represents options for the runner.
*/
export interface RunOptions
extends Omit<Deno.CommandOptions, "cmd" | "stdin" | "stdout" | "stderr"> {
/**
* A flag indicating whether to enable verbose output.
*/
verbose?: boolean;
}
/**
* Represents results of the runner.
*/
export interface RunResult extends AsyncDisposable {
/**
* Aborts the process.
*/
close(): void;
/**
* Wait the process closed and returns status.
*/
waitClosed(): Promise<WaitClosedResult>;
}
type WaitClosedResult = {
status: Deno.CommandStatus;
output?: string;
};
/**
* Checks if the provided mode is a valid `RunMode`.
*/
export const isRunMode = is.LiteralOneOf(["vim", "nvim"] as const);
/**
* Runs the specified commands in the runner.
*
* @param mode - The mode in which the runner operates (`vim` or `nvim`).
* @param cmds - An array of commands to run.
* @param options - Options for configuring the runner.
*/
export function run(
mode: RunMode,
cmds: string[],
options: RunOptions = {},
): RunResult {
const conf = getConfig();
const { verbose = conf.verbose } = options;
const [cmd, args] = buildArgs(conf, mode);
args.push(...cmds.flatMap((c) => ["-c", c]));
const aborter = new AbortController();
const { signal } = aborter;
const command = new Deno.Command(cmd, {
args,
env: options.env,
stdin: "piped",
stdout: "piped",
stderr: "piped",
signal,
});
const proc = command.spawn();
let outputStream = mergeReadableStreams(
proc.stdout.pipeThrough(new TextDecoderStream(), { signal }),
proc.stderr.pipeThrough(new TextDecoderStream(), { signal }),
);
if (verbose) {
const [consoleStream] = [, outputStream] = outputStream.tee();
consoleStream.pipeTo(
new WritableStream({ write: (data) => console.error(data) }),
).catch(() => {});
}
return {
close() {
aborter.abort("close");
},
async waitClosed() {
const [status, output] = await Promise.all([
proc.status,
Array.fromAsync(outputStream)
.then((list) => list.join(""))
.catch(() => undefined),
]);
await proc.stdin.abort();
return { status, output };
},
async [Symbol.asyncDispose]() {
this.close();
await this.waitClosed();
},
};
}
function buildArgs(conf: Config, mode: RunMode): [string, string[]] {
switch (mode) {
case "vim":
return [
conf.vimExecutable,
[
"-u",
"NONE", // Disable vimrc, plugins, defaults.vim
"-i",
"NONE", // Disable viminfo
"-n", // Disable swap file
"-N", // Disable compatible mode
"-X", // Disable xterm
"-e", // Start Vim in Ex mode
"-s", // Silent or batch mode ("-e" is required before)
"-V1", // Verbose level 1 (Echo messages to stderr)
"-c",
"visual", // Go to Normal mode
"-c",
"set columns=9999", // Avoid unwilling output newline
],
];
case "nvim":
return [
conf.nvimExecutable,
[
"--clean",
"--headless",
"-n", // Disable swap file
"-V1", // Verbose level 1 (Echo messages to stderr)
"-c",
"set columns=9999", // Avoid unwilling output newline
],
];
default:
unreachable(mode);
}
}