Move the cursor

This commit is contained in:
Timothy Warren 2023-11-10 08:36:18 -05:00
parent c5e7d6e209
commit 1723219452
5 changed files with 81 additions and 29 deletions

View File

@ -50,7 +50,7 @@ deno-test:
# Create test coverage report with deno # Create test coverage report with deno
deno-coverage: deno-coverage:
deno test --allow-all --coverage=.deno-cover deno test --allow-all --coverage=.deno-cover
deno coverage --lcov .deno-cover deno coverage --unstable-ffi .deno-cover
# Run with deno # Run with deno
deno-run: deno-run:

View File

@ -9,7 +9,7 @@ export const Ansi = {
HideCursor: esc`?25l`, HideCursor: esc`?25l`,
ShowCursor: esc`?25h`, 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`;
}, },
}; };

View File

@ -3,6 +3,7 @@ import Buffer from './buffer.ts';
import { import {
ctrl_key, ctrl_key,
importDefaultForRuntime, importDefaultForRuntime,
IPoint,
ITerminalSize, ITerminalSize,
truncate, truncate,
VERSION, VERSION,
@ -10,14 +11,21 @@ import {
export class Editor { export class Editor {
#buffer: Buffer; #buffer: Buffer;
#screenRows: number; #screen: ITerminalSize;
#screenCols: number; #cursor: IPoint;
constructor(terminalSize: ITerminalSize) { constructor(terminalSize: ITerminalSize) {
this.#buffer = new Buffer(); this.#buffer = new Buffer();
this.#screenRows = terminalSize.rows; this.#screen = terminalSize;
this.#screenCols = terminalSize.cols; this.#cursor = {
x: 0,
y: 0,
};
} }
// --------------------------------------------------------------------------
// Command/input mapping
// --------------------------------------------------------------------------
/** /**
* Determine what to do based on input * Determine what to do based on input
* @param input - the decoded chunk of stdin * @param input - the decoded chunk of stdin
@ -28,14 +36,37 @@ export class Editor {
this.clearScreen().then(() => {}); this.clearScreen().then(() => {});
return false; return false;
default: case 'w':
case 's':
case 'a':
case 'd':
this.moveCursor(input);
break;
}
return true; return true;
} }
private moveCursor(char: string): void {
switch (char) {
case 'a':
this.#cursor.x--;
break;
case 'd':
this.#cursor.x++;
break;
case 'w':
this.#cursor.y--;
break;
case 's':
this.#cursor.y++;
break;
}
} }
// ------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------
// Terminal Output / Drawing // Terminal Output / Drawing
// ------------------------------------------------------------------------------------------------------------------- // --------------------------------------------------------------------------
/** /**
* Clear the screen and write out the buffer * Clear the screen and write out the buffer
@ -44,6 +75,9 @@ export class Editor {
this.#buffer.append(Ansi.HideCursor); this.#buffer.append(Ansi.HideCursor);
this.#buffer.append(Ansi.ResetCursor); this.#buffer.append(Ansi.ResetCursor);
this.drawRows(); this.drawRows();
this.#buffer.append(
Ansi.moveCursor(this.#cursor.y + 1, this.#cursor.x + 1),
);
this.#buffer.append(Ansi.ShowCursor); this.#buffer.append(Ansi.ShowCursor);
await this.writeToScreen(); await this.writeToScreen();
@ -61,11 +95,13 @@ export class Editor {
} }
private drawPlaceholderRows(): void { private drawPlaceholderRows(): void {
for (let y = 0; y < this.#screenRows; y++) { for (let y = 0; y < this.#screen.rows; y++) {
if (y === Math.trunc(this.#screenRows / 2)) { if (y === Math.trunc(this.#screen.rows / 2)) {
const message = `Kilo editor -- version ${VERSION}`; const message = `Kilo editor -- version ${VERSION}`;
const messageLen = (message.length > this.#screenCols) ? this.#screenCols : message.length; const messageLen = (message.length > this.#screen.cols)
let padding = Math.trunc((this.#screenCols - messageLen) / 2); ? this.#screen.cols
: message.length;
let padding = Math.trunc((this.#screen.cols - messageLen) / 2);
if (padding > 0) { if (padding > 0) {
this.#buffer.append('~'); this.#buffer.append('~');
padding -= 1; padding -= 1;
@ -79,7 +115,7 @@ export class Editor {
} }
this.#buffer.append(Ansi.ClearLine); this.#buffer.append(Ansi.ClearLine);
if (y < this.#screenRows - 1) { if (y < this.#screen.rows - 1) {
this.#buffer.appendLine(''); this.#buffer.appendLine('');
} }
} }

View File

@ -1,17 +1,15 @@
/** // ----------------------------------------------------------------------------
* The shared test interface, so tests can be run by both runtimes // General types
*/ // ----------------------------------------------------------------------------
export interface ITestBase {
test(name: string, fn: () => void): void; export interface IPoint {
assertEquals(actual: unknown, expected: unknown): void; x: number;
assertNotEquals(actual: unknown, expected: unknown): void; y: number;
assertStrictEquals(actual: unknown, expected: unknown): void;
assertExists(actual: unknown): void;
assertInstanceOf(actual: unknown, expectedType: any): void;
assertTrue(actual: boolean): void;
assertFalse(actual: boolean): void;
} }
// ----------------------------------------------------------------------------
// Runtime adapter interfaces
// ----------------------------------------------------------------------------
/** /**
* The native functions for terminal settings * The native functions for terminal settings
*/ */
@ -69,3 +67,21 @@ export interface IRuntime {
*/ */
onExit(cb: () => void): void; onExit(cb: () => void): void;
} }
// ----------------------------------------------------------------------------
// Testing
// ----------------------------------------------------------------------------
/**
* The shared test interface, so tests can be run by both runtimes
*/
export interface ITestBase {
test(name: string, fn: () => void): void;
assertEquals(actual: unknown, expected: unknown): void;
assertNotEquals(actual: unknown, expected: unknown): void;
assertStrictEquals(actual: unknown, expected: unknown): void;
assertExists(actual: unknown): void;
assertInstanceOf(actual: unknown, expectedType: any): void;
assertTrue(actual: boolean): void;
assertFalse(actual: boolean): void;
}

View File

@ -1,6 +1,6 @@
// --------------------------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Strings // Strings
// --------------------------------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------
/** /**
* Split a string by graphemes, not just bytes * Split a string by graphemes, not just bytes