Update to v13 and add queue and completely change code

This commit is contained in:
a 2021-12-06 16:34:00 +01:00
parent dcef23d0ed
commit 55a38726a3
6706 changed files with 424137 additions and 61608 deletions

151
node_modules/node-fzf/README.md generated vendored Normal file
View file

@ -0,0 +1,151 @@
[![npm](https://img.shields.io/npm/v/node-fzf.svg?maxAge=3600&style=flat-square)](https://www.npmjs.com/package/node-fzf)
[![npm](https://img.shields.io/npm/dm/node-fzf.svg?maxAge=3600)](https://www.npmjs.com/package/node-fzf)
[![npm](https://img.shields.io/npm/l/node-fzf.svg?maxAge=3600&style=flat-square)](https://github.com/talmobi/node-fzf/blob/master/LICENSE)
# node-fzf
[fzf](https://github.com/junegunn/fzf) inspired fuzzy CLI list selection 🎀
![](https://i.imgur.com/SFUV5nW.gif)
## Easy to use
#### CLI usage
```bash
npm install -g node-fzf
# by default (TTY mode) will glob list of current dir files
nfzf
# using pipes
find . | nfzf | xargs cat | less
alias merge="git branch | nfzf | xargs git merge"
alias checkout="git branch | nfzf | xargs git checkout"
```
#### API usage
##### promises
```js
const nfzf = require( 'node-fzf' )
// if you only care about r.query
// nfzf.getInput( label )
const opts = {
list: [ 'whale', 'giraffe', 'monkey' ]
}
;( async function () {
// opens interactive selection CLI
// note! this messes with stdout so if you are
// writing to stdout at the same time it will look a bit messy..
const result = await nfzf( opts )
const { selected, query } = result
if( !selected ) {
console.log( 'No matches for:', query )
} else {
console.log( selected.value ) // 'giraffe'
console.log( selected.index ) // 1
console.log( selected.value === opts.list[ selected.index ] ) // true
}
} )()
// can also add more items later..
setInterval( function () {
opts.list.push( 'foobar' )
// an .update method has been attached to the object/array
// that you gave to nfzf( ... )
opts.update( list )
}, 1000 )
```
##### callbacks
```js
const nfzf = require( 'node-fzf' )
// if you only care about r.query
// nfzf.getInput( label, callback )
const list = [ 'whale', 'giraffe', 'monkey' ]
// opens interactive selection CLI
// note! this messes with stdout so if you are
// writing to stdout at the same time it will look a bit messy..
const api = nfzf( list, function ( result ) {
const { selected, query } = result
if( !selected ) {
console.log( 'No matches for:', query )
} else {
console.log( selected.value ) // 'giraffe'
console.log( selected.index ) // 1
console.log( selected.value === list[ selected.index ] ) // true
// the api is a reference to the same argument0 object
// with an added .update method attached.
console.log( list === api ) // true
console.log( list.update === api.update ) // true
}
} )
// can also add more items later..
setInterval( function () {
list.push( 'foobar' )
api.update( list )
}, 1000 )
```
#### Keyboard
```bash
<ctrl-j>,<ctrl-n>,down scroll down
<ctrl-k>,<ctrl-p>,up scroll up
<ctrl-d> scroll down by page size
<ctrl-u> scroll up by page size
<ctrl-a> jump to start of input
<ctrl-e> jump to end of input
<esc>,<ctrl-q>,<ctrl-c> cancel
<return>,<ctrl-m> trigger callback/promise with current selection and exit
<ctrl-w> delte last word from input
<ctrl-b> jump back a word
<ctrl-f> jump forward a word
<backspace> delete last input character
<ctrl-s> switch between modes (fuzzy, normal)
```
## About
[fzf](https://github.com/junegunn/fzf) inspired fuzzy CLI list selection thing for node.
## Why
easy fuzzy list selection UI for NodeJS CLI programs.
## How
Mostly [cli-color](https://github.com/medikoo/cli-color) for dealing with the terminal rendering
and [ttys](https://github.com/TooTallNate/ttys) to hack the ttys to simultaneously
read from non TTY stdin and read key inputs from TTY stdin -> So that we can get piped input while
also at the same time receive and handle raw keyboard input.
## Used by
[yt-play](https://github.com/talmobi/yt-play)
[yt-search](https://github.com/talmobi/yt-search)
## Alternatives
[fzf](https://github.com/junegunn/fzf) even though it doesn't work in NodeJS directly is all-in-all a better tool than this piece of crap :) Highly recommend~
[ipt](https://github.com/ruyadorno/ipt) - similar node based solution
## Test
```bash
npm test
```

99
node_modules/node-fzf/bin/cli.js generated vendored Executable file
View file

@ -0,0 +1,99 @@
#!/usr/bin/env node
const fs = require( 'fs' )
const path = require( 'path' )
const glob = require( 'redstar' )
const nfzf = require( path.join( __dirname, '../src/main.js' ) )
const argv = require( 'minimist' )( process.argv.slice( 2 ) )
const pkgJSON = require( path.join( __dirname, '../package.json' ) )
if ( argv.version || argv.V || argv.v ) {
console.log( 'nfzf version: ' + pkgJSON.version )
process.exit()
}
if ( argv.h || argv.help ) {
const text = fs.readFileSync( path.join( __dirname, '../usage.txt' ), 'utf8' )
console.log( text )
process.exit()
}
const normalMode = ( argv.n || argv.normal || argv.norm )
return run()
function run ()
{
if ( process.stdin.isTTY && !argv._.length ) {
return glob( '**', function ( err, files, dirs ) {
if ( err ) throw err
const opts = {
mode: normalMode ? 'normal' : 'fuzzy',
list: files
}
nfzf( opts, function ( result ) {
if ( result.selected ) {
console.log( files[ result.selected.index ] )
} else if ( argv[ 'print-query' ] ) {
console.log()
console.log( result.query )
}
process.exit()
} )
} )
} else {
// update list later with input piped from stdin
const opts = {
mode: normalMode ? 'normal' : 'fuzzy',
list: [] // stdin will update it later
}
const api = nfzf( opts, function ( result ) {
if ( result.selected ) {
console.log( result.selected.value )
} else if ( argv[ 'print-query' ] ) {
console.log()
console.log( result.query )
}
process.exit()
} )
let buffer = ''
process.stdin.setEncoding( 'utf8' )
process.stdin.on( 'data', function ( chunk ) {
buffer += chunk
// so you need this if you accidentally get stuck in
// a `cat | nfzf` loop
if (
chunk === '\x03' || // ctrl-c
chunk === '\x1B' // esc
) {
console.log( 'exit' )
return process.exit( 1 )
}
const list = (
buffer.split( '\n' )
.filter( function ( t ) { return t.trim().length > 0 } )
)
api.update( list )
} )
process.stdin.on( 'end', function () {
const list = (
buffer.split( '\n' )
.filter( function ( t ) { return t.trim().length > 0 } )
)
api.update( list )
} )
}
}

48
node_modules/node-fzf/package.json generated vendored Normal file
View file

@ -0,0 +1,48 @@
{
"name": "node-fzf",
"version": "0.5.3",
"description": "fzf ( junegunn/fzf ) inspired cli utility for node",
"main": "src/main.js",
"bin": {
"nfzf": "bin/cli.js"
},
"files": [
"bin/cli.js",
"src/main.js",
"usage.txt"
],
"scripts": {
"test": "tape test/test.js"
},
"keywords": [
"node-fzf",
"fzf",
"fuzzy",
"list",
"search",
"cli"
],
"author": "talmobi <talmo.christian@gmail.com>",
"license": "MIT",
"private": false,
"repository": {
"type": "git",
"url": "https://github.com/talmobi/node-fzf"
},
"bugs": {
"url": "https://github.com/talmobi/node-fzf/issues",
"email": "talmo.christian@gmail.com"
},
"dependencies": {
"cli-color": "~1.2.0",
"keypress": "~0.2.1",
"minimist": "~1.2.0",
"redstar": "0.0.2",
"string-width": "~2.1.1",
"ttys": "0.0.3"
},
"devDependencies": {
"mock-stdin": "~1.0.0",
"tape": "~4.13.0"
}
}

939
node_modules/node-fzf/src/main.js generated vendored Normal file
View file

@ -0,0 +1,939 @@
// used to read keyboard input while at the same time
// reading piped stdin input and printing to stdout
const keypress = require( 'keypress' )
const ttys = require( 'ttys' )
const stdin = ttys.stdin
const stdout = ttys.stdout
// get printed width of text
// ex. 漢字 are 4 characters wide but still
// only 2 characters in length
const stringWidth = require( 'string-width' )
// print/render to the terminal
const clc = require( 'cli-color' )
// available filtering modes ( fuzzy by default )
const modes = [ 'fuzzy', 'normal' ]
module.exports = queryUser
// helper to only get user input
module.exports.getInput = getInput
function getInput ( label, callback )
{
const opts = {
label: label,
list: [],
nolist: true // don't print list/matches
}
return queryUser( opts, callback )
}
function queryUser ( opts, callback )
{
/* opts should reference same object at all times
* as it will be returned as an api as well that the
* user can use.
*
* a few functions will be added to opts that will
* work as an API to the user to ex. update the list
* at a later time.
*
* we do it like this instead of return a separate api
* object in order to support promises when a callback
* fn is omitted.
*/
const _opts = opts
if ( Array.isArray( _opts ) ) {
_opts.list = _opts
_opts.mode = 'fuzzy'
}
if ( typeof _opts !== 'object' ) {
// in JavaScript arrays are also a typeof 'object'
throw new TypeError( 'arg0 has to be an array or an object' )
}
_opts.list = _opts.list || []
_opts.mode = _opts.mode || 'fuzzy'
const promise = new Promise( function ( resolve, reject ) {
let originalList = _opts.list || []
let _list = prepareList( originalList )
let _input = ''
// user defined vertical scrolling
let scrollOffset = 0
_opts.update = function ( list ) {
originalList = list
_opts.list = originalList
_list = prepareList( originalList )
render()
}
_opts.stop = function () {
finish()
}
// prepare provided list for internal searching/sorting
function prepareList ( newList ) {
const list = newList.map( function ( value, index ) {
return {
originalValue: value, // text
originalIndex: index
}
} )
return list
}
function finish ( result ) {
if ( finish.done ) return
finish.done = true
stdout.removeListener( 'resize', handleResize )
stdin.removeListener( 'keypress', handleKeypress )
stdin.setRawMode && stdin.setRawMode( false )
stdin.pause()
if ( !result ) {
// quit, exit, cancel, abort
buffer = undefined
result = {
selected: undefined,
// common alternatives for the same thing
query: buffer,
search: buffer,
input: buffer
}
}
if ( callback ) {
callback( result )
}
resolve( result )
}
// make `process.stdin` begin emitting "keypress" events
keypress( stdin )
// selected index relative to currently matched results
// (filtered subset of _list)
let selectedIndex = 0
// input buffer
let buffer = ''
// input cursor position ( only horizontal )
// relative to input buffer
let cursorPosition = 0
// number of items printed on screen, usually ~7
let _printedMatches = 0
let _matches = []
let _selectedItem
const MIN_HEIGHT = 6
function getMaxWidth () {
const mx = stdout.columns - 7
return Math.max( 0, mx )
}
stdout.on( 'resize', handleResize )
function handleResize () {
clearTimeout( handleResize.timeout )
handleResize.timeout = setTimeout( function () {
cleanDirtyScreen()
render()
}, 1 )
}
const debug = false
function handleKeypress ( chunk, key ) {
debug && console.log( 'chunk: ' + chunk )
key = key || { name: '' }
const name = String( key.name )
debug && console.log( 'got "keypress"', key )
if ( key && key.ctrl && name === 'c' ) {
cleanDirtyScreen()
return finish()
}
if ( key && key.ctrl && name === 'z' ) {
cleanDirtyScreen()
return finish()
}
if ( key && key.ctrl && name === 'l' ) {
// return stdout.write( clc.reset )
}
const view_height = _printedMatches || 10
if ( key.ctrl ) {
switch ( name ) {
case 'h': // backspace
// ignore
break
case 'b': // jump back 1 word
{
const slice = buffer.slice( 0, cursorPosition )
const m = slice.match( /\S+\s*$/ ) // last word
if ( m && m.index > 0 ) {
// console.log( m.index )
cursorPosition = m.index
} else {
cursorPosition = 0
}
}
return render()
break
case 'j': // down
case 'n': // down
selectedIndex += 1
return render()
break
case 'k': // up
case 'p': // up
selectedIndex -= 1
return render()
break
case 'l': // right
// ignore
break
case 's':
// TODO ctrl-s support? switch between match modes?
{
// cleanDirtyScreen()
let i = modes.indexOf( _opts.mode )
_opts.mode = modes[ ++i % modes.length ]
}
return render()
break
case 'f': // jump forward 1 word
{
const slice = buffer.slice( cursorPosition )
const m = slice.match( /^\S+\s*/ ) // first word
if ( m && m.index >= 0 && m[ 0 ] && m[ 0 ].length >= 0 ) {
// console.log( m.index )
cursorPosition += ( m.index + m[ 0 ].length )
} else {
cursorPosition = buffer.length
}
}
return render()
break
case 'd': // down
// basically intended as page-down
selectedIndex += view_height
return render()
break
case 'u': // up
// basically intended as page-up
selectedIndex -= view_height
return render()
break
case 'a': // beginning of line
cursorPosition = 0
return render()
break
case 'e': // end of line
cursorPosition = buffer.length
return render()
break
case 'w': // clear word
{
const a = buffer.slice( 0, cursorPosition )
const b = buffer.slice( cursorPosition )
const m = a.match( /\S+\s*$/ ) // last word
if ( m && m.index > 0 ) {
// console.log( m.index )
cursorPosition = m.index
buffer = a.slice( 0, cursorPosition ).concat( b )
} else {
cursorPosition = 0
buffer = b
}
}
return render()
break
case 'q': // quit
cleanDirtyScreen()
return finish()
}
}
// usually ALT key
if ( key.meta ) {
switch ( name ) {
case 'n': // left arrow key
scrollOffset--
return render()
case 'p': // right arrow key
scrollOffset++
return render()
}
}
if ( key.ctrl ) return
if ( key.meta ) return
switch ( name ) {
case 'backspace': // ctrl-h
{
const a = buffer.slice( 0, cursorPosition - 1 )
const b = buffer.slice( cursorPosition )
buffer = a.concat( b )
cursorPosition--
if ( cursorPosition < 0 ) {
cursorPosition = 0
}
}
return render()
break
case 'left': // left arrow key
cursorPosition--
if ( cursorPosition < 0 ) cursorPosition = 0
return render()
break
case 'right': // right arrow key
cursorPosition++
if ( cursorPosition > buffer.length ) {
cursorPosition = buffer.length
}
return render()
break
// text terminals treat ctrl-j as newline ( enter )
// ref: https://ss64.com/bash/syntax-keyboard.html
case 'down': // ctrl-j
case 'enter':
selectedIndex += 1
return render()
case 'up':
selectedIndex -= 1
return render()
case 'esc':
case 'escape':
cleanDirtyScreen()
return finish()
// hit return key ( aka enter key ) ( aka ctrl-m )
case 'return': // ctrl-m
cleanDirtyScreen()
function transformResult ( match ) {
return {
value: match.originalValue,
index: match.originalIndex
}
}
const result = {
selected: _selectedItem && transformResult( _selectedItem ) || undefined,
// common alternatives for the same thing
query: buffer,
search: buffer,
input: buffer
}
return finish( result )
}
if ( chunk && chunk.length === 1 ) {
let c = ''
if ( key.shift ) {
c = chunk.toUpperCase()
} else {
c = chunk
}
if ( c ) {
const a = buffer.slice( 0, cursorPosition )
const b = buffer.slice( cursorPosition )
buffer = a.concat( c, b )
cursorPosition++
if ( cursorPosition > buffer.length ) {
cursorPosition = buffer.length
}
}
render()
}
}
stdin.setEncoding( 'utf8' )
stdin.on( 'keypress', handleKeypress )
const clcBgGray = clc.bgXterm( 236 )
const clcFgArrow = clc.xterm( 198 )
const clcFgBufferArrow = clc.xterm( 110 )
const clcFgGreen = clc.xterm( 143 )
// const clcFgMatchGreen = clc.xterm( 151 )
const clcFgModeStatus = clc.xterm( 110 )
const clcFgMatchGreen = clc.xterm( 107 )
// get matches based on the search mode
function getMatches ( mode, filter, text )
{
switch ( mode.trim().toLowerCase() ) {
case 'normal':
return textMatches( filter, text )
case 'fuzzy':
default:
// default to fuzzy matching
return fuzzyMatches( filter, text )
}
}
// get matched list based on the search mode
function getList ( mode, filter, list )
{
// default to fuzzy matching
switch ( mode.trim().toLowerCase() ) {
case 'normal':
return textList( filter, list )
case 'fuzzy':
default:
// default to fuzzy matching
return fuzzyList( filter, list )
}
}
function fuzzyMatches ( fuzz, text )
{
fuzz = fuzz.toLowerCase()
text = text.toLowerCase()
let tp = 0 // text position/pointer
let matches = []
// nothing to match with
if ( !fuzz ) return matches
for ( let i = 0; i < fuzz.length; i++ ) {
const f = fuzz[ i ]
for ( ; tp < text.length; tp++ ) {
const t = text[ tp ]
if ( f === t ) {
matches.push( tp )
tp++
break
}
}
}
return matches
}
function fuzzyList ( fuzz, list )
{
const results = []
for ( let i = 0; i < list.length; i++ ) {
const item = list[ i ]
const originalIndex = item.originalIndex
const originalValue = item.originalValue
// get rid of unnecessary whitespace that only takes of
// valuable scren space
const normalizedItem = originalValue.split( /\s+/ ).join( ' ' )
/* matches is an array of indexes on the normalizedItem string
* that have matched the fuzz
*/
const matches = fuzzyMatches( fuzz, normalizedItem )
if ( matches.length === fuzz.length ) {
/* When the matches.length is exacly the same as fuzz.length
* it means we have a fuzzy match -> all characters in
* the fuzz string have been found on the normalizedItem string.
* The matches array holds each string index position
* of those matches on the normalizedItem string.
* ex. fuzz = 'foo', normalizedItem = 'far out dog', matches = [0,4,9]
*/
let t = normalizedItem
results.push( {
originalIndex: originalIndex,
originalValue: originalValue,
matchedIndex: results.length,
original: item,
text: t // what shows up on terminal/screen
} )
}
}
return results
}
function textMatches ( filter, text )
{
filter = filter.toLowerCase() // ex. foo
text = text.toLowerCase() // ex. dog food is geat
let tp = 0 // text position/pointer
let matches = []
// nothing to match with
if ( !filter ) return matches
// source pointer ( first index of matched text )
const sp = text.indexOf( filter )
if ( sp >= 0 ) {
// end pointer ( last index of matched text )
const ep = sp + filter.length
for ( let i = sp; i < ep; i++ ) {
matches.push( i )
}
}
return matches
}
function textList ( filter, list )
{
const results = []
for ( let i = 0; i < list.length; i++ ) {
const item = list[ i ]
const originalIndex = item.originalIndex
const originalValue = item.originalValue
// get rid of unnecessary whitespace that only takes of
// valuable scren space
const normalizedItem = originalValue.split( /\s+/ ).join( ' ' )
/* matches is an array of indexes on the normalizedItem string
* that have matched the fuzz
*/
const matches = textMatches( filter, normalizedItem )
if ( matches.length === filter.length ) {
/* When the matches.length is exacly the same as filter.length
* it means we have a fuzzy match -> all characters in
* the filter string have been found on the normalizedItem string.
* The matches array holds each string index position
* of those matches on the normalizedItem string.
* ex. filter = 'foo', normalizedItem = 'dog food yum', matches = [4,5,6]
*/
let t = normalizedItem
results.push( {
originalIndex: originalIndex,
originalValue: originalValue,
matchedIndex: results.length,
original: item,
text: t // what shows up on terminal/screen
} )
}
}
return results
}
function colorIndexesOnText ( indexes, text, clcColor )
{
const paintBucket = [] // characters to colorize at the end
for ( let i = 0; i < indexes.length; i++ ) {
const index = indexes[ i ]
paintBucket.push( { index: index, clc: clcColor || clcFgMatchGreen } )
}
// copy match text colorize it based on the matches
// this variable with the colorized ANSI text will be
// returned at the end of the function
let t = text
// colorise in reverse because invisible ANSI color
// characters increases string length
paintBucket.sort( function ( a, b ) {
return b.index - a.index
} )
for ( let i = 0; i < paintBucket.length; i++ ) {
const paint = paintBucket[ i ]
const index = Number( paint.index )
// skip fuzzy chars that have shifted out of view
if ( index < 0 ) continue
if ( index > t.length ) continue
const c = paint.clc( t[ index ] )
t = t.slice( 0, index ) + c + t.slice( index + 1 )
}
// return the colorized match text
return t
}
function trimOnIndexes ( indexes, text )
{
let t = text
indexes = (
indexes.map( function ( i ) { return Number( i ) } )
)
indexes.sort() // sort indexes
// the last ( right-most ) index/character we want to be
// visible on screen as centered as possible until there are
// no more text to be shown to the right of it
const lastIndex = indexes[ indexes.length - 1 ]
const maxLen = getMaxWidth() - 2 // terminal width + padding
/* we want to show the user the last characters that matches
* as those are the most relevant
* ( and ignore earlier matches if they go off-screen )
*
* use the marginRight to shift the matched text left until
* the last characters that match are visible on the screen
*/
const marginRight = Math.ceil( stdout.columns * 0.4 )
// how wide the last index would be printed currently
const lastMatchLength = stringWidth( t.slice( 0, lastIndex ) )
// how much to shift left to get last index to get into
// marginRight range (almost center)
let shiftLeft = ( marginRight - lastMatchLength )
// [1] but not too much if there is no additional text
// const delta = ( stringWidth( t ) - lastMatchLength )
// if ( Math.abs( shiftLeft ) > delta ) shiftLeft = -Math.floor( delta * .5 )
let startIndex = 0
let shiftAmount = 0
if ( shiftLeft < 0 ) {
// we need to shift left so that the matched text in view
while ( shiftAmount > shiftLeft ) {
startIndex++
shiftAmount = -stringWidth( t.slice( 0, startIndex ) )
if ( startIndex >= t.length ) {
break // shouldn't happen because of [1]
}
}
startIndex = startIndex + scrollOffset
if ( startIndex < 0 ) {
startIndex = 0
}
t = t.slice( startIndex )
}
// console.log( 't.length: ' + t.length )
// console.log( 'shiftLeft: ' + shiftLeft )
// console.log( 'shiftamount: ' + shiftAmount )
// console.log( 'startindex: ' + startIndex )
// normalize excessive lengths to avoid too much while looping
// if ( t.length > ( maxLen * 2 + 20 ) ) t = t.slice( 0, maxLen * 2 + 20 )
/* Cut off from the end of the (visual) line until
* it fits on the terminal width screen.
*/
const tlen = t.length
let endIndex = t.length
while ( stringWidth( t ) > maxLen ) {
t = t.slice( 0, --endIndex )
if ( t.length <= 0 ) break
}
if ( startIndex > 0 ) {
t = '...' + t
}
if ( endIndex < tlen ) {
t = t + '...'
}
return {
text: t,
startOffset: startIndex ? ( startIndex - '...'.length ) : startIndex
}
}
function cleanDirtyScreen ()
{
const width = stdout.columns
const writtenHeight = Math.max(
MIN_HEIGHT,
2 + _printedMatches
)
stdout.write( clc.move( -width ) )
for ( let i = 0; i < writtenHeight; i++ ) {
stdout.write( clc.erase.line )
stdout.write( clc.move.down( 1 ) )
}
stdout.write( clc.move.up( writtenHeight ) )
}
function render ()
{
const width = stdout.columns || clc.windowSize.width
const height = stdout.rows || clc.windowSize.height
// console.log( 'window height: ' + height )
// !debug && stdout.write( clc.erase.screen )
// stdout.write( clc.move.to( 0, height ) )
cleanDirtyScreen()
// calculate matches
_matches = [] // reset matches
const words = buffer.split( /\s+/ ).filter( function ( word ) { return word.length > 0 } )
for ( let i = 0; i < words.length; i++ ) {
const word = words[ i ]
let list = _list // fuzzy match against all items in list
if ( i > 0 ) {
// if we already have matches, fuzzy match against
// those instead (combines the filters)
list = _matches
}
const matches = getList( _opts.mode, word, list )
_matches = matches
}
// special case no input ( show all with no matches )
if ( words.length === 0 ) {
const matches = getList( _opts.mode, '', _list )
_matches = matches
}
if ( selectedIndex >= _matches.length ) {
// max out at end of filtered/matched results
selectedIndex = _matches.length - 1
}
if ( selectedIndex < 0 ) {
// min out at beginning of filtered/matched results
selectedIndex = 0
}
const inputLabel = _opts.label || clcFgBufferArrow( '> ' )
const inputLabels = inputLabel.split( '\n' )
const lastInputLabel = inputLabels[ inputLabels.length - 1 ]
const inputLabelHeight = inputLabels.length - 1
if ( render.init ) {
stdout.write( clc.move.up( inputLabelHeight ) )
} else {
// get rid of dirt when being pushed above MIN_HEIGHT
// from the bottom of the terminal
cleanDirtyScreen()
}
render.init = true
// print input label
stdout.write( inputLabel )
stdout.write( buffer )
// do not print the list at all when `nolist` is set
// this is used when we only care about the input query
if ( !_opts.nolist ) {
stdout.write( '\n' )
/* Here we color the matched items text for terminal
* printing based on what characters were found/matched.
*
* Since each filter is separated by space we first
* combine all matches from all filters(words).
*
* If we want to only color based on the most recent
* filter (last word) then just use the matches from the
* last word.
*/
for ( let i = 0; i < _matches.length; i++ ) {
const match = _matches[ i ]
const words = buffer.split( /\s+/ ).filter( function ( word ) { return word.length > 0 } )
const indexMap = {} // as map to prevent duplicates indexes
for ( let i = 0; i < words.length; i++ ) {
const word = words[ i ]
const matches = getMatches( _opts.mode, word, match.text )
matches.forEach( function ( i ) {
indexMap[ i ] = true
} )
}
// trim and position text ( horizontally ) based on
// last word/filter that matched ( most relevant )
const lastWord = words[ words.length - 1 ] || ' '
const lastIndexes = getMatches( _opts.mode, lastWord, match.text )
const { text, startOffset } = trimOnIndexes( lastIndexes, match.text )
match.text = text
if ( words.length === 0 ) continue
const indexes = (
Object.keys( indexMap )
.map( function ( i ) { return Number( i ) - startOffset } )
)
indexes.sort() // sort indexes
// transform the text to a colorized version
match.text = colorIndexesOnText( indexes, match.text /*, clcFgGreen */ )
}
// print matches length vs original list length
const n = _matches.length
stdout.write( ' ' )
stdout.write( clcFgGreen( n + '/' + _list.length ) )
// TODO print mode
stdout.write( ' ' + clcFgModeStatus( _opts.mode + ' mode' ) )
// show mode switch suggestion
let suggestionColor = clc.blackBright
if ( n === 0 || n === _opts.list.length ) {
suggestionColor = clc.yellowBright
}
stdout.write( suggestionColor( ' ctrl-s to switch' ) )
stdout.write( ' ' + clc.magenta( `[${ scrollOffset > 0 ? '+' : '' }${ scrollOffset }]` ) )
stdout.write( '\n' )
// select first item in list by default ( empty fuzzy search matches first
// item.. )
if ( !_selectedItem ) {
_selectedItem = _matches[ 0 ]
}
// print the matches
_printedMatches = 0
// max lines to use for printing matched results
const maxPrintedLines = Math.min( _matches.length, MIN_HEIGHT )
let paddingBottom = 2 // 1 extra padding at the bottom when scrolling down
if ( _matches.length <= MIN_HEIGHT ) {
// no extra padding at the bottom since there is no room for it
// - othewise first match is cut off and will not be visible
paddingBottom = 1
}
// first matched result to print
const startIndex = Math.max( 0, selectedIndex - maxPrintedLines + paddingBottom )
// last matched result to print
const endIndex = Math.min( maxPrintedLines + startIndex, _matches.length )
// print matches
for ( let i = startIndex; i < endIndex; i++ ) {
_printedMatches++
const match = _matches[ i ]
const item = match.text
const itemSelected = (
( selectedIndex === i )
)
if ( itemSelected ) {
_selectedItem = match
stdout.write( clcBgGray( clcFgArrow( '> ' ) ) )
stdout.write( clcBgGray( item ) )
stdout.write( '\n' )
} else {
stdout.write( clcBgGray( ' ' ) )
stdout.write( ' ' )
stdout.write( item )
stdout.write( '\n' )
}
}
// move back to cursor position after printing matches
stdout.write( clc.move.up( 2 + _printedMatches ) )
}
if ( _printedMatches < 1 ) {
// clear selected item when nothing matches
_selectedItem = undefined
}
// if ( inputLabelHeight > 0 ) stdout.write( clc.move.up( inputLabelHeight ) )
// reset cursor left position
stdout.write( clc.move( -stdout.columns ) )
const cursorOffset = stringWidth( buffer.slice( 0, cursorPosition ) )
const cursorLeftPadding = stringWidth( lastInputLabel )
// set cursor left position
stdout.write( clc.move.right( cursorLeftPadding + cursorOffset ) )
}
stdin.setRawMode && stdin.setRawMode( true )
stdin.resume()
render()
} )
if ( !callback ) {
return promise
}
return _opts
}
// quick debugging, only executes when run with `node main.js`
if ( require.main === module ) {
;( async function () {
const r = await getInput( 'Name: ' )
console.log( r.query )
} )()
}

24
node_modules/node-fzf/usage.txt generated vendored Normal file
View file

@ -0,0 +1,24 @@
Usage: nfzf [options]
Options:
-n, --normal Use normal text matching instead of fuzzy matching.
-h, --help Display help (this text)
Keyboard:
down,<ctrl-j>,<ctrl-n> scroll down
up,<ctrl-k>,<ctrl-p> scroll up
<ctrl-d> scroll down by page size
<ctrl-u> scroll up by page size
<ctrl-a> jump to start of input
<ctrl-e> jump to end of input
<esc>,<ctrl-q>,<ctrl-c> cancel
<return>,<ctrl-m> trigger callback/promise with current selection and exit
<ctrl-w> delte last word from input
<ctrl-b> jump back a word
<ctrl-f> jump forward a word
<backspace><ctrl-h> delete last input character
<ctrl-s> switch between modes (fuzzy, normal)
Examples:
find . | nfzf
cat log.txt | nfzf -n