UNPKG

less-pager-mini

Version:

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

191 lines (190 loc) 6.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = pager; const config_1 = require("./config"); const lessHelp_1 = require("./lessHelp"); const readKey_1 = require("./readKey"); const normalKeys_1 = require("./normalKeys"); const helpers_1 = require("./helpers"); const moving_1 = require("./features/moving"); const constants_1 = require("./constants"); const TITLE = constants_1.CONSOLE_TITLE_START + 'less-pager-mini' + constants_1.CONSOLE_TITLE_END; /** * Less-pager-mini * * - If `examineFile` is true, treats input as file path(s) and loads file * content. * - Otherwise, converts arbitrary input into displayable string content. * * @param input - The input to render, which can be a string, object, or array. * @param preserveFormat - Whether to preserve original formatting * (no indentation). * @param examineFile - If true, treats input as file path(s) and reads from * disk. */ async function pager(input, preserveFormat = false, examineFile = false) { if (examineFile) { await filePager((0, helpers_1.inputToFilePaths)(input), preserveFormat); return; } const content = (0, helpers_1.inputToString)(input, preserveFormat); if (!content.length) return; await contentPager(content); } /** * Displays the contents of provided file paths using the pager. * * - Ignores empty file path arrays. * - Converts file content to string arrays for rendering. * * @param filePaths - Array of file paths to display. * @param preserveFormat - Whether to preserve the file’s original formatting. */ async function filePager(filePaths, preserveFormat) { if (!filePaths.length) return; // remove line below in the future if (preserveFormat) console.log('TODO: preserveFormat not implemented yet'); } /** * Starts an interactive pager session to navigate through string content. * * - Handles terminal resizing (SIGWINCH) to repaint content. * - Supports key-based navigation with buffered numeric input. * - Responds to various paging actions like line/window movement and exit. * * @param content - The content to be displayed in the pager. */ async function contentPager(content) { process.stdout.write(TITLE); process.stdout.write(constants_1.ALTERNATE_CONSOLE_ON); process.on('uncaughtException', (error) => { process.stdout.write(constants_1.ALTERNATE_CONSOLE_OFF); process.stdout.write(constants_1.CONSOLE_TITLE_RESET); console.error(error); process.exit(1); }); process.on('SIGWINCH', () => { config_1.mode.INIT = false; config_1.config.window = process.stdout.rows; config_1.config.screenWidth = process.stdout.columns; config_1.config.halfWindow = Math.floor(config_1.config.window / 2); config_1.config.halfScreenWidth = Math.floor(config_1.config.screenWidth / 2); buffer = []; config_1.config.bufferOffset = 0; (0, helpers_1.render)(content, buffer); }); process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.setEncoding('utf8'); let exit = false; let repaint = true; let buffer = []; let prevContent = []; let prevConfig = config_1.config; let prevMode = config_1.mode; while (!exit) { config_1.mode.BUFFERING = Boolean(buffer.length); if (repaint) (0, helpers_1.render)(content, buffer); repaint = true; const key = await (0, readKey_1.readKey)(); if (key >= '0' && key <= '9') { (0, helpers_1.addBufferChar)(buffer, key); continue; } const action = (0, normalKeys_1.getAction)(key); if (action === 'BACKSPACE' && buffer.length) { (0, helpers_1.delBufferChar)(buffer); continue; } switch (action) { case 'FORCE_EXIT': exit = true; break; case 'EXIT': exit = shouldExit(); break; case 'HELP': prepareHelp(); break; case 'LINE_FORWARD': (0, moving_1.lineForward)(content, (0, helpers_1.bufferToNum)(buffer) || 1); break; case 'LINE_BACKWARD': (0, moving_1.lineBackward)(content, (0, helpers_1.bufferToNum)(buffer) || 1); break; case 'WINDOW_FORWARD': (0, moving_1.windowForward)(content, buffer); break; case 'WINDOW_BACKWARD': (0, moving_1.windowBackward)(content, buffer); break; case 'SET_WINDOW_FORWARD': (0, moving_1.setWindowForward)(content, buffer); break; case 'SET_WINDOW_BACKWARD': (0, moving_1.setWindowBackward)(content, buffer); break; case 'NO_EOF_WINDOW_FORWARD': (0, moving_1.windowForward)(content, buffer, true); break; case 'SET_HALF_WINDOW_FORWARD': (0, moving_1.setHalfWindowForward)(content, buffer); break; case 'SET_HALF_WINDOW_BACKWARD': (0, moving_1.setHalfWindowBackward)(content, buffer); break; case 'SET_HALF_SCREEN_RIGHT': (0, moving_1.setHalfScreenRight)(buffer); break; case 'SET_HALF_SCREEN_LEFT': (0, moving_1.setHalfScreenLeft)(buffer); break; case 'REPAINT': break; default: (0, helpers_1.ringBell)(); repaint = false; } buffer = []; config_1.config.bufferOffset = 0; } process.stdin.setRawMode(false); process.stdin.pause(); process.stdout.write(constants_1.ALTERNATE_CONSOLE_OFF); process.stdout.write(constants_1.CONSOLE_TITLE_RESET); // helpers /** * Exits help mode if active, otherwise allows pager to exit. * * @returns `true` if should exit, `false` if returning from help. */ function shouldExit() { if (!config_1.mode.HELP) return true; content = prevContent; (0, config_1.applyConfig)(prevConfig); (0, config_1.applyMode)(prevMode); config_1.mode.HELP = false; return false; } /** * Enters help mode by saving current state and loading help content. */ function prepareHelp() { if (config_1.mode.HELP) return; prevContent = content; prevConfig = config_1.config; prevMode = config_1.mode; (0, config_1.resetConfig)(); (0, config_1.resetMode)(); config_1.mode.HELP = true; content = lessHelp_1.help; } } module.exports = pager; module.exports.default = pager;