lib-curses
Version:
Simple node.js library for work with console
510 lines (345 loc) • 13.3 kB
Markdown
Простая работа с консолью.
Библиотка javascript/typescript (ES6) для node.js.
- работа через потоки ввода/вывода,
- работа со встроенными функциями ноды,
- привычные методы input, print, getch,
- бесконечный цикл loop с возможностью выхода,
- асинхронная пауза.
Нужно понимать, что библиотека написана для простой и быстрой работы. Она не является заменой curses из c и c++.
Если вам нужен более мощный функционал, то стоит обратить внимание на другие библиотеки.
# Установка
```
npm install lib-curses
```
или
```
yarn add lib-curses
```
# Начало работы
Можно импортировать методы как с отдельные функции:
```
import { initScreen, print } from 'lib-curses';
initScreen();
print('Hello world');
```
или вызывать их как статические методы объекта:
```
import * as curses from 'lib-curses';
curses.initScreen();
curses.print('Hello world');
```
# Список методов
**addch(string: any, x = -1, y = -1): void**
Вывод символов в консоль.
Вторым и третьим необязательными параметрами можно задать смещение по осям x и y относительно верхнего левого угла.
Отсчет начинается с нуля. Любые значения меньше нуля игнорируют смещение. И таким образом, вывод идет в текущем месте расположения курсора.
**clearLine(y: number): void**
Очищает строку.
**clearScreen(): void**
Очищает экран.
**async getch(): Promise<Buffer>**
Считывает ввод с клавиатуры. Возвращает объект Buffer.
> Обратите внимание, что любые сочетания клавиш (ctrl + c) и специальные вызовы (SIGINT) не будут обработаны автоматически. Поэтому вам придется обрабатывать их самостоятельно.
**hideCursor(): void**
Скрывает курсор.
**initScreen(): void**
Подготовка экрана консоли к работе.
**async input(printedString = '', callback: (key: string) => string = (key) => key): Promise<string>**
Требует ввод строки в консоль. Возвращает введенное пользователем значение.
Первым необязательным параметром можно передать строку, которая будет выведена перед запросом ввода.
Вторым необязательным параметром можно задать callback-функцию, которая будет обрабатывать полученное значение и возвращать его.
**async inputch(printedString = '', callback: (key: string) => string = (key) => key): Promise<string>**
Требует ввод символа в консоль.
Отличие от **input** только в том, что ввод ожидает нажатия любой клавиши и возвращает ее значение.
**async loop(callback: () => Promise<boolean>, milliseconds = 0): Promise<void>**
Запускает цикл.
В цикле вызывается функция callback, пока она возвращает true.
Цикл завершится, когда функция callback вернет false.
Вторым необязательным аргументом можно передать задержку между итерациями.
**position(x = -1, y = -1): void**
Задает позицию курсора по осям x и y относительно верхнего левого угла.
Отсчет начинается с нуля. Любые значения меньше нуля игнорируют смещение. И таким образом, вывод идет в текущем месте расположения курсора.
**print(string: any, x = -1, y = -1): void**
Вывод строки в консоль.
Отличие от **addch** только в том, что в конце вывода добавляется символ перевода строки.
**showCursor(): void**
Возвращает показ курсора.
**async wait(milliseconds: number): Promise<void>**
Пауза для асинхронного кода.
# Обработка ошибок
Все вызовы возвращают ошибки как есть, без дополнительных перехватов и обработки. Поэтому оборачиваем в try/catch самостоятельно.
```
async function customInput(question) {
try {
const userInput = await input(question);
return { userInput };
} catch (error) {
return { error };
}
}
```
# Примеры
# Простой вывод
Вывод символов:
```
addch('Hello world');
```
Вывод строки:
```
print('Hello world');
```
Посмотрим как это работает, если повторить несколько вызовов последовательно.
Например:
```
addch('1');
addch('2');
print('3');
print('4');
addch('5');
addch('6');
print('7');
print('8');
```
выведет в консоль:
```
123
4
567
8
```
# Вывод в заданное место
Такой пример со счетчиком:
```
let count = 0;
setInterval(() => {
clearLine(4);
print(`Counter: ${count}`, 0, 4);
count++;
}, 1000);
```
выведет в консоль:
```
Counter: 0
```
# Запрос ввода пользователя
```
const userInput = input();
```
Выведет в консоль:
```
> _
```
Можно задать первый параметр. Например:
```
const userInput = input('What is your name: ');
```
Выведет в консоль:
```
> What is your name: _
```
# Ввод строки с обработкой
Следующий пример будет передавать только числа от 1 до 100 и 0 как неверное значение.
```
const string = await input('Input 1-100: ', (string) => {
let answer = parseInt(string, 10) || 0;
if (answer < 1) {
answer = 0;
}
if (answer > 100) {
answer = 0;
}
process.stdout.write(answer + '\n');
return String(answer);
});
console.log(`Input: ${string}`);
```
# Ввод символа с обработкой
Следующий пример задает ввод по-умолчанию, если никакой символ не был введен. По клавише **Esc** он прерывает выполнение программы.
```
const key = await inputch('(Y)es or (N)o: ', (key) => {
if (key === '\x1B') {
process.exit();
}
let answer = key.toString().trim().toLowerCase();
if (!answer || /[^a-z]+/iu.test(answer)) {
answer = 'y';
}
process.stdout.write(answer + '\n');
return answer;
});
console.log(`Input: ${key}`);
```
# Ввод в заданном месте
Следующий пример задает ввод на пятой строке. Важно помнить, что смещение работает только после инициализации экрана консоли.
```
initScreen();
position(0, 4)
const string = await input('Write something: ');
console.log(`Input: ${string}`);
```
# Пример обработки inputch
В данном примере предлагается ввести максимум 5 символов. Ввод будет прерван после нажатия клавиши **Enter**.
```
let inputString = '';
let readyString = '';
initScreen();
const handler = (key: string) => {
if (key === '\x0d') {
readyString = inputString;
}
if (/\W/iu.test(key)) {
return '';
}
inputString += key;
if (inputString.length > 5) {
inputString = key;
}
return key;
};
while (!readyString) {
position(0, 0);
const printedString = `Input max 5 symbols: ${`${inputString}_____`.slice(0, 5)}`;
const key = await inputch(printedString, (key) => {
position(printedString.length, 0);
return handler(key);
});
}
print(`> ${readyString}`, 0, 1);
```
# Обработка ввода с клавиатуры
Ввод с клавиатуры обрабатывается таким образом:
```
const key = await getch();
console.log(key);
```
Важно помнить, что метод **getch** не обрабатывает отдельно специальные вызовы (например SIGINT). Поэтому вам придется самостоятельно предусмотреть выход из программы.
Например, если вызывать **getch** в цикле, можно обработать прерывание так:
```
let infinite = true;
while (infinite) {
const key = await getch();
console.log({
uint8: new Uint8Array(key),
x16: [...new Uint8Array(key)].map((code) =>
`${'0' + code.toString(16)}`.slice(-2),
),
utf8: key.toString(),
hex: key.toString('hex'),
});
if (key === '\x03') {
infinite = false;
}
}
```
# Пример обработки getch
В данном примере мы будем управлять символом **@** с помощью курсора.
Нажатие клавиш **Enter**, **Esc** или **Ctrl** + **C** прервет процесс.
```
const keys = {
enter: '\x0d',
esc: '\x1b',
sigint: '\x03',
up: '\x1B[A',
down: '\x1B[B',
left: '\x1B[D',
right: '\x1B[C',
};
const w = 10, h = 10;
let x = 0, y = 0;
initScreen();
hideCursor();
addch('@', x, y);
let infinite = true;
while (infinite) {
const code = await getch();
const key = code.toString();
const oldX = x, oldY = y;
if (key === keys.up) { y -= 1; }
if (key === keys.down) { y += 1; }
if (key === keys.left) { x -= 1; }
if (key === keys.right) { x += 1; }
if (x < 0) { x = 0; }
if (x > w) { x = w; }
if (y < 0) { y = 0; }
if (y > h) { y = h; }
if (x !== oldX || y !== oldY) {
addch(' ', oldX, oldY);
addch('@', x, y);
}
if ([keys.enter, keys.esc, keys.sigint].includes(key)) {
infinite = false;
}
}
```
# Пример бесконечного цикла
Предположим, у нас есть асинхронная функция **main**:
```
async function main(): Promise<boolean> {
if (...) {
return false;
}
return true;
}
```
Запускать эту функцию в цикле можно самым простым способом, даже прямо из тела индексного файла:
```
loop(main);
```
Эквивалентный способ:
```
(async () => loop(main))();
```
Если функция **main** синхронная:
```
function main(): boolean {
if (...) {
return false;
}
return true;
}
```
Ее нужно запускать внутри асинхронного вызова:
```
loop(async () => main());
```
Теперь допускается запускать внутри синхронный вызов:
```
loop(() => {
...
return true;
});
```
или синхронную функцию:
```
loop(main);
```
Т.к. данный цикл асинхронный, то несколько циклов подряд будут запускаться параллельно.
Если вы хотите запускать их последовательно, вам нужно вызывать их через **await**:
```
await loop(...);
await loop(...);
```
Можно сделать обработку ошибок:
```
loop(() => main().catch((e) => {
console.error(e);
return false;
}));
```
# Пример паузы в цикле
Самый простой и правильный способ - задать паузу вторым аргументом:
```
loop(main, 1000)
```
Можно также добавлять задержки внутри кода исполняемой логики, например в функции main:
```
async function main(): Promise<boolean> {
if (...) {
return false;
}
await wait(1000);
return true;
}
```
# Лицензия
Лицензия MIT, 2025