Extract termi
This commit is contained in:
3
Makefile
3
Makefile
@@ -1,5 +1,8 @@
|
||||
all: build
|
||||
|
||||
env:
|
||||
go env -w GOPRIVATE=tea.kareha.org
|
||||
|
||||
build:
|
||||
go build -o levi ./cmd/levi
|
||||
|
||||
|
||||
5
go.mod
5
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
|
||||
|
||||
2
go.sum
2
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=
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user