From b395af2fc182e4ac464e60196229e3bba0a22c21 Mon Sep 17 00:00:00 2001 From: Aki Kareha Date: Wed, 25 Mar 2026 19:16:24 +0900 Subject: [PATCH] Add arrow keys --- internal/editor/editor.go | 160 +++++++++++++++++++++++++++--------- internal/editor/keyboard.go | 55 +++++++++++-- internal/editor/screen.go | 8 +- 3 files changed, 172 insertions(+), 51 deletions(-) diff --git a/internal/editor/editor.go b/internal/editor/editor.go index 55dff36..877664e 100644 --- a/internal/editor/editor.go +++ b/internal/editor/editor.go @@ -16,8 +16,8 @@ const ( ) type Editor struct { - scr *Screen - kb *Keyboard + scr *screen + kb *keyboard col, row int x, y int lines []string @@ -29,8 +29,8 @@ type Editor struct { func Init() *Editor { console.Raw() - scr := NewScreen() - kb := NewKeyboard() + scr := newScreen() + kb := newKeyboard() return &Editor{ scr: &scr, @@ -58,42 +58,85 @@ func (ed *Editor) runeCount() int { return utf8.RuneCountInString(ed.lines[ed.row]) } +func (ed *Editor) lineHeight(line string) int { + w, _ := ed.scr.size() + rc := utf8.RuneCountInString(line) + width := util.StringWidth(line, rc) + return 1 + max(width-1, 0)/w +} + func (ed *Editor) drawBuffer() { + _, h := ed.scr.size() + + y := 0 for i := 0; i < len(ed.lines); i++ { - console.MoveCursor(0, i) + var line string if ed.mode == modeInsert && i == ed.row { - console.Print(ed.head) - console.Print(ed.insert.String()) - console.Print(ed.tail) + line = ed.head + ed.insert.String() + ed.tail } else { - console.Print(ed.lines[i]) + line = ed.lines[i] } + + console.MoveCursor(0, y) + console.Print(line) + + y += ed.lineHeight(line) + if y >= h-1 { + break + } + } + + for ; y < h-1; y++ { + console.MoveCursor(0, y) + console.Print("~") } } func (ed *Editor) drawStatus() { - _, h := console.Size() + _, h := ed.scr.size() - console.MoveCursor(0, h-2) + console.MoveCursor(0, h-1) switch ed.mode { case modeCommand: - console.Print("-- [command] q: quit, i: insert, a: insert after --") + console.Print("c") case modeInsert: - console.Print("-- [insert] Esc: command mode --") + console.Print("i") } } func (ed *Editor) updateCursor() { + w, _ := ed.scr.size() + + var dy int switch ed.mode { case modeCommand: ed.row = min(max(ed.row, 0), max(len(ed.lines)-1, 0)) len := ed.runeCount() ed.col = min(ed.col, max(len-1, 0)) - ed.x = util.StringWidth(ed.lines[ed.row], ed.col) + + // XXX approximation + width := util.StringWidth(ed.lines[ed.row], ed.col) + ed.x = width % w + dy = width / w case modeInsert: - ed.x = util.StringWidth(ed.head+ed.insert.String(), ed.col) + // XXX approximation + width := util.StringWidth(ed.head+ed.insert.String(), ed.col) + ed.x = width % w + dy = width / w } - ed.y = ed.row + + y := 0 + for i := 0; i < ed.row; i++ { + var line string + if ed.mode == modeInsert && i == ed.row { + line = ed.head + ed.insert.String() + ed.tail + } else { + line = ed.lines[i] + } + + y += ed.lineHeight(line) + } + ed.y = y + dy } func (ed *Editor) repaint() { @@ -120,11 +163,18 @@ func (ed *Editor) exitInsert() { } func (ed *Editor) insertNewline() { - ed.lines[ed.row] = ed.head + ed.insert.String() - ed.lines = append(ed.lines, "") - copy(ed.lines[ed.row+1:], ed.lines[ed.row:]) + lines := make([]string, 0, len(ed.lines)+1) + lines = append(lines, ed.lines[:ed.row+1]...) + lines = append(lines, "") + if ed.row+1 < len(ed.lines) { + lines = append(lines, ed.lines[ed.row+1:]...) + } + + lines[ed.row] = ed.head + ed.insert.String() + lines[ed.row+1] = ed.tail + ed.lines = lines ed.row++ - ed.lines[ed.row] = ed.tail + ed.col = 0 ed.head = "" ed.insert = new(strings.Builder) @@ -139,33 +189,63 @@ func (ed *Editor) Main() { for { ed.repaint() - r := ed.kb.ReadRune() + k, r := ed.kb.readKey() switch ed.mode { case modeCommand: - switch r { - case 'q': - return - case 'i': - ed.enterInsert() - case 'a': - ed.enterInsertAfter() - case 'h': - ed.moveLeft(1) - case 'l': - ed.moveRight(1) - case 'j': - ed.moveDown(1) - case 'k': + switch k { + case keyNormal: + switch r { + case 'q': + return + case 'i': + ed.enterInsert() + case 'a': + ed.enterInsertAfter() + case 'h': + ed.moveLeft(1) + case 'l': + ed.moveRight(1) + case 'j': + ed.moveDown(1) + case 'k': + ed.moveUp(1) + } + case keyUp: ed.moveUp(1) + case keyDown: + ed.moveDown(1) + case keyRight: + ed.moveRight(1) + case keyLeft: + ed.moveLeft(1) + default: + // TODO ring } case modeInsert: - switch r { - case Escape: + switch k { + case keyNormal: + switch r { + case runeEscape: + ed.exitInsert() + case runeEnter: + ed.insertNewline() + default: + ed.insertRune(r) + } + case keyUp: ed.exitInsert() - case Enter: - ed.insertNewline() + ed.moveUp(1) + case keyDown: + ed.exitInsert() + ed.moveDown(1) + case keyRight: + ed.exitInsert() + ed.moveRight(1) + case keyLeft: + ed.exitInsert() + ed.moveLeft(1) default: - ed.insertRune(r) + // TODO ring } } } diff --git a/internal/editor/keyboard.go b/internal/editor/keyboard.go index 73f75f7..581477a 100644 --- a/internal/editor/keyboard.go +++ b/internal/editor/keyboard.go @@ -4,15 +4,56 @@ import ( "tea.kareha.org/lab/levi/internal/console" ) -const Escape rune = 0x1b -const Enter rune = '\r' +const runeEscape rune = 0x1b +const runeEnter rune = '\r' -type Keyboard struct{} +type key int -func NewKeyboard() Keyboard { - return Keyboard{} +const ( + keyNormal = iota + keyUp + keyDown + keyRight + keyLeft +) + +type keyboard struct { + buf []rune } -func (kb *Keyboard) ReadRune() rune { - return console.ReadRune() +func newKeyboard() keyboard { + return keyboard{ + buf: make([]rune, 0), + } +} + +func (kb *keyboard) readKey() (key, rune) { + if len(kb.buf) > 0 { + r := kb.buf[0] + kb.buf = kb.buf[1:] + return keyNormal, r + } + r := console.ReadRune() + if r != runeEscape { + return keyNormal, r + } + r2 := console.ReadRune() + if r2 != '[' { + kb.buf = append(kb.buf, r2) + return keyNormal, r + } + r3 := console.ReadRune() + switch r3 { + case 'A': + return keyUp, 0 + case 'B': + return keyDown, 0 + case 'C': + return keyRight, 0 + case 'D': + return keyLeft, 0 + } + kb.buf = append(kb.buf, r2) + kb.buf = append(kb.buf, r3) + return keyNormal, r } diff --git a/internal/editor/screen.go b/internal/editor/screen.go index d59080f..38372d4 100644 --- a/internal/editor/screen.go +++ b/internal/editor/screen.go @@ -4,18 +4,18 @@ import ( "tea.kareha.org/lab/levi/internal/console" ) -type Screen struct { +type screen struct { w, h int } -func NewScreen() Screen { +func newScreen() screen { w, h := console.Size() - return Screen{ + return screen{ w: w, h: h, } } -func (scr *Screen) Size() (int, int) { +func (scr *screen) size() (int, int) { return scr.w, scr.h }