autocomplete.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. 'use strict';
  2. const Select = require('./select');
  3. const highlight = (input, color) => {
  4. const regex = input ? new RegExp(input, 'ig') : /$^/;
  5. return str => {
  6. return input ? str.replace(regex, match => color(match)) : str;
  7. };
  8. };
  9. class AutoComplete extends Select {
  10. constructor(options) {
  11. super(options);
  12. this.cursorShow();
  13. }
  14. moveCursor(n) {
  15. this.state.cursor += n;
  16. }
  17. dispatch(ch) {
  18. return this.append(ch);
  19. }
  20. space(ch) {
  21. return this.options.multiple ? super.space(ch) : this.append(ch);
  22. }
  23. append(ch) {
  24. let { cursor, input } = this.state;
  25. this.input = input.slice(0, cursor) + ch + input.slice(cursor);
  26. this.moveCursor(1);
  27. return this.complete();
  28. }
  29. delete() {
  30. let { cursor, input } = this.state;
  31. if (!input) return this.alert();
  32. this.input = input.slice(0, cursor - 1) + input.slice(cursor);
  33. this.moveCursor(-1);
  34. return this.complete();
  35. }
  36. deleteForward() {
  37. let { cursor, input } = this.state;
  38. if (input[cursor] === void 0) return this.alert();
  39. this.input = `${input}`.slice(0, cursor) + `${input}`.slice(cursor + 1);
  40. return this.complete();
  41. }
  42. number(ch) {
  43. return this.append(ch);
  44. }
  45. async complete() {
  46. this.completing = true;
  47. this.choices = await this.suggest(this.input, this.state._choices);
  48. this.state.limit = void 0; // allow getter/setter to reset limit
  49. this.index = Math.min(Math.max(this.visible.length - 1, 0), this.index);
  50. await this.render();
  51. this.completing = false;
  52. }
  53. suggest(input = this.input, choices = this.state._choices) {
  54. if (typeof this.options.suggest === 'function') {
  55. return this.options.suggest.call(this, input, choices);
  56. }
  57. let str = input.toLowerCase();
  58. return choices.filter(ch => ch.message.toLowerCase().includes(str));
  59. }
  60. pointer() {
  61. return '';
  62. }
  63. format() {
  64. if (!this.focused) return this.input;
  65. if (this.options.multiple && this.state.submitted) {
  66. return this.selected.map(ch => this.styles.primary(ch.message)).join(', ');
  67. }
  68. if (this.state.submitted) {
  69. let value = this.value = this.input = this.focused.value;
  70. return this.styles.primary(value);
  71. }
  72. return this.input;
  73. }
  74. async render() {
  75. if (this.state.status !== 'pending') return super.render();
  76. const hl = this.options.highlight || this.styles.complement;
  77. const style = (input, color) => {
  78. if (!input) return input;
  79. if (hl.stack) return hl(input);
  80. return hl.call(this, input);
  81. };
  82. const color = highlight(this.input, style);
  83. const choices = this.choices;
  84. this.choices = choices.map(ch => ({ ...ch, message: color(ch.message) }));
  85. await super.render();
  86. this.choices = choices;
  87. }
  88. submit() {
  89. if (this.options.multiple) {
  90. this.value = this.selected.map(ch => ch.name);
  91. }
  92. return super.submit();
  93. }
  94. }
  95. module.exports = AutoComplete;