UNPKG

8.98 kBJavaScriptView Raw
1"use strict";
2
3/**
4 * Module dependencies.
5 */
6
7var _ = require("lodash")
8 , inquirer = require("inquirer")
9 , EventEmitter = require("events").EventEmitter
10 , VantageUtil = require("./util")
11 ;
12
13var ui = {
14
15 /**
16 * Sets intial variables and registers
17 * listeners. This is called once in a
18 * process thread regardless of how many
19 * instances of Vantage have been generated.
20 *
21 * @api private
22 */
23
24 _init: function() {
25
26 var self = this;
27
28 // Attached vantage instance. The UI can
29 // only attach to one instance of Vantage
30 // at a time, and directs all events to that
31 // instance.
32 this.parent = void 0;
33
34 // Hook to reference active inquirer prompt.
35 this._activePrompt = void 0;
36
37 // Fail-safe to ensure there is no double
38 // prompt in odd situations.
39 this._midPrompt = false;
40
41 // Handle for inquirer's prompt.
42 this.inquirer = inquirer;
43
44 // Whether a prompt is currently in cancel mode.
45 this._cancelled = false;
46
47 // Middleware for piping stdout through.
48 this._pipeFn = void 0;
49
50 // Hook in to steal inquirer's keypress.
51 inquirer.prompt.prompts.input.prototype.onKeypress = function(e) {
52 self.emit("client_keypress", e);
53 return self._keypressHandler(e, this);
54 };
55
56 // Extend the render function to steal the active prompt object,
57 // as inquirer doesn't expose it and we need it.
58 (function(render){
59 inquirer.prompt.prompts.input.prototype.render = function() {
60 self._activePrompt = this;
61 return render.call(this);
62 };
63 })(inquirer.prompt.prompts.input.prototype.render);
64 },
65
66 /**
67 * Creates an inquirer prompt on the TTY.
68 *
69 * @param {Object} options
70 * @param {Function} cb
71 * @api public
72 */
73
74 prompt: function(options, cb) {
75 var self = this;
76 options = options || {};
77 if (!this.parent) { return; }
78 if (options.delimiter) {
79 this.setDelimiter(options.delimiter);
80 }
81 if (options.message) {
82 this.setDelimiter(options.message);
83 }
84 if (self._midPrompt) {
85 console.log("Prompt called when mid prompt...");
86 throw new Error("UI Prompt called when already mid prompt.");
87 }
88 self._midPrompt = true;
89 try {
90 inquirer.prompt(options, function(result) {
91 self._midPrompt = false;
92 cb(result);
93 });
94 } catch(e) {
95 console.log("Vantage Prompt error:", e);
96 }
97 },
98
99 /**
100 * Returns a boolean as to whether user
101 * is mid another pr ompt.
102 *
103 * @return {Boolean}
104 * @api public
105 */
106
107 midPrompt: function() {
108 return (
109 (this._midPrompt && this.parent) ? true : false
110 );
111 },
112
113 /**
114 * Sets the temporarily delimiter based
115 * on the delimiter provided by another
116 * vantage server to this instance's client
117 * upon the establishment of a session.
118 *
119 * @param {String} str
120 * @api public
121 */
122
123 setDelimiter: function(str) {
124 if (!this.parent) { return; }
125 str = String(str).trim() + " ";
126 this._lastDelimiter = str;
127 inquirer.prompt.prompts.password.prototype.prefix = function(){
128 return str;
129 };
130 inquirer.prompt.prompts.input.prototype.prefix = function(){
131 return str;
132 };
133 },
134
135 /**
136 * Event handler for keypresses - deals with command history
137 * and tabbed auto-completion.
138 *
139 * @param {Event} e
140 * @param {Prompt} prompt
141 * @api private
142 */
143
144 _keypressHandler: function(e, prompt) {
145 if (!this.parent) { return; }
146 this._activePrompt = prompt;
147 var key = (e.key || {}).name;
148 var value = (prompt) ? String(prompt.rl.line).trim() : void 0;
149 this.emit("vantage_ui_keypress", { key: key, value: value });
150 },
151
152 /**
153 * Refreshes active prompt.
154 *
155 * @return {UI}
156 * @api public
157 */
158
159 refresh: function() {
160 if (!this.parent) { return false; }
161 if (!this._activePrompt) { return false; }
162 if (!this._midPrompt) { return false; }
163 this._activePrompt.clean();
164 this._midPrompt = false;
165 this._cancelled = true;
166 if (this._activePrompt.status !== "answered") { // huh?
167 this._activePrompt.status = "answered";
168 this._activePrompt.done();
169 }
170 this._cancelled = false;
171 this.parent._prompt();
172 return this;
173 },
174
175 /**
176 * Pauses active prompt, returning
177 * the value of what had been typed so far.
178 *
179 * @return {String} val
180 * @api public
181 */
182
183 pause: function() {
184 if (!this.parent) { return false; }
185 if (!this._activePrompt) { return false; }
186 if (!this._midPrompt) { return false; }
187 var val = this._activePrompt.rl.line;
188 this._midPrompt = false;
189 this._cancelled = true;
190 this._activePrompt.clean();
191 this._activePrompt.status = "answered";
192 this._activePrompt.done();
193 return val;
194 },
195
196 /**
197 * Resumes active prompt, accepting
198 * a string, which will fill the prompt
199 * with that text and put the cursor at
200 * the end.
201 *
202 * @param {String} val
203 * @api public
204 */
205
206 resume: function(val) {
207 if (!this.parent) { return this; }
208 val = val || "";
209 if (!this._activePrompt) { return this; }
210 if (this._midPrompt) { return this; }
211 this.parent._prompt();
212 this._activePrompt.rl.line = val;
213 this._activePrompt.rl.cursor = val.length;
214 this._activePrompt.cacheCursorPos();
215 this._activePrompt.clean().render().write( this._activePrompt.rl.line );
216 this._activePrompt.restoreCursorPos();
217 return this;
218 },
219
220 /**
221 * Logs the current delimiter and typed data.
222 *
223 * @return {UI}
224 * @api public
225 */
226
227 imprint: function(str) {
228 if (!this.parent) { return this; }
229 var val = this._activePrompt.rl.line;
230 var delimiter = this._lastDelimiter || "";
231 this.log(delimiter + val);
232 return this;
233 },
234
235 /**
236 * Redraws the inquirer prompt with a new string.
237 *
238 * @param {String} str
239 * @return {UI}
240 * @api private
241 */
242
243 redraw: function(str) {
244 if (!this.parent) { return this; }
245 this._activePrompt.rl.line = str;
246 this._activePrompt.rl.cursor = str.length;
247 this._activePrompt.cacheCursorPos();
248 this._activePrompt.clean().render().write( this._activePrompt.rl.line );
249 this._activePrompt.restoreCursorPos();
250 return this;
251 },
252
253 /**
254 * Attaches TTY prompt to a given Vantage instance.
255 *
256 * @param {Vantage} vantage
257 * @return {UI}
258 * @api public
259 */
260
261 attach: function(vantage) {
262 this.parent = vantage;
263 this.refresh();
264 this.parent._prompt();
265 return this;
266 },
267
268 /**
269 * Receives and runs logging through
270 * a piped function is one is provided
271 * through ui.pipe(). Pauses any active
272 * prompts, logs the data and then if
273 * paused, resumes the prompt.
274 *
275 * @return {UI}
276 * @api public
277 */
278
279 log: function() {
280 var args = VantageUtil.fixArgsForApply(arguments);
281 args = (_.isFunction(this._pipeFn))
282 ? this._pipeFn.call(this, args)
283 : args;
284 if (args === "") {
285 return this;
286 }
287 args = VantageUtil.fixArgsForApply(args);
288 if (this.midPrompt()) {
289 var data = this.pause();
290 console.log.apply(console.log, args);
291 if (typeof data !== "undefined" && data !== false) {
292 this.resume(data);
293 } else {
294 console.log("Log got back 'false' as data. This shouldn't happen.", data);
295 }
296 } else {
297 console.log.apply(console.log, args);
298 }
299 return this;
300 },
301
302 /**
303 * Detaches UI from a given Vantage instance.
304 *
305 * @param {Vantage} vantage
306 * @return {UI}
307 * @api public
308 */
309
310 detach: function(vantage) {
311 if (vantage === this.parent) {
312 this.parent = void 0;
313 }
314 return this;
315 }
316};
317
318/**
319 * Make UI an EventEmitter.
320 */
321
322_.assign(ui, EventEmitter.prototype);
323
324/**
325 * Expose `ui`.
326 *
327 * Modifying global? WTF?!? Yes. It is evil.
328 * However node.js prompts are also quite
329 * evil in a way. Nothing prevents dual prompts
330 * between applications in the same terminal,
331 * and inquirer doesn't catch or deal with this, so
332 * if you want to start two independent instances of
333 * vantage, you need to know that prompt listeners
334 * have already been initiated, and that you can
335 * only attach the tty to one vantage instance
336 * at a time.
337 * When you fire inqurier twice, you get a double-prompt,
338 * where every keypress fires twice and it's just a
339 * total mess. So forgive me.
340 */
341
342global.__vantage = global.__vantage || {};
343global.__vantage.ui = global.__vantage.ui || {
344 exists: false,
345 exports: void 0
346};
347
348if (!global.__vantage.ui.exists) {
349 global.__vantage.ui.exists = true;
350 global.__vantage.ui.exports = ui;
351 module.exports = exports = ui;
352 ui._init();
353} else {
354 module.exports = global.__vantage.ui.exports;
355}