UNPKG

less-pager-mini

Version:

A scrollable terminal pager for Node.js CLI apps (terminal only)

271 lines (270 loc) 8.82 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isStyled = exports.isAscii = exports.maxSubRow = void 0; exports.bufferToNum = bufferToNum; exports.inputToFilePaths = inputToFilePaths; exports.inputToString = inputToString; exports.ringBell = ringBell; exports.formatContent = formatContent; exports.addBufferChar = addBufferChar; exports.delBufferChar = delBufferChar; exports.render = render; exports.visualWidth = visualWidth; const fs_1 = __importDefault(require("fs")); const wcwidth_1 = __importDefault(require("wcwidth")); const config_1 = require("./config"); const chopLongLines_1 = require("./chopLongLines"); const wrapLongLines_1 = require("./wrapLongLines"); const constants_1 = require("./constants"); /** * Returns how many extra sub-rows a line will take if it overflows screen * width. * * - Returns 0 if line-chopping is enabled. * - Otherwise, calculates how many full rows the visual width spans. * * @param line - The string to measure. * @returns Number of sub-rows needed to display the line. */ const maxSubRow = (line) => config_1.config.chopLongLines ? 0 : Math.floor(visualWidth(line) / config_1.config.screenWidth); exports.maxSubRow = maxSubRow; /** * Converts a buffer string to a number. * * - Parses the string as a base-10 integer. * - Returns 0 if the input is not a valid number or equals 0. * * @param buffer - The string array to convert. * @returns Parsed numeric value, or 0 if invalid. */ function bufferToNum(buffer) { const n = parseInt(buffer.join(''), 10); return n ? n : 0; } /** * Normalizes unknown input into an array of valid file paths. * * - Accepts a string, an array, or nested arrays containing strings. * - Filters out non-string values and paths that do not exist on the * filesystem. * * @param input - A potential file path, array of paths, or nested arrays. * @returns An array of existing file paths. */ function inputToFilePaths(input) { if (Array.isArray(input)) { return input .flat(Infinity) .filter(path => typeof path === 'string' && fs_1.default.existsSync(path)); } if (typeof input === 'string' && fs_1.default.existsSync(input)) { return [input]; } return []; } /** * Converts any input to a string array. * * - Strings and primitives are split by newline. * - Objects are stringified with optional formatting. * * @param input - Value to convert. * @param preserveFormat - Whether to keep original formatting. * @returns - Array of strings representing the input. */ function inputToString(input, preserveFormat) { switch (typeof input) { case 'string': return input.split('\n'); case 'undefined': return ['undefined']; case 'number': case 'bigint': case 'boolean': case 'function': return input.toString().split('\n'); case 'object': return JSON .stringify(input, null, preserveFormat ? 0 : config_1.config.indentation) .split('\n'); } return []; } /** * Triggers an audible bell sound in the terminal. * * Sends the ASCII bell character (`\x07`) to `stdout`. */ function ringBell() { process.stdout.write('\x07'); } /** * Formats content for display based on line wrapping configuration. * * - Chooses between chopping or wrapping long lines. * - Limits formatting to the current window range. * * @param content - The full array of content lines to format. * @returns A formatted string array ready for rendering. */ function formatContent(content) { const lines = []; if (config_1.config.chopLongLines || config_1.config.col) { (0, chopLongLines_1.chopLongLines)(content, lines); } else { (0, wrapLongLines_1.wrapLongLines)(content, lines); } padToEOF(lines); return lines; } /** * Adds a character to the input buffer. * * - Increments buffer offset if visible width limit is reached. * * @param buffer - Current input buffer array. * @param key - Character to append. */ function addBufferChar(buffer, key) { if (visibleBufferLength(buffer.length) + 1 === config_1.config.screenWidth - 1) { config_1.config.bufferOffset++; } buffer.push(key); } /** * Removes the last character from the input buffer. * * - Decrements buffer offset if no visible characters remain. * * @param buffer - Current input buffer array. */ function delBufferChar(buffer) { if (visibleBufferLength(buffer.length) === 0) { config_1.config.bufferOffset--; } buffer.pop(); } /** * Renders the given content to the terminal. * * - Clears the screen before writing. * - Outputs the content directly to `stdout`. * * @param rawContent - The string content to display in the terminal. * @param buffer - Array of buffer characters. */ function render(rawContent, buffer) { const content = formatContent(rawContent); const prompt = getPrompt(); if (prompt) content.push(prompt + getBuffer(buffer)); console.clear(); process.stdout.write(content.join('\n')); } /** * Calculates the total visual width of a string based on terminal character * widths. * * @param line - The input string to measure. * @returns The total visual width of the string in terminal columns. */ function visualWidth(line) { if ((0, exports.isStyled)(line)) line = line.replace(constants_1.STYLE_REGEX_G, ''); if ((0, exports.isAscii)(line)) return line.length; const segments = Array.from(line); let length = 0; for (let i = 0; i < segments.length; i++) { length += (0, wcwidth_1.default)(segments[i]); } return length; } /** * Checks whether a given segment consists entirely of ASCII characters. * * - Matches characters in the range 0x00 to 0x7F. * - Used to determine whether fast-path rendering can be applied. * * @param segment - A string segment to check. * @returns Whether the segment is pure ASCII. */ const isAscii = (segment) => constants_1.ASCII_REGEX.test(segment); exports.isAscii = isAscii; /** * Checks whether a given string contains ANSI style codes. * * @param line The input string to test. * @returns `true` if ANSI style codes are present, otherwise `false`. */ const isStyled = (line) => constants_1.STYLE_REGEX.test(line); exports.isStyled = isStyled; /** * Returns the prompt string to be shown at the bottom of the screen. * * - Typically returns `':'` to indicate the pager is awaiting user input. * - Suppressed if an EOF marker like `(END)` is already displayed. * - May adapt based on the current mode (e.g. buffering, EOF). * * @returns The prompt string, or an empty string if suppressed. */ function getPrompt() { const helpPrompt = [ 'HELP -- ', config_1.mode.EOF ? 'END -- Press g to see it again' : 'Press RETURN for more', ', or q when done' ].join(''); if (config_1.mode.HELP && !config_1.mode.BUFFERING) { return [ constants_1.INVERSE_ON, helpPrompt.slice(Math.max(helpPrompt.length - config_1.config.screenWidth + 2, 0)), constants_1.INVERSE_OFF ].join(''); } if (!config_1.mode.EOF || config_1.mode.BUFFERING) return ':'; return ''; } /** * Trims the buffer to fit within the screen width. * * - If too long, trims equally from the start to keep the tail visible. * * @param buffer - Array of buffer characters. * @returns The buffer as a string, trimmed if necessary. */ function getBuffer(buffer) { const width = config_1.config.screenWidth - 1; const halfWidth = Math.floor(width / 2); return buffer.slice(halfWidth * config_1.config.bufferOffset).join(''); } /** * Calculates visible characters in the buffer. * * @param bufferLength - Total buffer character count. * @returns Number of visible characters based on offset. */ function visibleBufferLength(bufferLength) { const width = config_1.config.screenWidth - 1; const halfWidth = Math.floor(width / 2); return bufferLength - halfWidth * config_1.config.bufferOffset; } /** * Pads remaining window space with `~` lines or an `(END)` marker. * * @param lines - The array of formatted lines to pad. */ function padToEOF(lines) { while (!config_1.mode.INIT && lines.length < config_1.config.window - 1) { lines.push('\x1b[1m~\x1b[0m'); } if (config_1.mode.INIT && lines.length === config_1.config.window - 1) config_1.mode.INIT = false; if (!config_1.mode.BUFFERING && !config_1.mode.HELP && config_1.mode.EOF) { lines.push('\x1b[7m(END)\x1b[0m'); } }