scroll/src/common/fns.ts

233 lines
5.1 KiB
JavaScript
Raw Normal View History

2023-11-29 16:09:58 -05:00
import { KeyCommand } from './ansi.ts';
const decoder = new TextDecoder();
2023-11-16 16:00:03 -05:00
// ----------------------------------------------------------------------------
// Misc
// ----------------------------------------------------------------------------
2023-11-29 16:09:58 -05:00
/**
* An empty function
*/
export const noop = () => {};
/**
* Convert input from ANSI escape sequences into a form
* that can be more easily mapped to editor commands
*
* @param raw - the raw chunk of input
*/
export function readKey(raw: Uint8Array): string {
if (raw.length === 0) {
return '';
}
const parsed = decoder.decode(raw);
// Return the input if it's unambiguous
if (parsed in KeyCommand) {
return parsed;
}
2024-07-05 15:51:30 -04:00
// Some key codes have multiple potential inputs
2023-11-29 16:09:58 -05:00
switch (parsed) {
case '\x1b[1~':
case '\x1b[7~':
case '\x1bOH':
case '\x1b[H':
return KeyCommand.Home;
case '\x1b[4~':
case '\x1b[8~':
case '\x1bOF':
case '\x1b[F':
return KeyCommand.End;
case '\n':
case '\v':
return KeyCommand.Enter;
case ctrlKey('l'):
return KeyCommand.Escape;
case ctrlKey('h'):
return KeyCommand.Backspace;
default:
return parsed;
}
}
// ----------------------------------------------------------------------------
// Array manipulation
// ----------------------------------------------------------------------------
2023-11-22 17:09:41 -05:00
/**
* Insert a value into an array at the specified index
*
2023-11-22 17:09:41 -05:00
* @param arr - the array
* @param at - the index to insert at
* @param value - what to add into the array
*/
export function arrayInsert<T>(
arr: Array<T>,
at: number,
value: T | Array<T>,
): Array<T> {
const insert = Array.isArray(value) ? value : [value];
if (at >= arr.length) {
arr.push(...insert);
return arr;
}
return [...arr.slice(0, at), ...insert, ...arr.slice(at)];
}
// ----------------------------------------------------------------------------
// Math
// ----------------------------------------------------------------------------
2023-11-20 14:21:42 -05:00
/**
* Subtract two numbers, returning a zero if the result is negative
*
2023-11-20 14:21:42 -05:00
* @param l
* @param s
*/
export function posSub(l: number, s: number): number {
return minSub(l, s, 0);
}
/**
* Subtract two numbers, returning at least the minimum specified
* @param l
* @param s
* @param min
*/
export function minSub(l: number, s: number, min: number): number {
return Math.max(l - s, min);
}
/**
* Add two numbers, up to a max value
* @param n1
* @param n2
* @param max
*/
export function maxAdd(n1: number, n2: number, max: number): number {
return Math.min(n1 + n2, max);
}
2023-11-10 08:36:18 -05:00
// ----------------------------------------------------------------------------
2023-11-09 12:32:41 -05:00
// Strings
2023-11-10 08:36:18 -05:00
// ----------------------------------------------------------------------------
2023-11-09 12:32:41 -05:00
2023-11-16 16:00:03 -05:00
/**
* Get the codepoint of the first byte of a string. If the string
* is empty, this will return 256
*
* @param s - the string
*/
export function ord(s: string): number {
if (s.length > 0) {
return s.codePointAt(0)!;
}
return 256;
}
2023-11-02 13:06:48 -04:00
/**
* Split a string by graphemes, not just bytes
2023-11-16 16:00:03 -05:00
*
* @param s - the string to split into unicode code points
2023-11-02 13:06:48 -04:00
*/
export function strChars(s: string): string[] {
return [...s];
2023-11-02 13:06:48 -04:00
}
2023-11-09 12:05:30 -05:00
/**
* Get the 'character length' of a string, not its UTF16 byte count
2023-11-16 16:00:03 -05:00
*
2023-11-09 12:05:30 -05:00
* @param s - the string to check
*/
export function strlen(s: string): number {
return strChars(s).length;
2023-11-09 12:05:30 -05:00
}
/**
* Get a slice of a string
*
* @param s - the string
* @param from - the 'character' index of the start of the slice
* @param to - the 'character' index of the last character you want
*/
export function substr(s: string, from: number, to?: number): string {
return strChars(s).slice(from, to).join('');
}
2023-11-02 13:06:48 -04:00
/**
2023-11-16 16:00:03 -05:00
* Are all the characters in the string in ASCII range?
2023-11-02 13:06:48 -04:00
*
2023-11-16 16:00:03 -05:00
* @param char - string to check
2023-11-02 13:06:48 -04:00
*/
export function isAscii(char: string): boolean {
return strChars(char).every((char) => ord(char) < 0x80);
2023-11-02 13:06:48 -04:00
}
2024-02-29 14:24:22 -05:00
/**
* Are all the characters numerals?
*
* @param char - string to check
*/
export function isAsciiDigit(char: string): boolean {
return isAscii(char) &&
strChars(char).every((char) => ord(char) >= 0x30 && ord(char) < 0x3a);
}
2023-11-02 13:06:48 -04:00
/**
* Is the one char in the string an ascii control character?
*
* @param char - a one character string to check
*/
export function isControl(char: string): boolean {
2023-11-16 16:00:03 -05:00
const code = ord(char);
return isAscii(char) && (code === 0x7f || code < 0x20);
2023-11-02 13:06:48 -04:00
}
2024-07-16 11:17:45 -04:00
/**
* Is the one char string a common separator/operator character
*
* @param char - a one character string to check
*/
export function isSeparator(char: string): boolean {
return /\s/.test(char) || char === '\0' || ',.()+-/*=~%<>[];'.includes(char);
}
2023-11-02 13:06:48 -04:00
/**
* Get the key code for a ctrl chord
2023-11-16 16:00:03 -05:00
*
2023-11-02 13:06:48 -04:00
* @param char - a one character string
*/
export function ctrlKey(char: string): string {
2023-11-02 13:06:48 -04:00
// This is the normal use case, of course
if (isAscii(char)) {
2023-11-16 16:00:03 -05:00
const point = ord(char);
2023-11-02 13:06:48 -04:00
return String.fromCodePoint(point & 0x1f);
}
// If it's not ascii, just return the input key code
return char;
}
2023-11-09 12:05:30 -05:00
/**
* Trim a string to a max number of characters
* @param s
* @param maxLen
*/
export function truncate(s: string, maxLen: number): string {
const chin = strChars(s);
2023-11-09 12:05:30 -05:00
if (maxLen >= chin.length) {
return s;
}
return chin.slice(0, maxLen).join('');
}