Add arrow keys

This commit is contained in:
2026-03-25 19:16:24 +09:00
parent c7447d6f13
commit b395af2fc1
3 changed files with 172 additions and 51 deletions

View File

@@ -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
}
}
}

View File

@@ -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
}

View File

@@ -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
}