From 3fe8bd6e654efdb4b9d7f947b734d386dd400a1e Mon Sep 17 00:00:00 2001 From: ajstrongdev Date: Mon, 31 Aug 2026 11:01:27 +0100 Subject: [PATCH 1/4] chore: reorganise folders --- package.json | 2 +- index.ts => src/index.ts | 10 +++++----- src/{ => services}/buffer.ts | 2 +- src/{ => services}/cursor.ts | 0 src/{ => services}/editor.ts | 2 +- src/{ => services}/renderer.ts | 0 src/{ => services}/terminal.ts | 0 src/{ => services}/viewport.ts | 0 src/tests/buffer.test.ts | 4 ++-- src/tests/cursor.test.ts | 4 ++-- src/tests/editor.test.ts | 2 +- src/tests/terminal.test.ts | 2 +- src/tests/utils.test.ts | 2 +- src/tests/viewport.test.ts | 4 ++-- src/{types.ts => types/index.ts} | 0 src/{utils.ts => utils/inputs.ts} | 2 +- 16 files changed, 18 insertions(+), 18 deletions(-) rename index.ts => src/index.ts (79%) rename src/{ => services}/buffer.ts (94%) rename src/{ => services}/cursor.ts (100%) rename src/{ => services}/editor.ts (95%) rename src/{ => services}/renderer.ts (100%) rename src/{ => services}/terminal.ts (100%) rename src/{ => services}/viewport.ts (100%) rename src/{types.ts => types/index.ts} (100%) rename src/{utils.ts => utils/inputs.ts} (95%) diff --git a/package.json b/package.json index 2971570..b261201 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fae", - "module": "./index.ts", + "module": "./src/index.ts", "type": "module", "private": true, "devDependencies": { diff --git a/index.ts b/src/index.ts similarity index 79% rename from index.ts rename to src/index.ts index ed9b234..119d1f0 100644 --- a/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ -import { Editor } from "./src/editor.ts"; -import { Renderer } from "./src/renderer.ts"; -import { Terminal } from "./src/terminal.ts"; -import { parseKey } from "./src/utils.ts"; -import { Viewport } from "./src/viewport.ts"; +import { Editor } from "./services/editor.ts"; +import { Renderer } from "./services/renderer.ts"; +import { Terminal } from "./services/terminal.ts"; +import { parseKey } from "./utils/inputs.ts"; +import { Viewport } from "./services/viewport.ts"; const editor = new Editor(); diff --git a/src/buffer.ts b/src/services/buffer.ts similarity index 94% rename from src/buffer.ts rename to src/services/buffer.ts index 51562e3..61b4a79 100644 --- a/src/buffer.ts +++ b/src/services/buffer.ts @@ -1,4 +1,4 @@ -import type { BufferType, InsertType } from "./types"; +import type { BufferType, InsertType } from "../types"; export class TextBuffer { lines: string[] = [""]; diff --git a/src/cursor.ts b/src/services/cursor.ts similarity index 100% rename from src/cursor.ts rename to src/services/cursor.ts diff --git a/src/editor.ts b/src/services/editor.ts similarity index 95% rename from src/editor.ts rename to src/services/editor.ts index 6cfd1c2..157c510 100644 --- a/src/editor.ts +++ b/src/services/editor.ts @@ -1,6 +1,6 @@ import { Cursor } from "./cursor.ts"; import { TextBuffer } from "./buffer.ts"; -import type { BufferType, InsertType } from "./types"; +import type { BufferType, InsertType } from "../types"; export class Editor { readonly buffer: TextBuffer; diff --git a/src/renderer.ts b/src/services/renderer.ts similarity index 100% rename from src/renderer.ts rename to src/services/renderer.ts diff --git a/src/terminal.ts b/src/services/terminal.ts similarity index 100% rename from src/terminal.ts rename to src/services/terminal.ts diff --git a/src/viewport.ts b/src/services/viewport.ts similarity index 100% rename from src/viewport.ts rename to src/services/viewport.ts diff --git a/src/tests/buffer.test.ts b/src/tests/buffer.test.ts index 8b97591..a1021b5 100644 --- a/src/tests/buffer.test.ts +++ b/src/tests/buffer.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { TextBuffer } from "../buffer"; -import type { BufferType, InsertType } from "../types.ts"; +import { TextBuffer } from "../services/buffer.ts"; +import type { BufferType, InsertType } from "../types"; test("starts on empty line", () => { const buffer = new TextBuffer(); diff --git a/src/tests/cursor.test.ts b/src/tests/cursor.test.ts index d9db87a..7f91df3 100644 --- a/src/tests/cursor.test.ts +++ b/src/tests/cursor.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { TextBuffer } from "../buffer"; -import { Cursor } from "../cursor"; +import { TextBuffer } from "../services/buffer.ts"; +import { Cursor } from "../services/cursor.ts"; test("starts at 0,0", () => { const cursor = new Cursor(); diff --git a/src/tests/editor.test.ts b/src/tests/editor.test.ts index 02ea3ff..90c0a03 100644 --- a/src/tests/editor.test.ts +++ b/src/tests/editor.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test"; -import { Editor } from "../editor"; +import { Editor } from "../services/editor.ts"; test("starts on empty buffer", () => { const editor = new Editor(); diff --git a/src/tests/terminal.test.ts b/src/tests/terminal.test.ts index 9996726..f310ff5 100644 --- a/src/tests/terminal.test.ts +++ b/src/tests/terminal.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test"; -import { Terminal } from "../terminal"; +import { Terminal } from "../services/terminal.ts"; test("fetches terminal size", () => { const terminal = new Terminal(); diff --git a/src/tests/utils.test.ts b/src/tests/utils.test.ts index 1a8a710..0017641 100644 --- a/src/tests/utils.test.ts +++ b/src/tests/utils.test.ts @@ -1,5 +1,5 @@ import { expect, test } from "bun:test"; -import { parseKey } from "../utils.ts"; +import { parseKey } from "../utils/inputs.ts"; test("parse character", () => { expect(parseKey("a")).toEqual({ diff --git a/src/tests/viewport.test.ts b/src/tests/viewport.test.ts index b97e4dc..7d421d6 100644 --- a/src/tests/viewport.test.ts +++ b/src/tests/viewport.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "bun:test"; -import { Viewport } from "../viewport"; -import { Cursor } from "../cursor"; +import { Viewport } from "../services/viewport.ts"; +import { Cursor } from "../services/cursor.ts"; test("viewport shows cursor (right)", () => { const viewport = new Viewport(8, 8); diff --git a/src/types.ts b/src/types/index.ts similarity index 100% rename from src/types.ts rename to src/types/index.ts diff --git a/src/utils.ts b/src/utils/inputs.ts similarity index 95% rename from src/utils.ts rename to src/utils/inputs.ts index 9358146..923fbc6 100644 --- a/src/utils.ts +++ b/src/utils/inputs.ts @@ -1,4 +1,4 @@ -import type { Key } from "./types.ts"; +import type { Key } from "../types"; export function parseKey(input: string): Key | null { if (input === "\x1b[A") { From 6332d50a1d075f83faf8069cace7c2dc18cab3a0 Mon Sep 17 00:00:00 2001 From: ajstrongdev Date: Mon, 31 Aug 2026 12:17:02 +0100 Subject: [PATCH 2/4] feat: input handler --- src/index.ts | 35 ++++++++--------------------------- src/services/inputs.ts | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 27 deletions(-) create mode 100644 src/services/inputs.ts diff --git a/src/index.ts b/src/index.ts index 119d1f0..f799050 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import { Renderer } from "./services/renderer.ts"; import { Terminal } from "./services/terminal.ts"; import { parseKey } from "./utils/inputs.ts"; import { Viewport } from "./services/viewport.ts"; +import { InputHandler } from "./services/inputs.ts"; const editor = new Editor(); @@ -11,40 +12,20 @@ let size = terminal.size(); const viewport = new Viewport(size.width, size.height); const renderer = new Renderer(terminal, viewport); +const handler = new InputHandler(editor); terminal.start(); viewport.followCursor(editor.cursor); renderer.render(editor); -process.stdin.on("data", (input: string) => { - const key = parseKey(input); +process.stdin.on("data", (data: string) => { + const key = parseKey(data); if (key === null) return; - switch (key.type) { - case "character": - editor.insert(key.value); - break; - case "backspace": - editor.backspace(); - break; - case "enter": - editor.newline(); - break; - case "left": - editor.moveLeft(); - break; - case "right": - editor.moveRight(); - break; - case "up": - editor.moveUp(); - break; - case "down": - editor.moveDown(); - break; - case "quit": - terminal.stop(); - process.exit(0); + if (key.type === "quit") { + terminal.stop(); + process.exit(0); } + handler.handle(key); viewport.followCursor(editor.cursor); renderer.render(editor); }); diff --git a/src/services/inputs.ts b/src/services/inputs.ts new file mode 100644 index 0000000..1957127 --- /dev/null +++ b/src/services/inputs.ts @@ -0,0 +1,22 @@ +import type { Key } from "../types"; +import { Editor } from "../services/editor"; + +export class InputHandler { + constructor(private editor: Editor) {} + private commands = new Map void>([ + ["backspace", () => this.editor.backspace()], + ["enter", () => this.editor.newline()], + ["left", () => this.editor.moveLeft()], + ["right", () => this.editor.moveRight()], + ["down", () => this.editor.moveDown()], + ["up", () => this.editor.moveUp()], + ]); + + handle(key: Key) { + if (key.type === "character") { + this.editor.insert(key.value); + return; + } + this.commands.get(key.type)?.(); + } +} From 024ed76f036e58b2c7486f7452429fb72ec0e8fd Mon Sep 17 00:00:00 2001 From: ajstrongdev Date: Mon, 31 Aug 2026 12:41:07 +0100 Subject: [PATCH 3/4] feat: terminal resize window --- src/index.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/index.ts b/src/index.ts index f799050..ba37fe0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,3 +29,11 @@ process.stdin.on("data", (data: string) => { viewport.followCursor(editor.cursor); renderer.render(editor); }); + +process.stdout.on("resize", () => { + size = terminal.size(); + viewport.width = size.width; + viewport.height = size.height; + viewport.followCursor(editor.cursor); + renderer.render(editor); +}); From 294428f1a063121a6e5e61a034d4e0dc5d12e252 Mon Sep 17 00:00:00 2001 From: ajstrongdev Date: Mon, 31 Aug 2026 13:00:23 +0100 Subject: [PATCH 4/4] refactor: introduce event listener service --- src/index.ts | 27 ++++----------------------- src/services/listeners.ts | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 23 deletions(-) create mode 100644 src/services/listeners.ts diff --git a/src/index.ts b/src/index.ts index ba37fe0..360b38c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,36 +4,17 @@ import { Terminal } from "./services/terminal.ts"; import { parseKey } from "./utils/inputs.ts"; import { Viewport } from "./services/viewport.ts"; import { InputHandler } from "./services/inputs.ts"; +import { Listener } from "./services/listeners.ts"; const editor = new Editor(); - const terminal = new Terminal(); -let size = terminal.size(); - +const size = terminal.size(); const viewport = new Viewport(size.width, size.height); const renderer = new Renderer(terminal, viewport); const handler = new InputHandler(editor); +const listeners = new Listener(editor, renderer, handler, terminal, viewport); terminal.start(); viewport.followCursor(editor.cursor); renderer.render(editor); - -process.stdin.on("data", (data: string) => { - const key = parseKey(data); - if (key === null) return; - if (key.type === "quit") { - terminal.stop(); - process.exit(0); - } - handler.handle(key); - viewport.followCursor(editor.cursor); - renderer.render(editor); -}); - -process.stdout.on("resize", () => { - size = terminal.size(); - viewport.width = size.width; - viewport.height = size.height; - viewport.followCursor(editor.cursor); - renderer.render(editor); -}); +listeners.start(); diff --git a/src/services/listeners.ts b/src/services/listeners.ts new file mode 100644 index 0000000..e8c1bca --- /dev/null +++ b/src/services/listeners.ts @@ -0,0 +1,38 @@ +import type { Editor } from "./editor.ts"; +import type { Renderer } from "./renderer.ts"; +import type { InputHandler } from "./inputs.ts"; +import type { Terminal } from "./terminal.ts"; +import type { Viewport } from "./viewport.ts"; +import { parseKey } from "../utils/inputs.ts"; + +export class Listener { + constructor( + private editor: Editor, + private renderer: Renderer, + private handler: InputHandler, + private terminal: Terminal, + private viewport: Viewport, + ) {} + + start() { + process.stdin.on("data", (data: string) => { + const key = parseKey(data); + if (key === null) return; + if (key.type === "quit") { + this.terminal.stop(); + process.exit(0); + } + this.handler.handle(key); + this.viewport.followCursor(this.editor.cursor); + this.renderer.render(this.editor); + }); + + process.stdout.on("resize", () => { + const size = this.terminal.size(); + this.viewport.height = size.height; + this.viewport.width = size.width; + this.viewport.followCursor(this.editor.cursor); + this.renderer.render(this.editor); + }); + } +}