Skip to content

Commit

Permalink
feat: Add typeahead search (vitest-dev#4275)
Browse files Browse the repository at this point in the history
  • Loading branch information
bonyuta0204 committed Dec 16, 2023
1 parent 9cc3668 commit 43bee52
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 37 deletions.
1 change: 1 addition & 0 deletions packages/vitest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@
"@types/micromatch": "^4.0.6",
"@types/prompts": "^2.4.9",
"@types/sinonjs__fake-timers": "^8.1.5",
"ansi-escapes": "^6.2.0",
"birpc": "0.2.14",
"chai-subset": "^1.6.0",
"cli-truncate": "^4.0.0",
Expand Down
86 changes: 86 additions & 0 deletions packages/vitest/src/node/stdin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import readline from 'node:readline'
import c from 'picocolors'
import prompt from 'prompts'
import ansiEscapes from 'ansi-escapes'
import { isWindows, stdout } from '../utils'
import { toArray } from '../utils/base'
import type { Vitest } from './core'
Expand Down Expand Up @@ -28,6 +29,7 @@ ${keys.map(i => c.dim(' press ') + c.reset([i[0]].flat().map(c.bold).join(', ')

export function registerConsoleShortcuts(ctx: Vitest) {
let latestFilename = ''
let currentKeyword: string | undefined

async function _keypressHandler(str: string, key: any) {
// Cancel run and exit when ctrl-c or esc is pressed.
Expand Down Expand Up @@ -95,12 +97,18 @@ export function registerConsoleShortcuts(ctx: Vitest) {

async function inputNamePattern() {
off()
turnOnSearchMode(async (str: string) => {
const files = await ctx.state.getFiles()
return files.map(file => file.tasks).flat().map(task => task.name).filter(name => name.includes(str))
},
)
const { filter = '' }: { filter: string } = await prompt([{
name: 'filter',
type: 'text',
message: 'Input test name pattern (RegExp)',
initial: ctx.configOverride.testNamePattern?.source || '',
}])
turnOffSearchMode()
on()
await ctx.changeNamePattern(filter.trim(), undefined, 'change pattern')
}
Expand All @@ -119,17 +127,62 @@ export function registerConsoleShortcuts(ctx: Vitest) {

async function inputFilePattern() {
off()
turnOnSearchMode(async (str: string) => {
const files = await ctx.globTestFiles([str])
return files.map(file => file[1])
},
)

const { filter = '' }: { filter: string } = await prompt([{
name: 'filter',
type: 'text',
message: 'Input filename pattern',
initial: latestFilename,
}])
turnOffSearchMode()
latestFilename = filter.trim()
on()
await ctx.changeFilenamePattern(filter.trim())
}

function searchHandler(searchFunc: SearchFunc) {
return async function (str: string, key: any) {
// backspace
if (key.sequence === '\x7F') {
if (currentKeyword && currentKeyword?.length > 1)

currentKeyword = currentKeyword?.slice(0, -1)

else
currentKeyword = undefined
}
else if (key?.name === 'return') {
// reset current keyword
currentKeyword = undefined
return
}
else {
if (currentKeyword === undefined)
currentKeyword = str
else
currentKeyword += str
}

if (currentKeyword) {
const files = await searchFunc(currentKeyword)

if (files.length === 0)
eraceAndPrint(`\nPattern matches no files`)

else
eraceAndPrint(`\nPattern matches ${files.length} files` + `\n${files.map(file => c.dim(` › ${file}`)).join('\n')}`)
}
else {
eraceAndPrint('\nPlease input filename pattern')
}
}
}

let rl: readline.Interface | undefined
function on() {
off()
Expand All @@ -148,8 +201,41 @@ export function registerConsoleShortcuts(ctx: Vitest) {
process.stdin.setRawMode(false)
}

type SearchFunc = (str: string) => Promise<string[]>

function turnOnSearchMode(searchFunc: SearchFunc) {
off()
rl = readline.createInterface({ input: process.stdin, escapeCodeTimeout: 50 })
readline.emitKeypressEvents(process.stdin, rl)
if (process.stdin.isTTY)
process.stdin.setRawMode(false)
process.stdin.on('keypress', searchHandler(searchFunc))
}

function turnOffSearchMode() {
rl?.close()
rl = undefined
process.stdin.removeListener('keypress', searchHandler)
if (process.stdin.isTTY)
process.stdin.setRawMode(false)
}

on()

/**
* Print string and back to original cursor position
* @param str
*/
function eraceAndPrint(str: string) {
const lineBreasks = str.split('\n').length - 1

stdout().write(ansiEscapes.cursorDown(1))
stdout().write(ansiEscapes.cursorLeft)
stdout().write(ansiEscapes.eraseDown)
stdout().write(str)
stdout().write(ansiEscapes.cursorUp(lineBreasks + 1))
}

return function cleanup() {
off()
}
Expand Down
53 changes: 16 additions & 37 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 43bee52

Please sign in to comment.