First output of welcome message
This commit is contained in:
parent
7eb07520ae
commit
abee0a80bf
@ -14,8 +14,8 @@ function getSizeFromTput(): ITerminalSize {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rows: (rows > 0) ? rows : 25,
|
rows: (rows > 0) ? rows + 1 : 25,
|
||||||
cols: (cols > 0) ? cols : 80,
|
cols: (cols > 0) ? cols + 1 : 80,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,11 @@ function esc(pieces: TemplateStringsArray): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Ansi = {
|
export const Ansi = {
|
||||||
|
ClearLine: esc`K`,
|
||||||
ClearScreen: esc`2J`,
|
ClearScreen: esc`2J`,
|
||||||
ResetCursor: esc`H`,
|
ResetCursor: esc`H`,
|
||||||
|
HideCursor: esc`?25l`,
|
||||||
|
ShowCursor: esc`?25h`,
|
||||||
moveCursor: function moveCursor(row: number, col: number): string {
|
moveCursor: function moveCursor(row: number, col: number): string {
|
||||||
return `\x1b${row};${col}H`;
|
return `\x1b${row};${col}H`;
|
||||||
},
|
},
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { strlen } from '../strings.ts';
|
||||||
|
|
||||||
class Buffer {
|
class Buffer {
|
||||||
#b = '';
|
#b = '';
|
||||||
|
|
||||||
@ -19,6 +21,10 @@ class Buffer {
|
|||||||
getBuffer(): string {
|
getBuffer(): string {
|
||||||
return this.#b;
|
return this.#b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strlen(): number {
|
||||||
|
return strlen(this.#b);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Buffer;
|
export default Buffer;
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
import Ansi from './ansi.ts';
|
import Ansi from './ansi.ts';
|
||||||
import Buffer from './buffer.ts';
|
import Buffer from './buffer.ts';
|
||||||
import { importDefaultForRuntime } from '../runtime.ts';
|
import {
|
||||||
import { ctrl_key } from '../strings.ts';
|
ctrl_key,
|
||||||
import { ITerminalSize } from '../types.ts';
|
importDefaultForRuntime,
|
||||||
|
ITerminalSize,
|
||||||
|
strlen,
|
||||||
|
truncate,
|
||||||
|
VERSION,
|
||||||
|
} from '../mod.ts';
|
||||||
|
|
||||||
export class Editor {
|
export class Editor {
|
||||||
#buffer: Buffer;
|
#buffer: Buffer;
|
||||||
@ -29,27 +34,46 @@ export class Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------
|
||||||
|
// Terminal Output / Drawing
|
||||||
|
// -------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear the screen and write out the buffer
|
* Clear the screen and write out the buffer
|
||||||
*/
|
*/
|
||||||
public async refreshScreen(): Promise<void> {
|
public async refreshScreen(): Promise<void> {
|
||||||
const { write } = await importDefaultForRuntime('terminal_io');
|
const { write } = await importDefaultForRuntime('terminal_io');
|
||||||
|
|
||||||
this.clearScreen();
|
this.#buffer.append(Ansi.HideCursor);
|
||||||
|
this.#buffer.append(Ansi.ResetCursor);
|
||||||
this.drawRows();
|
this.drawRows();
|
||||||
|
this.#buffer.append(Ansi.ShowCursor);
|
||||||
|
|
||||||
await write(this.#buffer.getBuffer());
|
await write(this.#buffer.getBuffer());
|
||||||
this.#buffer.clear();
|
this.#buffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private drawRows(): void {
|
|
||||||
for (let y = 0; y <= this.#screenRows; y++) {
|
|
||||||
this.#buffer.appendLine('~');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private clearScreen(): void {
|
private clearScreen(): void {
|
||||||
|
importDefaultForRuntime('terminal_io').then(({ write }) => {
|
||||||
this.#buffer.append(Ansi.ClearScreen);
|
this.#buffer.append(Ansi.ClearScreen);
|
||||||
this.#buffer.append(Ansi.ResetCursor);
|
this.#buffer.append(Ansi.ResetCursor);
|
||||||
|
write(this.#buffer.getBuffer()).then(() => {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawRows(): void {
|
||||||
|
for (let y = 0; y < this.#screenRows; y++) {
|
||||||
|
if (y === this.#screenRows / 3) {
|
||||||
|
const message = `Kilo editor -- version ${VERSION}`;
|
||||||
|
this.#buffer.append(truncate(message, this.#screenCols));
|
||||||
|
} else {
|
||||||
|
this.#buffer.append('~');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#buffer.append(Ansi.ClearLine);
|
||||||
|
if (y < this.#screenRows - 1) {
|
||||||
|
this.#buffer.append('\r\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,3 +3,5 @@ export * from './runtime.ts';
|
|||||||
export * from './strings.ts';
|
export * from './strings.ts';
|
||||||
export * from './termios.ts';
|
export * from './termios.ts';
|
||||||
export type * from './types.ts';
|
export type * from './types.ts';
|
||||||
|
|
||||||
|
export const VERSION = '0.0.1';
|
||||||
|
@ -6,19 +6,23 @@ export function chars(s: string): string[] {
|
|||||||
return s.split(/(?:)/u);
|
return s.split(/(?:)/u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the 'character length' of a string, not its UTF16 byte count
|
||||||
|
* @param s - the string to check
|
||||||
|
*/
|
||||||
|
export function strlen(s: string): number {
|
||||||
|
return chars(s).length;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the character part of ascii?
|
* Is the character part of ascii?
|
||||||
*
|
*
|
||||||
* @param char - a one character string to check
|
* @param char - a one character string to check
|
||||||
*/
|
*/
|
||||||
export function is_ascii(char: string): boolean {
|
export function is_ascii(char: string): boolean {
|
||||||
if (typeof char !== 'string') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return chars(char).every((char) => {
|
return chars(char).every((char) => {
|
||||||
const point = char.codePointAt(0);
|
const point = char.codePointAt(0);
|
||||||
if (typeof point === 'undefined') {
|
if (point === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,3 +62,17 @@ export function ctrl_key(char: string): string {
|
|||||||
// If it's not ascii, just return the input key code
|
// If it's not ascii, just return the input key code
|
||||||
return char;
|
return char;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trim a string to a max number of characters
|
||||||
|
* @param s
|
||||||
|
* @param maxLen
|
||||||
|
*/
|
||||||
|
export function truncate(s: string, maxLen: number): string {
|
||||||
|
const chin = chars(s);
|
||||||
|
if (maxLen >= chin.length) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return chin.slice(0, maxLen).join('');
|
||||||
|
}
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { importDefaultForRuntime, ITestBase } from './mod.ts';
|
import { importDefaultForRuntime, ITestBase } from './mod.ts';
|
||||||
import { chars, is_ascii } from './strings.ts';
|
import {
|
||||||
|
chars,
|
||||||
|
ctrl_key,
|
||||||
|
is_ascii,
|
||||||
|
is_control,
|
||||||
|
strlen,
|
||||||
|
truncate,
|
||||||
|
} from './strings.ts';
|
||||||
|
|
||||||
const t: ITestBase = await importDefaultForRuntime('test_base');
|
const t: ITestBase = await importDefaultForRuntime('test_base');
|
||||||
|
|
||||||
@ -7,7 +14,42 @@ t.test('chars fn properly splits strings into unicode characters', () => {
|
|||||||
t.assertEquals(chars('😺😸😹'), ['😺', '😸', '😹']);
|
t.assertEquals(chars('😺😸😹'), ['😺', '😸', '😹']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
t.test('ctrl_key fn returns expected values', () => {
|
||||||
|
const ctrl_a = ctrl_key('a');
|
||||||
|
t.assertTrue(is_control(ctrl_a));
|
||||||
|
t.assertEquals(ctrl_a, String.fromCodePoint(0x01));
|
||||||
|
|
||||||
|
const invalid = ctrl_key('😺');
|
||||||
|
t.assertFalse(is_control(invalid));
|
||||||
|
t.assertEquals(invalid, '😺');
|
||||||
|
});
|
||||||
|
|
||||||
t.test('is_ascii properly discerns ascii chars', () => {
|
t.test('is_ascii properly discerns ascii chars', () => {
|
||||||
t.assertTrue(is_ascii('asjyverkjhsdf1928374'));
|
t.assertTrue(is_ascii('asjyverkjhsdf1928374'));
|
||||||
t.assertFalse(is_ascii('😺acalskjsdf'));
|
t.assertFalse(is_ascii('😺acalskjsdf'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
t.test('is_control fn works as expected', () => {
|
||||||
|
t.assertFalse(is_control('abc'));
|
||||||
|
t.assertTrue(is_control(String.fromCodePoint(0x01)));
|
||||||
|
t.assertFalse(is_control('😺'));
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('strlen fn returns expected length for multibyte characters', () => {
|
||||||
|
t.assertEquals(strlen('😺😸😹'), 3);
|
||||||
|
t.assertNotEquals('😺😸😹'.length, strlen('😺😸😹'));
|
||||||
|
|
||||||
|
// Skin tone modifier + base character
|
||||||
|
t.assertEquals(strlen('🤰🏼'), 2);
|
||||||
|
t.assertNotEquals('🤰🏼'.length, strlen('🤰🏼'));
|
||||||
|
|
||||||
|
// This has 4 sub-characters, and 3 zero-width-joiners
|
||||||
|
t.assertEquals(strlen('👨👩👧👦'), 7);
|
||||||
|
t.assertNotEquals('👨👩👧👦'.length, strlen('👨👩👧👦'));
|
||||||
|
});
|
||||||
|
|
||||||
|
t.test('truncate shortens strings', () => {
|
||||||
|
t.assertEquals(truncate('😺😸😹', 1), '😺');
|
||||||
|
t.assertEquals(truncate('😺😸😹', 5), '😺😸😹');
|
||||||
|
t.assertEquals(truncate('👨👩👧👦', 5), '👨👩👧');
|
||||||
|
});
|
||||||
|
@ -20,12 +20,13 @@ export async function main() {
|
|||||||
const t = await getTermios();
|
const t = await getTermios();
|
||||||
t.enableRawMode();
|
t.enableRawMode();
|
||||||
onExit(() => {
|
onExit(() => {
|
||||||
console.info('Exit handler called, disabling raw mode');
|
console.log('Exit handler called, disabling raw mode\r\n');
|
||||||
t.disableRawMode();
|
t.disableRawMode();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create the editor itself
|
// Create the editor itself
|
||||||
const editor = new Editor(getSize());
|
const editor = new Editor(getSize());
|
||||||
|
await editor.refreshScreen();
|
||||||
|
|
||||||
// The main event loop
|
// The main event loop
|
||||||
for await (const chunk of inputLoop()) {
|
for await (const chunk of inputLoop()) {
|
||||||
|
Loading…
Reference in New Issue
Block a user