/
stdin.ts
147 lines (128 loc) · 3.92 KB
/
stdin.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
import readline from 'node:readline'
import c from 'picocolors'
import prompt from 'prompts'
import { isWindows, stdout } from '../utils'
import type { Vitest } from './core'
const keys = [
[['a', 'return'], 'rerun all tests'],
['r', 'rerun current pattern tests'],
['f', 'rerun only failed tests'],
['u', 'update snapshot'],
['p', 'filter by a filename'],
['t', 'filter by a test name regex pattern'],
['q', 'quit'],
]
const cancelKeys = ['space', 'c', 'h', ...keys.map(key => key[0]).flat()]
export function printShortcutsHelp() {
stdout().write(
`
${c.bold(' Watch Usage')}
${keys.map(i => c.dim(' press ') + c.reset([i[0]].flat().map(c.bold).join(', ')) + c.dim(` to ${i[1]}`)).join('\n')}
`,
)
}
export function registerConsoleShortcuts(ctx: Vitest) {
let latestFilename = ''
async function _keypressHandler(str: string, key: any) {
// Cancel run and exit when ctrl-c or esc is pressed.
// If cancelling takes long and key is pressed multiple times, exit forcefully.
if (str === '\x03' || str === '\x1B' || (key && key.ctrl && key.name === 'c')) {
if (!ctx.isCancelling) {
ctx.logger.logUpdate.clear()
ctx.logger.log(c.red('Cancelling test run. Press CTRL+c again to exit forcefully.\n'))
process.exitCode = 130
await ctx.cancelCurrentRun('keyboard-input')
await ctx.runningPromise
}
return ctx.exit(true)
}
// window not support suspend
if (!isWindows && key && key.ctrl && key.name === 'z') {
process.kill(process.ppid, 'SIGTSTP')
process.kill(process.pid, 'SIGTSTP')
return
}
// Other keys are for watch mode only
if (!ctx.config.watch)
return
const name = key?.name
if (ctx.runningPromise) {
if (cancelKeys.includes(name))
await ctx.cancelCurrentRun('keyboard-input')
return
}
// quit
if (name === 'q')
return ctx.exit(true)
// TODO typechecking doesn't support shortcuts this yet
if (ctx.mode === 'typecheck')
return
// help
if (name === 'h')
return printShortcutsHelp()
// update snapshot
if (name === 'u')
return ctx.updateSnapshot()
// rerun all tests
if (name === 'a' || name === 'return')
return ctx.changeNamePattern('')
// rerun current pattern tests
if (name === 'r')
return ctx.rerunFiles()
// rerun only failed tests
if (name === 'f')
return ctx.rerunFailed()
// change testNamePattern
if (name === 't')
return inputNamePattern()
// change fileNamePattern
if (name === 'p')
return inputFilePattern()
}
async function keypressHandler(str: string, key: any) {
await _keypressHandler(str, key)
}
async function inputNamePattern() {
off()
const { filter = '' }: { filter: string } = await prompt([{
name: 'filter',
type: 'text',
message: 'Input test name pattern (RegExp)',
initial: ctx.configOverride.testNamePattern?.source || '',
}])
on()
await ctx.changeNamePattern(filter.trim(), undefined, 'change pattern')
}
async function inputFilePattern() {
off()
const { filter = '' }: { filter: string } = await prompt([{
name: 'filter',
type: 'text',
message: 'Input filename pattern',
initial: latestFilename,
}])
latestFilename = filter.trim()
on()
await ctx.changeFilenamePattern(filter.trim())
}
let rl: readline.Interface | undefined
function on() {
off()
rl = readline.createInterface({ input: process.stdin, escapeCodeTimeout: 50 })
readline.emitKeypressEvents(process.stdin, rl)
if (process.stdin.isTTY)
process.stdin.setRawMode(true)
process.stdin.on('keypress', keypressHandler)
}
function off() {
rl?.close()
rl = undefined
process.stdin.removeListener('keypress', keypressHandler)
if (process.stdin.isTTY)
process.stdin.setRawMode(false)
}
on()
return function cleanup() {
off()
}
}