@muxik/md-viewer
Version:
A CLI tool for rendering Markdown files with syntax highlighting and pagination, optimized for AI output content display
162 lines • 6.72 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Pager = void 0;
const readline_1 = require("readline");
const process_1 = require("process");
const chalk_1 = __importDefault(require("chalk"));
class Pager {
constructor(lines, options) {
this.currentLine = 0;
this.lines = lines;
this.theme = options.theme;
this.title = options.title;
this.showLineNumbers = options.showLineNumbers || false;
this.terminalHeight = process.stdout.rows || 24;
this.terminalWidth = process.stdout.columns || 80;
this.rl = (0, readline_1.createInterface)({
input: process_1.stdin,
output: process_1.stdout,
terminal: true
});
}
async show() {
return new Promise((resolve) => {
this.setupTerminal();
this.render();
this.rl.input.setRawMode(true);
this.rl.input.resume();
this.rl.input.on('keypress', (str, key) => {
this.handleKeyPress(key, resolve);
});
});
}
setupTerminal() {
process.stdout.write('\x1B[?1049h');
process.stdout.write('\x1B[2J');
process.stdout.write('\x1B[H');
process.on('SIGWINCH', () => {
this.terminalHeight = process.stdout.rows || 24;
this.terminalWidth = process.stdout.columns || 80;
this.render();
});
}
render() {
const contentHeight = this.terminalHeight - 2;
const visibleLines = this.lines.slice(this.currentLine, this.currentLine + contentHeight);
process.stdout.write('\x1B[2J');
process.stdout.write('\x1B[H');
this.renderHeader();
this.renderContent(visibleLines);
this.renderFooter();
}
renderHeader() {
const headerBg = this.theme.colors.accent;
const headerText = this.theme.colors.background;
let header = this.title || 'mdview';
if (header.length > this.terminalWidth - 4) {
header = header.substring(0, this.terminalWidth - 7) + '...';
}
const padding = this.terminalWidth - header.length;
const paddedHeader = header + ' '.repeat(Math.max(0, padding));
process.stdout.write(chalk_1.default.hex(headerText).bgHex(headerBg)(paddedHeader));
process.stdout.write('\n');
}
renderContent(visibleLines) {
const startLineNumber = this.currentLine + 1;
visibleLines.forEach((line, index) => {
let displayLine = line;
if (this.showLineNumbers) {
const lineNumber = startLineNumber + index;
const lineNumberStr = lineNumber.toString().padStart(4, ' ');
const lineNumberColor = this.theme.colors.accent;
displayLine = `${chalk_1.default.hex(lineNumberColor)(lineNumberStr)} │ ${line}`;
}
if (displayLine.length > this.terminalWidth) {
displayLine = displayLine.substring(0, this.terminalWidth - 3) + '...';
}
process.stdout.write(displayLine);
process.stdout.write('\n');
});
}
renderFooter() {
const footerBg = this.theme.colors.border;
const footerText = this.theme.colors.text;
const totalLines = this.lines.length;
const currentPage = Math.floor(this.currentLine / (this.terminalHeight - 2)) + 1;
const totalPages = Math.ceil(totalLines / (this.terminalHeight - 2));
const progress = totalLines > 0 ? Math.round((this.currentLine + this.terminalHeight - 2) / totalLines * 100) : 100;
const progressStr = progress > 100 ? '100%' : `${progress}%`;
const leftStatus = `Line ${this.currentLine + 1}/${totalLines}`;
const rightStatus = `${progressStr} | Page ${currentPage}/${totalPages}`;
const helpText = 'q:quit ↑/↓:scroll PgUp/PgDn:page g/G:top/bottom';
const availableWidth = this.terminalWidth - leftStatus.length - rightStatus.length;
const centeredHelp = availableWidth > helpText.length ?
helpText.padStart((availableWidth + helpText.length) / 2).padEnd(availableWidth) :
helpText.substring(0, availableWidth);
const footer = leftStatus + centeredHelp + rightStatus;
const paddedFooter = footer.padEnd(this.terminalWidth);
process.stdout.write(chalk_1.default.hex(footerText).bgHex(footerBg)(paddedFooter));
}
handleKeyPress(key, resolve) {
const contentHeight = this.terminalHeight - 2;
switch (key.name) {
case 'q':
case 'escape':
this.cleanup();
resolve();
break;
case 'up':
if (this.currentLine > 0) {
this.currentLine--;
this.render();
}
break;
case 'down':
if (this.currentLine < this.lines.length - 1) {
this.currentLine++;
this.render();
}
break;
case 'pageup':
this.currentLine = Math.max(0, this.currentLine - contentHeight);
this.render();
break;
case 'pagedown':
this.currentLine = Math.min(this.lines.length - 1, this.currentLine + contentHeight);
this.render();
break;
case 'g':
this.currentLine = 0;
this.render();
break;
case 'G':
this.currentLine = Math.max(0, this.lines.length - contentHeight);
this.render();
break;
case 'home':
this.currentLine = 0;
this.render();
break;
case 'end':
this.currentLine = Math.max(0, this.lines.length - contentHeight);
this.render();
break;
default:
if (key.ctrl && key.name === 'c') {
this.cleanup();
process.exit(0);
}
break;
}
}
cleanup() {
process.stdout.write('\x1B[?1049l');
this.rl.input.setRawMode(false);
this.rl.close();
}
}
exports.Pager = Pager;
//# sourceMappingURL=pager.js.map