From 3fb866072076da548d4c0bee544f963f33fb74dc Mon Sep 17 00:00:00 2001 From: Aki Kareha Date: Thu, 26 Mar 2026 23:11:48 +0900 Subject: [PATCH] Extract termi --- Makefile | 3 ++ go.mod | 5 +- go.sum | 2 + internal/console/console.go | 65 ------------------------- internal/console/input.go | 95 ------------------------------------- internal/console/output.go | 92 ----------------------------------- internal/editor/command.go | 2 +- internal/editor/editor.go | 12 ++--- internal/editor/main.go | 42 ++++++++-------- internal/editor/view.go | 42 ++++++++-------- 10 files changed, 58 insertions(+), 302 deletions(-) delete mode 100644 internal/console/console.go delete mode 100644 internal/console/input.go delete mode 100644 internal/console/output.go diff --git a/Makefile b/Makefile index be1ec61..f3f3109 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ all: build +env: + go env -w GOPRIVATE=tea.kareha.org + build: go build -o levi ./cmd/levi diff --git a/go.mod b/go.mod index 1147796..767c754 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,9 @@ module tea.kareha.org/lab/levi go 1.25.0 -require golang.org/x/term v0.41.0 +require ( + golang.org/x/term v0.41.0 // indirect + tea.kareha.org/lab/termi v0.0.0-20260326135653-28299eeba224 +) require golang.org/x/sys v0.42.0 // indirect diff --git a/go.sum b/go.sum index 91a25f5..cd2a563 100644 --- a/go.sum +++ b/go.sum @@ -2,3 +2,5 @@ golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +tea.kareha.org/lab/termi v0.0.0-20260326135653-28299eeba224 h1:nf3D+GjzIP9ab7fXIZmaFloRFD478SV4+bPhB9Wa1U0= +tea.kareha.org/lab/termi v0.0.0-20260326135653-28299eeba224/go.mod h1:+ticjUt1pyuink8Qip4QHN3GGz1QaNPRuJrM+jWRLgU= diff --git a/internal/console/console.go b/internal/console/console.go deleted file mode 100644 index 371bbb8..0000000 --- a/internal/console/console.go +++ /dev/null @@ -1,65 +0,0 @@ -package console - -import ( - "fmt" - "os" - - "golang.org/x/term" -) - -var state *term.State - -func Raw() { - if state != nil { - term.Restore(int(os.Stdin.Fd()), state) - state = nil - } - s, err := term.MakeRaw(int(os.Stdin.Fd())) - if err != nil { - panic(err) - } - state = s -} - -func Cooked() { - if state == nil { - panic("invalid state") - } - term.Restore(int(os.Stdin.Fd()), state) -} - -func Clear() { - fmt.Print("\x1b[2J") -} - -func HomeCursor() { - fmt.Print("\x1b[H") -} - -func MoveCursor(x, y int) { - fmt.Printf("\x1b[%d;%dH", y+1, x+1) -} - -func HideCursor() { - fmt.Print("\x1b[?25l") -} - -func ShowCursor() { - fmt.Print("\x1b[?25h") -} - -func Size() (int, int) { - w, h, err := term.GetSize(int(os.Stdout.Fd())) - if err != nil { - return 80, 24 - } - return w, h -} - -func EnableInvert() { - fmt.Print("\x1b[7m") -} - -func DisableInvert() { - fmt.Print("\x1b[0m") -} diff --git a/internal/console/input.go b/internal/console/input.go deleted file mode 100644 index 43ef138..0000000 --- a/internal/console/input.go +++ /dev/null @@ -1,95 +0,0 @@ -package console - -import ( - "io" - "os" - "unicode/utf8" -) - -type Key int - -const ( - KeyNormal Key = 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 -} diff --git a/internal/console/output.go b/internal/console/output.go deleted file mode 100644 index 9090e6b..0000000 --- a/internal/console/output.go +++ /dev/null @@ -1,92 +0,0 @@ -package console - -import ( - "fmt" - "unicode" -) - -func isWide(r rune) bool { - return r >= 0x1100 && (r <= 0x115f || // Hangul Jamo - r == 0x2329 || r == 0x232a || - (r >= 0x2e80 && r <= 0xa4cf) || - (r >= 0xac00 && r <= 0xd7a3) || - (r >= 0xf900 && r <= 0xfaff) || - (r >= 0xfe10 && r <= 0xfe19) || - (r >= 0xfe30 && r <= 0xfe6f) || - (r >= 0xff00 && r <= 0xff60) || - (r >= 0xffe0 && r <= 0xffe6)) -} - -func isEmoji(r rune) bool { - return r >= 0x1f300 && r <= 0x1faff -} - -const tabWidth = 4 - -func runeWidth(r rune, x int) int { - // tab - if r == '\t' { - return tabWidth - (x % tabWidth) - } - - // control code - if r == 0 { - return 0 - } - if r < 32 || (r >= 0x7f && r < 0xa0) { - return 0 - } - - // combining mark - if unicode.Is(unicode.Mn, r) { - return 0 - } - - // wide (loose CJK) - if isWide(r) { - return 2 - } - - // emoji - if isEmoji(r) { - return 2 - } - - return 1 -} - -func StringWidth(s string, col int) int { - sum := 0 - i := 0 - for _, r := range s { - if i >= col { - break - } - w := runeWidth(r, sum) - sum += w - i++ - } - return sum -} - -func Print(s string) { - x := 0 - for _, r := range s { - if r == '\t' { - spaces := tabWidth - (x % tabWidth) - for i := 0; i < spaces; i++ { - fmt.Print(" ") - } - x += spaces - } else { - fmt.Printf("%c", r) - x += runeWidth(r, x) - } - } -} - -func Printf(format string, a ...any) (n int, err error) { - s := fmt.Sprintf(format, a...) - Print(s) - return len(s), nil -} diff --git a/internal/editor/command.go b/internal/editor/command.go index 26878fd..b174b7e 100644 --- a/internal/editor/command.go +++ b/internal/editor/command.go @@ -52,6 +52,6 @@ func (ed *Editor) deleteRune(n int) { } rc := ed.runeCount() if ed.col >= rc { - ed.col = max(rc - 1, 0) + ed.col = max(rc-1, 0) } } diff --git a/internal/editor/editor.go b/internal/editor/editor.go index c5dcaa6..f03e218 100644 --- a/internal/editor/editor.go +++ b/internal/editor/editor.go @@ -5,7 +5,7 @@ import ( "strings" "unicode/utf8" - "tea.kareha.org/lab/levi/internal/console" + "tea.kareha.org/lab/termi" ) type mode int @@ -72,7 +72,7 @@ func Init(args []string) *Editor { ed.load() - console.Raw() + termi.Raw() return ed } @@ -88,10 +88,10 @@ func (ed *Editor) save() { } func (ed *Editor) Finish() { - console.Clear() - console.HomeCursor() - console.Cooked() - console.ShowCursor() + termi.Clear() + termi.HomeCursor() + termi.Cooked() + termi.ShowCursor() ed.save() } diff --git a/internal/editor/main.go b/internal/editor/main.go index f15792d..b8507cb 100644 --- a/internal/editor/main.go +++ b/internal/editor/main.go @@ -3,7 +3,7 @@ package editor import ( "unicode/utf8" - "tea.kareha.org/lab/levi/internal/console" + "tea.kareha.org/lab/termi" ) func (ed *Editor) exitInsert() { @@ -53,12 +53,12 @@ func (ed *Editor) Main() { for { ed.repaint() - k, r := console.ReadKey() + key := termi.ReadKey() switch ed.mode { case modeCommand: - switch k { - case console.KeyNormal: - switch r { + switch key.Kind { + case termi.KeyRune: + switch key.Rune { case 'q': return case 'i': @@ -78,42 +78,42 @@ func (ed *Editor) Main() { default: ed.ring() } - case console.KeyUp: + case termi.KeyUp: ed.moveUp(1) - case console.KeyDown: + case termi.KeyDown: ed.moveDown(1) - case console.KeyRight: + case termi.KeyRight: ed.moveRight(1) - case console.KeyLeft: + case termi.KeyLeft: ed.moveLeft(1) default: ed.ring() } case modeInsert: - switch k { - case console.KeyNormal: - switch r { - case console.RuneEscape: + switch key.Kind { + case termi.KeyRune: + switch key.Rune { + case termi.RuneEscape: ed.exitInsert() - case console.RuneEnter: + case termi.RuneEnter: ed.insertNewline() - case console.RuneBackspace: + case termi.RuneBackspace: ed.deleteBefore() - case console.RuneDelete: + case termi.RuneDelete: ed.deleteBefore() default: - ed.insertRune(r) + ed.insertRune(key.Rune) } - case console.KeyUp: + case termi.KeyUp: ed.exitInsert() ed.moveUp(1) - case console.KeyDown: + case termi.KeyDown: ed.exitInsert() ed.moveDown(1) - case console.KeyRight: + case termi.KeyRight: ed.exitInsert() ed.moveRight(1) - case console.KeyLeft: + case termi.KeyLeft: ed.exitInsert() ed.moveLeft(1) default: diff --git a/internal/editor/view.go b/internal/editor/view.go index 668be03..a7e4f2c 100644 --- a/internal/editor/view.go +++ b/internal/editor/view.go @@ -3,18 +3,18 @@ package editor import ( "unicode/utf8" - "tea.kareha.org/lab/levi/internal/console" + "tea.kareha.org/lab/termi" ) func (ed *Editor) lineHeight(line string) int { - w, _ := console.Size() + w, _ := termi.Size() rc := utf8.RuneCountInString(line) - width := console.StringWidth(line, rc) + width := termi.StringWidth(line, rc) return 1 + max(width-1, 0)/w } func (ed *Editor) drawBuffer() { - _, h := console.Size() + _, h := termi.Size() y := 0 for i := ed.vrow; i < len(ed.lines); i++ { @@ -25,8 +25,8 @@ func (ed *Editor) drawBuffer() { line = ed.lines[i] } - console.MoveCursor(0, y) - console.Print(line) + termi.MoveCursor(0, y) + termi.Draw(line) y += ed.lineHeight(line) if y >= h-1 { @@ -35,8 +35,8 @@ func (ed *Editor) drawBuffer() { } for ; y < h-1; y++ { - console.MoveCursor(0, y) - console.Print("~") + termi.MoveCursor(0, y) + termi.Draw("~") } } @@ -49,20 +49,20 @@ func (ed *Editor) drawStatus() { m = "i" } - _, h := console.Size() - console.MoveCursor(0, h-1) + _, h := termi.Size() + termi.MoveCursor(0, h-1) if ed.bell { - console.EnableInvert() + termi.EnableInvert() } - console.Printf("%s %d,%d %s", m, ed.row, ed.col, ed.path) + termi.Printf("%s %d,%d %s", m, ed.row, ed.col, ed.path) if ed.bell { - console.DisableInvert() + termi.DisableInvert() } ed.bell = false } func (ed *Editor) updateCursor() { - w, h := console.Size() + w, h := termi.Size() var dy int switch ed.mode { @@ -72,12 +72,12 @@ func (ed *Editor) updateCursor() { ed.col = min(ed.col, max(len-1, 0)) // XXX approximation - width := console.StringWidth(ed.lines[ed.row], ed.col) + width := termi.StringWidth(ed.lines[ed.row], ed.col) ed.x = width % w dy = width / w case modeInsert: // XXX approximation - width := console.StringWidth(ed.head+ed.insert.String(), ed.col) + width := termi.StringWidth(ed.head+ed.insert.String(), ed.col) ed.x = width % w dy = width / w } @@ -104,17 +104,17 @@ func (ed *Editor) updateCursor() { } func (ed *Editor) repaint() { - console.HideCursor() + termi.HideCursor() - console.Clear() - console.HomeCursor() + termi.Clear() + termi.HomeCursor() ed.updateCursor() ed.drawBuffer() ed.drawStatus() - console.MoveCursor(ed.x, ed.y) + termi.MoveCursor(ed.x, ed.y) - console.ShowCursor() + termi.ShowCursor() }