snippet.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. 'use strict';
  2. const colors = require('ansi-colors');
  3. const interpolate = require('../interpolate');
  4. const Prompt = require('../prompt');
  5. class SnippetPrompt extends Prompt {
  6. constructor(options) {
  7. super(options);
  8. this.cursorHide();
  9. this.reset(true);
  10. }
  11. async initialize() {
  12. this.interpolate = await interpolate(this);
  13. await super.initialize();
  14. }
  15. async reset(first) {
  16. this.state.keys = [];
  17. this.state.invalid = new Map();
  18. this.state.missing = new Set();
  19. this.state.completed = 0;
  20. this.state.values = {};
  21. if (first !== true) {
  22. await this.initialize();
  23. await this.render();
  24. }
  25. }
  26. moveCursor(n) {
  27. let item = this.getItem();
  28. this.cursor += n;
  29. item.cursor += n;
  30. }
  31. dispatch(ch, key) {
  32. if (!key.code && !key.ctrl && ch != null && this.getItem()) {
  33. this.append(ch, key);
  34. return;
  35. }
  36. this.alert();
  37. }
  38. append(ch, key) {
  39. let item = this.getItem();
  40. let prefix = item.input.slice(0, this.cursor);
  41. let suffix = item.input.slice(this.cursor);
  42. this.input = item.input = `${prefix}${ch}${suffix}`;
  43. this.moveCursor(1);
  44. this.render();
  45. }
  46. delete() {
  47. let item = this.getItem();
  48. if (this.cursor <= 0 || !item.input) return this.alert();
  49. let suffix = item.input.slice(this.cursor);
  50. let prefix = item.input.slice(0, this.cursor - 1);
  51. this.input = item.input = `${prefix}${suffix}`;
  52. this.moveCursor(-1);
  53. this.render();
  54. }
  55. increment(i) {
  56. return i >= this.state.keys.length - 1 ? 0 : i + 1;
  57. }
  58. decrement(i) {
  59. return i <= 0 ? this.state.keys.length - 1 : i - 1;
  60. }
  61. first() {
  62. this.state.index = 0;
  63. this.render();
  64. }
  65. last() {
  66. this.state.index = this.state.keys.length - 1;
  67. this.render();
  68. }
  69. right() {
  70. if (this.cursor >= this.input.length) return this.alert();
  71. this.moveCursor(1);
  72. this.render();
  73. }
  74. left() {
  75. if (this.cursor <= 0) return this.alert();
  76. this.moveCursor(-1);
  77. this.render();
  78. }
  79. prev() {
  80. this.state.index = this.decrement(this.state.index);
  81. this.getItem();
  82. this.render();
  83. }
  84. next() {
  85. this.state.index = this.increment(this.state.index);
  86. this.getItem();
  87. this.render();
  88. }
  89. up() {
  90. this.prev();
  91. }
  92. down() {
  93. this.next();
  94. }
  95. format(value) {
  96. let color = this.state.completed < 100 ? this.styles.warning : this.styles.success;
  97. if (this.state.submitted === true && this.state.completed !== 100) {
  98. color = this.styles.danger;
  99. }
  100. return color(`${this.state.completed}% completed`);
  101. }
  102. async render() {
  103. let { index, keys = [], submitted, size } = this.state;
  104. let newline = [this.options.newline, '\n'].find(v => v != null);
  105. let prefix = await this.prefix();
  106. let separator = await this.separator();
  107. let message = await this.message();
  108. let prompt = [prefix, message, separator].filter(Boolean).join(' ');
  109. this.state.prompt = prompt;
  110. let header = await this.header();
  111. let error = (await this.error()) || '';
  112. let hint = (await this.hint()) || '';
  113. let body = submitted ? '' : await this.interpolate(this.state);
  114. let key = this.state.key = keys[index] || '';
  115. let input = await this.format(key);
  116. let footer = await this.footer();
  117. if (input) prompt += ' ' + input;
  118. if (hint && !input && this.state.completed === 0) prompt += ' ' + hint;
  119. this.clear(size);
  120. let lines = [header, prompt, body, footer, error.trim()];
  121. this.write(lines.filter(Boolean).join(newline));
  122. this.restore();
  123. }
  124. getItem(name) {
  125. let { items, keys, index } = this.state;
  126. let item = items.find(ch => ch.name === keys[index]);
  127. if (item && item.input != null) {
  128. this.input = item.input;
  129. this.cursor = item.cursor;
  130. }
  131. return item;
  132. }
  133. async submit() {
  134. if (typeof this.interpolate !== 'function') await this.initialize();
  135. await this.interpolate(this.state, true);
  136. let { invalid, missing, output, values } = this.state;
  137. if (invalid.size) {
  138. let err = '';
  139. for (let [key, value] of invalid) err += `Invalid ${key}: ${value}\n`;
  140. this.state.error = err;
  141. return super.submit();
  142. }
  143. if (missing.size) {
  144. this.state.error = 'Required: ' + [...missing.keys()].join(', ');
  145. return super.submit();
  146. }
  147. let lines = colors.unstyle(output).split('\n');
  148. let result = lines.map(v => v.slice(1)).join('\n');
  149. this.value = { values, result };
  150. return super.submit();
  151. }
  152. }
  153. module.exports = SnippetPrompt;