├── .gitignore ├── .travis.yml ├── .editorconfig ├── test.coffee ├── license.md ├── package.json ├── example.js ├── readme.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | 4 | .nvm-version 5 | node_modules 6 | npm-debug.log 7 | 8 | /package-lock.json 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 'stable' 5 | - '7' 6 | - '6' 7 | - '5' 8 | - '4' 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | # Use tabs in JavaScript, CoffeeScript and JSON. 14 | [**.{coffee,js,json}] 15 | indent_style = tab 16 | indent_size = 4 17 | 18 | # Use spaces in YAML. 19 | [**.{yml,yaml}] 20 | indent_style = spaces 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /test.coffee: -------------------------------------------------------------------------------- 1 | prompt = require './index.js' 2 | stream = require 'stream' 3 | sink = require 'stream-sink' 4 | 5 | pass = require 'pass-stream' 6 | 7 | module.exports = 8 | 9 | 'prints to stdout': (t) -> 10 | # stdin = new stream.PassThrough() 11 | stdin = pass() 12 | stdout = sink() 13 | # p = prompt 'foo', {stdin, stdout, suggest: -> ['bar']} 14 | p = prompt 'foo', {stdin, stdout: process.stdout, suggest: -> ['bar']} 15 | stdin.write '_\n' # send any char 16 | stdout.on 'data', console.error 17 | t.done() 18 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Jannis R 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cli-autocomplete", 3 | "description": "A command line prompt with autocompletion.", 4 | "version": "0.4.3", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "keywords": [ 10 | "command", 11 | "line", 12 | "cli", 13 | "prompt", 14 | "autocomplete", 15 | "completion", 16 | "prompt" 17 | ], 18 | "author": "Jannis R ", 19 | "homepage": "https://github.com/derhuerst/cli-autocomplete", 20 | "repository": "git://github.com/derhuerst/cli-autocomplete.git", 21 | "license": "ISC", 22 | "engines": { 23 | "node": ">=4" 24 | }, 25 | "dependencies": { 26 | "ansi-escapes": "^2.0.0", 27 | "chalk": "^1.1.1", 28 | "cli-styles": "^0.6.2", 29 | "prompt-skeleton": "^0.4.0", 30 | "so": "^1.0.1", 31 | "strip-ansi": "^3.0.1" 32 | }, 33 | "devDependencies": { 34 | "more-words": "^1.0.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const words = require('more-words') 4 | const autocompletePrompt = require('./index') 5 | 6 | const data = words.map((word) => ({title: word, value: word})) 7 | 8 | const suggestWords = (input) => new Promise((yay) => { 9 | setTimeout(() => { 10 | const words = data 11 | .filter((d) => d.title.slice(0, input.length) === input) 12 | // .map((d, i) => { 13 | // if (i === 3) return Object.assign({}, d, { 14 | // title: d.title + 'f'.repeat(70) 15 | // }) 16 | // else if (i === 4) return Object.assign({}, d, { 17 | // title: d.title + 'f'.repeat(80) 18 | // }) 19 | // return d 20 | // }) 21 | yay(words) 22 | }, 10) 23 | }) 24 | 25 | autocompletePrompt('What is your favorite color?', suggestWords) 26 | // .on('data', (e) => console.log('Interim value', e.value)) 27 | .on('abort', (v) => console.log('Aborted with', v)) 28 | .on('submit', (v) => console.log('Submitted with', v)) 29 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # cli-autocomplete 2 | 3 | **Deprecated. Use the [autocomplete prompt](https://github.com/enquirer/enquirer#autocomplete-prompt) from [`enquirer`](https://github.com/enquirer/enquirer).** 4 | 5 | --- 6 | 7 | A command line prompt with autocompletion. It provides just the ui, you are responsible for relevant completions. 8 | 9 | [![asciicast](https://asciinema.org/a/82643.png)](https://asciinema.org/a/82643) 10 | 11 | [![npm version](https://img.shields.io/npm/v/cli-autocomplete.svg)](https://www.npmjs.com/package/cli-autocomplete) 12 | [![build status](https://img.shields.io/travis/derhuerst/cli-autocomplete.svg)](https://travis-ci.org/derhuerst/cli-autocomplete) 13 | [![dependency status](https://img.shields.io/david/derhuerst/cli-autocomplete.svg)](https://david-dm.org/derhuerst/cli-autocomplete#info=dependencies) 14 | [![dev dependency status](https://img.shields.io/david/dev/derhuerst/cli-autocomplete.svg)](https://david-dm.org/derhuerst/cli-autocomplete#info=devDependencies) 15 | ![ISC-licensed](https://img.shields.io/github/license/derhuerst/cli-autocomplete.svg) 16 | [![chat on gitter](https://badges.gitter.im/derhuerst.svg)](https://gitter.im/derhuerst) 17 | 18 | *cli-autocomplete* uses [*cli-styles*](https://github.com/derhuerst/cli-styles) and [*prompt-skeleton*](https://github.com/derhuerst/prompt-skeleton) to have a look & feel consistent with [other prompts](https://github.com/derhuerst/prompt-skeleton#prompts-using-prompt-skeleton). 19 | 20 | 21 | ## Installing 22 | 23 | ``` 24 | npm install cli-autocomplete 25 | ``` 26 | 27 | 28 | ## Usage 29 | 30 | ```js 31 | const autocompletePrompt = require('cli-autocomplete') 32 | 33 | const colors = [ 34 | {title: 'red', value: '#f00'}, 35 | {title: 'yellow', value: '#ff0'}, 36 | {title: 'green', value: '#0f0'}, 37 | {title: 'blue', value: '#00f'}, 38 | {title: 'black', value: '#000'}, 39 | {title: 'white', value: '#fff'} 40 | ] 41 | const suggestColors = (input) => Promise.resolve(colors 42 | .filter((color) => color.title.slice(0, input.length) === input)) 43 | 44 | autocompletePrompt('What is your favorite color?', suggestColors) 45 | .on('data', (e) => console.log('Interim value', e.value)) 46 | .on('abort', (v) => console.log('Aborted with', v)) 47 | .on('submit', (v) => console.log('Submitted with', v)) 48 | ``` 49 | 50 | 51 | ## Related 52 | 53 | - [`date-prompt`](https://github.com/derhuerst/date-prompt) 54 | - [`mail-prompt`](https://github.com/derhuerst/mail-prompt) 55 | - [`multiselect-prompt`](https://github.com/derhuerst/multiselect-prompt) 56 | - [`range-prompt`](https://github.com/derhuerst/range-prompt) 57 | - [`tree-select-prompt`](https://github.com/derhuerst/tree-select-prompt) 58 | 59 | 60 | ## Contributing 61 | 62 | If you **have a question**, **found a bug** or want to **propose a feature**, have a look at [the issues page](https://github.com/derhuerst/cli-autocomplete/issues). 63 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ui = require('cli-styles') 4 | const esc = require('ansi-escapes') 5 | const chalk = require('chalk') 6 | const strip = require('strip-ansi') 7 | const wrap = require('prompt-skeleton') 8 | 9 | 10 | 11 | const AutocompletePrompt = { 12 | 13 | moveCursor: function (i) { 14 | this.cursor = i 15 | if (this.suggestions.length > 0) this.value = this.suggestions[i].value 16 | else this.value = null 17 | this.emit() 18 | } 19 | 20 | , complete: function (cb) { 21 | const self = this 22 | const p = this.completing = this.suggest(this.input) 23 | p.then((suggestions) => { 24 | if (this.completing !== p) return 25 | 26 | self.suggestions = suggestions 27 | .slice(0, self.limit) 28 | .map((s) => strip(s)) 29 | this.completing = false 30 | 31 | const l = Math.max(suggestions.length - 1, 0) 32 | self.moveCursor(Math.min(l, self.cursor)) 33 | 34 | if (cb) cb() 35 | }).catch((err) => { 36 | self.emit('error', err) 37 | }) 38 | } 39 | 40 | , reset: function () { 41 | const self = this 42 | this.input = '' 43 | this.complete(() => { 44 | self.moveCursor(0) 45 | self.render() 46 | }) 47 | this.render() 48 | } 49 | 50 | , abort: function () { 51 | this.done = this.aborted = true 52 | this.emit() 53 | this.render() 54 | this.out.write('\n') 55 | this.close() 56 | } 57 | 58 | , submit: function () { 59 | this.done = true 60 | this.aborted = false 61 | this.emit() 62 | this.render() 63 | this.out.write('\n') 64 | this.close() 65 | } 66 | 67 | 68 | 69 | , _: function (c) { 70 | const self = this 71 | this.input += c 72 | this.complete(() => { 73 | self.render() 74 | }) 75 | this.render() 76 | } 77 | , delete: function () { 78 | if (this.input.length === 0) return this.bell() 79 | const self = this 80 | this.input = this.input.slice(0, -1) 81 | this.complete(() => { 82 | self.render() 83 | }) 84 | this.render() 85 | } 86 | 87 | 88 | 89 | , first: function () { 90 | this.moveCursor(0) 91 | this.render() 92 | } 93 | , last: function () { 94 | this.moveCursor(this.suggestions.length - 1) 95 | this.render() 96 | } 97 | 98 | , up: function () { 99 | if (this.cursor <= 0) return this.bell() 100 | this.moveCursor(this.cursor - 1) 101 | this.render() 102 | } 103 | , down: function () { 104 | if (this.cursor >= (this.suggestions.length - 1)) return this.bell() 105 | this.moveCursor(this.cursor + 1) 106 | this.render() 107 | } 108 | , next: function () { 109 | this.moveCursor((this.cursor + 1) % this.suggestions.length) 110 | this.render() 111 | } 112 | 113 | 114 | 115 | , lastRendered: '' 116 | 117 | , render: function () { 118 | let prompt = [ 119 | ui.symbol(this.done, this.aborted), 120 | this.msg, 121 | ui.delimiter(this.completing), 122 | this.done && this.suggestions[this.cursor] 123 | ? this.suggestions[this.cursor].title 124 | : this.transform(this.input) 125 | ].join(' ') 126 | 127 | if (!this.done) { 128 | prompt += esc.cursorSavePosition 129 | for (let i = 0; i < this.suggestions.length; i++) { 130 | const s = this.suggestions[i] 131 | prompt += '\n' + (i === this.cursor ? chalk.cyan(s.title) : s.title) 132 | } 133 | prompt += esc.cursorRestorePosition 134 | } 135 | 136 | this.out.write(ui.clear(this.lastRendered) + prompt) 137 | this.lastRendered = prompt 138 | } 139 | } 140 | 141 | 142 | 143 | const defaults = { 144 | input: '' 145 | , transform: ui.render() 146 | 147 | , suggestions: [] 148 | , limit: 10 149 | , completing: null 150 | , cursor: 0 151 | , value: null 152 | 153 | , done: false 154 | , aborted: false 155 | } 156 | 157 | const autocompletePrompt = (msg, suggest, opt) => { 158 | if ('string' !== typeof msg) 159 | throw new Error('Message must be a string.') 160 | if ('function' !== typeof suggest) 161 | throw new Error('Suggest must be a function.') 162 | if (Array.isArray(opt) || 'object' !== typeof opt) opt = {} 163 | 164 | let p = Object.assign(Object.create(AutocompletePrompt), defaults, opt) 165 | p.msg = msg 166 | p.suggest = suggest 167 | p.complete(() => p.render()) 168 | 169 | return wrap(p) 170 | } 171 | 172 | 173 | 174 | module.exports = Object.assign(autocompletePrompt, {AutocompletePrompt}) 175 | --------------------------------------------------------------------------------