Skip to main content
Module

std/node/_readline.mjs

Deno standard library
Go to Latest
File
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.// Copyright Joyent, Inc. and other Node contributors.//// Permission is hereby granted, free of charge, to any person obtaining a// copy of this software and associated documentation files (the// "Software"), to deal in the Software without restriction, including// without limitation the rights to use, copy, modify, merge, publish,// distribute, sublicense, and/or sell copies of the Software, and to permit// persons to whom the Software is furnished to do so, subject to the// following conditions://// The above copyright notice and this permission notice shall be included// in all copies or substantial portions of the Software.//// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE// USE OR OTHER DEALINGS IN THE SOFTWARE.
// deno-lint-ignore-file camelcase
import { clearLine, clearScreenDown, cursorTo, moveCursor,} from "./internal/readline/callbacks.mjs";import { emitKeypressEvents } from "./internal/readline/emitKeypressEvents.mjs";import { validateAbortSignal } from "./internal/validators.mjs";import { promisify } from "./internal/util.mjs";import { AbortError } from "./internal/errors.ts";
import { Interface as _Interface, InterfaceConstructor, kAddHistory, kDecoder, kDeleteLeft, kDeleteLineLeft, kDeleteLineRight, kDeleteRight, kDeleteWordLeft, kDeleteWordRight, kGetDisplayPos, kHistoryNext, kHistoryPrev, kInsertString, kLine, kLine_buffer, kMoveCursor, kNormalWrite, kOldPrompt, kOnLine, kPreviousKey, kPrompt, kQuestionCallback, kQuestionCancel, kRefreshLine, kSawKeyPress, kSawReturnAt, kSetRawMode, kTabComplete, kTabCompleter, kTtyWrite, kWordLeft, kWordRight, kWriteToOutput,} from "./internal/readline/interface.mjs";
function Interface(input, output, completer, terminal) { if (!(this instanceof Interface)) { return new Interface(input, output, completer, terminal); }
if ( input?.input && typeof input.completer === "function" && input.completer.length !== 2 ) { const { completer } = input; input.completer = (v, cb) => cb(null, completer(v)); } else if (typeof completer === "function" && completer.length !== 2) { const realCompleter = completer; completer = (v, cb) => cb(null, realCompleter(v)); }
// NOTE(bartlomieju): in Node this is `FunctionPrototypeCall(...)`, // but trying to do `Function.prototype.call()` somehow doesn't work here // /shrug InterfaceConstructor.bind( this, )( input, output, completer, terminal, ); if (process.env.TERM === "dumb") { this._ttyWrite = _ttyWriteDumb.bind(this); }}
Object.setPrototypeOf(Interface.prototype, _Interface.prototype);Object.setPrototypeOf(Interface, _Interface);
const superQuestion = _Interface.prototype.question;
/** * Displays `query` by writing it to the `output`. * @param {string} query * @param {{ signal?: AbortSignal; }} [options] * @param {Function} cb * @returns {void} */Interface.prototype.question = function (query, options, cb) { cb = typeof options === "function" ? options : cb; options = typeof options === "object" && options !== null ? options : {};
if (options.signal) { validateAbortSignal(options.signal, "options.signal"); if (options.signal.aborted) { return; }
const onAbort = () => { this[kQuestionCancel](); }; options.signal.addEventListener("abort", onAbort, { once: true }); const cleanup = () => { options.signal.removeEventListener(onAbort); }; cb = typeof cb === "function" ? (answer) => { cleanup(); return cb(answer); } : cleanup; }
if (typeof cb === "function") { superQuestion.call(this, query, cb); }};Interface.prototype.question[promisify.custom] = function (query, options) { options = typeof options === "object" && options !== null ? options : {};
if (options.signal && options.signal.aborted) { return Promise.reject(new AbortError()); }
return new Promise((resolve, reject) => { let cb = resolve;
if (options.signal) { const onAbort = () => { reject(new AbortError()); }; options.signal.addEventListener("abort", onAbort, { once: true }); cb = (answer) => { options.signal.removeEventListener("abort", onAbort); resolve(answer); }; }
this.question(query, options, cb); });};
/** * Creates a new `readline.Interface` instance. * @param {Readable | { * input: Readable; * output: Writable; * completer?: Function; * terminal?: boolean; * history?: string[]; * historySize?: number; * removeHistoryDuplicates?: boolean; * prompt?: string; * crlfDelay?: number; * escapeCodeTimeout?: number; * tabSize?: number; * signal?: AbortSignal; * }} input * @param {Writable} [output] * @param {Function} [completer] * @param {boolean} [terminal] * @returns {Interface} */function createInterface(input, output, completer, terminal) { return new Interface(input, output, completer, terminal);}
Object.defineProperties(Interface.prototype, { // Redirect internal prototype methods to the underscore notation for backward // compatibility. [kSetRawMode]: { get() { return this._setRawMode; }, }, [kOnLine]: { get() { return this._onLine; }, }, [kWriteToOutput]: { get() { return this._writeToOutput; }, }, [kAddHistory]: { get() { return this._addHistory; }, }, [kRefreshLine]: { get() { return this._refreshLine; }, }, [kNormalWrite]: { get() { return this._normalWrite; }, }, [kInsertString]: { get() { return this._insertString; }, }, [kTabComplete]: { get() { return this._tabComplete; }, }, [kWordLeft]: { get() { return this._wordLeft; }, }, [kWordRight]: { get() { return this._wordRight; }, }, [kDeleteLeft]: { get() { return this._deleteLeft; }, }, [kDeleteRight]: { get() { return this._deleteRight; }, }, [kDeleteWordLeft]: { get() { return this._deleteWordLeft; }, }, [kDeleteWordRight]: { get() { return this._deleteWordRight; }, }, [kDeleteLineLeft]: { get() { return this._deleteLineLeft; }, }, [kDeleteLineRight]: { get() { return this._deleteLineRight; }, }, [kLine]: { get() { return this._line; }, }, [kHistoryNext]: { get() { return this._historyNext; }, }, [kHistoryPrev]: { get() { return this._historyPrev; }, }, [kGetDisplayPos]: { get() { return this._getDisplayPos; }, }, [kMoveCursor]: { get() { return this._moveCursor; }, }, [kTtyWrite]: { get() { return this._ttyWrite; }, },
// Defining proxies for the internal instance properties for backward // compatibility. _decoder: { get() { return this[kDecoder]; }, set(value) { this[kDecoder] = value; }, }, _line_buffer: { get() { return this[kLine_buffer]; }, set(value) { this[kLine_buffer] = value; }, }, _oldPrompt: { get() { return this[kOldPrompt]; }, set(value) { this[kOldPrompt] = value; }, }, _previousKey: { get() { return this[kPreviousKey]; }, set(value) { this[kPreviousKey] = value; }, }, _prompt: { get() { return this[kPrompt]; }, set(value) { this[kPrompt] = value; }, }, _questionCallback: { get() { return this[kQuestionCallback]; }, set(value) { this[kQuestionCallback] = value; }, }, _sawKeyPress: { get() { return this[kSawKeyPress]; }, set(value) { this[kSawKeyPress] = value; }, }, _sawReturnAt: { get() { return this[kSawReturnAt]; }, set(value) { this[kSawReturnAt] = value; }, },});
// Make internal methods public for backward compatibility.Interface.prototype._setRawMode = _Interface.prototype[kSetRawMode];Interface.prototype._onLine = _Interface.prototype[kOnLine];Interface.prototype._writeToOutput = _Interface.prototype[kWriteToOutput];Interface.prototype._addHistory = _Interface.prototype[kAddHistory];Interface.prototype._refreshLine = _Interface.prototype[kRefreshLine];Interface.prototype._normalWrite = _Interface.prototype[kNormalWrite];Interface.prototype._insertString = _Interface.prototype[kInsertString];Interface.prototype._tabComplete = function (lastKeypressWasTab) { // Overriding parent method because `this.completer` in the legacy // implementation takes a callback instead of being an async function. this.pause(); const string = this.line.slice(0, this.cursor); this.completer(string, (err, value) => { this.resume();
if (err) { // TODO(bartlomieju): inspect is not ported yet // this._writeToOutput(`Tab completion error: ${inspect(err)}`); this._writeToOutput(`Tab completion error: ${err}`); return; }
this[kTabCompleter](lastKeypressWasTab, value); });};Interface.prototype._wordLeft = _Interface.prototype[kWordLeft];Interface.prototype._wordRight = _Interface.prototype[kWordRight];Interface.prototype._deleteLeft = _Interface.prototype[kDeleteLeft];Interface.prototype._deleteRight = _Interface.prototype[kDeleteRight];Interface.prototype._deleteWordLeft = _Interface.prototype[kDeleteWordLeft];Interface.prototype._deleteWordRight = _Interface.prototype[kDeleteWordRight];Interface.prototype._deleteLineLeft = _Interface.prototype[kDeleteLineLeft];Interface.prototype._deleteLineRight = _Interface.prototype[kDeleteLineRight];Interface.prototype._line = _Interface.prototype[kLine];Interface.prototype._historyNext = _Interface.prototype[kHistoryNext];Interface.prototype._historyPrev = _Interface.prototype[kHistoryPrev];Interface.prototype._getDisplayPos = _Interface.prototype[kGetDisplayPos];Interface.prototype._getCursorPos = _Interface.prototype.getCursorPos;Interface.prototype._moveCursor = _Interface.prototype[kMoveCursor];Interface.prototype._ttyWrite = _Interface.prototype[kTtyWrite];
function _ttyWriteDumb(s, key) { key = key || {};
if (key.name === "escape") return;
if (this[kSawReturnAt] && key.name !== "enter") { this[kSawReturnAt] = 0; }
if (key.ctrl) { if (key.name === "c") { if (this.listenerCount("SIGINT") > 0) { this.emit("SIGINT"); } else { // This readline instance is finished this.close(); }
return; } else if (key.name === "d") { this.close(); return; } }
switch (key.name) { case "return": // Carriage return, i.e. \r this[kSawReturnAt] = Date.now(); this._line(); break;
case "enter": // When key interval > crlfDelay if ( this[kSawReturnAt] === 0 || Date.now() - this[kSawReturnAt] > this.crlfDelay ) { this._line(); } this[kSawReturnAt] = 0; break;
default: if (typeof s === "string" && s) { this.line += s; this.cursor += s.length; this._writeToOutput(s); } }}
export { clearLine, clearScreenDown, createInterface, cursorTo, emitKeypressEvents, Interface, moveCursor,};