This commit is contained in:
2026-03-26 11:50:27 +09:00
parent d9d75b9305
commit 24cdd59e12
6 changed files with 124 additions and 155 deletions

View File

@@ -2,9 +2,7 @@ package console
import (
"fmt"
"io"
"os"
"unicode/utf8"
"golang.org/x/term"
)
@@ -25,7 +23,7 @@ func Raw() {
func Cooked() {
if state == nil {
panic("state is nil")
panic("invalid state")
}
term.Restore(int(os.Stdin.Fd()), state)
}
@@ -53,47 +51,7 @@ func ShowCursor() {
func Size() (int, int) {
w, h, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil {
panic(err)
return 80, 24
}
return w, h
}
func runeSize(b byte) int {
switch {
case b&0x80 == 0:
return 1
case b&0xe0 == 0xc0:
return 2
case b&0xf0 == 0xe0:
return 3
case b&0xf8 == 0xf0:
return 4
default:
return -1 // invalid
}
}
func ReadRune() rune {
buf := make([]byte, 1)
_, err := io.ReadFull(os.Stdin, buf)
if err != nil {
panic(err)
}
expected := runeSize(buf[0])
if expected == -1 {
panic("Invalid UTF-8 head")
}
full := make([]byte, expected)
full[0] = buf[0]
if expected > 1 {
_, err := io.ReadFull(os.Stdin, full[1:])
if err != nil {
panic(err)
}
}
r, size := utf8.DecodeRune(full)
if r == utf8.RuneError && size == 1 {
panic("Invalid UTF-8 body")
}
return r
}

95
internal/console/input.go Normal file
View File

@@ -0,0 +1,95 @@
package console
import (
"io"
"os"
"unicode/utf8"
)
type Key int
const (
KeyNormal = iota
KeyUp
KeyDown
KeyRight
KeyLeft
)
const RuneEscape rune = 0x1b
const RuneEnter rune = '\r'
const RuneBackspace rune = '\b'
const RuneDelete rune = 0x7f
var buf []rune = make([]rune, 0)
func runeSize(b byte) int {
switch {
case b&0x80 == 0:
return 1
case b&0xe0 == 0xc0:
return 2
case b&0xf0 == 0xe0:
return 3
case b&0xf8 == 0xf0:
return 4
default:
return -1 // invalid
}
}
func readRune() rune {
buf := make([]byte, 1)
_, err := io.ReadFull(os.Stdin, buf)
if err != nil {
panic(err)
}
expected := runeSize(buf[0])
if expected == -1 {
panic("Invalid UTF-8 head")
}
full := make([]byte, expected)
full[0] = buf[0]
if expected > 1 {
_, err := io.ReadFull(os.Stdin, full[1:])
if err != nil {
panic(err)
}
}
r, size := utf8.DecodeRune(full)
if r == utf8.RuneError && size == 1 {
panic("Invalid UTF-8 body")
}
return r
}
func ReadKey() (Key, rune) {
if len(buf) > 0 {
r := buf[0]
buf = buf[1:]
return KeyNormal, r
}
r := readRune()
if r != RuneEscape {
return KeyNormal, r
}
r2 := readRune()
if r2 != '[' {
buf = append(buf, r2)
return KeyNormal, r
}
r3 := readRune()
switch r3 {
case 'A':
return KeyUp, 0
case 'B':
return KeyDown, 0
case 'C':
return KeyRight, 0
case 'D':
return KeyLeft, 0
}
buf = append(buf, r2)
buf = append(buf, r3)
return KeyNormal, r
}

View File

@@ -1,4 +1,4 @@
package util
package console
import (
"fmt"

View File

@@ -7,7 +7,6 @@ import (
"unicode/utf8"
"tea.kareha.org/lab/levi/internal/console"
"tea.kareha.org/lab/levi/internal/util"
)
type mode int
@@ -18,8 +17,6 @@ const (
)
type Editor struct {
scr *screen
kb *keyboard
col, row int
x, y int
vrow int
@@ -55,12 +52,7 @@ func Init(args []string) *Editor {
console.Raw()
scr := newScreen()
kb := newKeyboard()
return &Editor{
scr: &scr,
kb: &kb,
col: 0,
row: 0,
x: 0,
@@ -95,14 +87,14 @@ func (ed *Editor) runeCount() int {
}
func (ed *Editor) lineHeight(line string) int {
w, _ := ed.scr.size()
w, _ := console.Size()
rc := utf8.RuneCountInString(line)
width := util.StringWidth(line, rc)
width := console.StringWidth(line, rc)
return 1 + max(width-1, 0)/w
}
func (ed *Editor) drawBuffer() {
_, h := ed.scr.size()
_, h := console.Size()
y := 0
for i := ed.vrow; i < len(ed.lines); i++ {
@@ -114,7 +106,7 @@ func (ed *Editor) drawBuffer() {
}
console.MoveCursor(0, y)
util.Print(line)
console.Print(line)
y += ed.lineHeight(line)
if y >= h-1 {
@@ -124,24 +116,24 @@ func (ed *Editor) drawBuffer() {
for ; y < h-1; y++ {
console.MoveCursor(0, y)
util.Print("~")
console.Print("~")
}
}
func (ed *Editor) drawStatus() {
_, h := ed.scr.size()
_, h := console.Size()
console.MoveCursor(0, h-1)
switch ed.mode {
case modeCommand:
util.Print("c")
console.Print("c")
case modeInsert:
util.Print("i")
console.Print("i")
}
}
func (ed *Editor) updateCursor() {
w, h := ed.scr.size()
w, h := console.Size()
var dy int
switch ed.mode {
@@ -151,12 +143,12 @@ func (ed *Editor) updateCursor() {
ed.col = min(ed.col, max(len-1, 0))
// XXX approximation
width := util.StringWidth(ed.lines[ed.row], ed.col)
width := console.StringWidth(ed.lines[ed.row], ed.col)
ed.x = width % w
dy = width / w
case modeInsert:
// XXX approximation
width := util.StringWidth(ed.head+ed.insert.String(), ed.col)
width := console.StringWidth(ed.head+ed.insert.String(), ed.col)
ed.x = width % w
dy = width / w
}
@@ -249,11 +241,11 @@ func (ed *Editor) Main() {
for {
ed.repaint()
k, r := ed.kb.readKey()
k, r := console.ReadKey()
switch ed.mode {
case modeCommand:
switch k {
case keyNormal:
case console.KeyNormal:
switch r {
case 'q':
return
@@ -272,42 +264,42 @@ func (ed *Editor) Main() {
case 'x':
ed.deleteRune(1)
}
case keyUp:
case console.KeyUp:
ed.moveUp(1)
case keyDown:
case console.KeyDown:
ed.moveDown(1)
case keyRight:
case console.KeyRight:
ed.moveRight(1)
case keyLeft:
case console.KeyLeft:
ed.moveLeft(1)
default:
// TODO ring
}
case modeInsert:
switch k {
case keyNormal:
case console.KeyNormal:
switch r {
case runeEscape:
case console.RuneEscape:
ed.exitInsert()
case runeEnter:
case console.RuneEnter:
ed.insertNewline()
case runeBackspace:
case console.RuneBackspace:
ed.deleteBefore()
case runeDelete:
case console.RuneDelete:
ed.deleteBefore()
default:
ed.insertRune(r)
}
case keyUp:
case console.KeyUp:
ed.exitInsert()
ed.moveUp(1)
case keyDown:
case console.KeyDown:
ed.exitInsert()
ed.moveDown(1)
case keyRight:
case console.KeyRight:
ed.exitInsert()
ed.moveRight(1)
case keyLeft:
case console.KeyLeft:
ed.exitInsert()
ed.moveLeft(1)
default:

View File

@@ -1,61 +0,0 @@
package editor
import (
"tea.kareha.org/lab/levi/internal/console"
)
const runeEscape rune = 0x1b
const runeEnter rune = '\r'
const runeBackspace rune = '\b'
const runeDelete rune = 0x7f
type key int
const (
keyNormal = iota
keyUp
keyDown
keyRight
keyLeft
)
type keyboard struct {
buf []rune
}
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

@@ -1,15 +0,0 @@
package editor
import (
"tea.kareha.org/lab/levi/internal/console"
)
type screen struct{}
func newScreen() screen {
return screen{}
}
func (scr *screen) size() (int, int) {
return console.Size()
}