Files
levi/internal/editor/editor.go
2026-03-28 21:56:49 +09:00

170 lines
2.6 KiB
Go

package editor
import (
"os"
"strings"
"unicode/utf8"
"tea.kareha.org/lab/termi"
)
type Mode int
const (
ModeCommand Mode = iota
ModeInsert
ModeSearch
ModePrompt
)
type Editor struct {
col, row int
vrow int
w, h int
x, y int
lines []string
inp *Input
mode Mode
path string
message string
parser *Parser
quit bool
}
func (ed *Editor) Load(path string) {
data, err := os.ReadFile(path)
if err != nil {
panic(err)
}
if len(data) < 1 {
ed.lines = make([]string, 1)
}
// TODO CRLF
if data[len(data)-1] == '\n' {
data = data[:len(data)-1]
}
ed.lines = strings.Split(string(data), "\n")
ed.path = path
}
func Init(args []string) *Editor {
var path string
if len(args) > 1 {
path = args[1]
}
w, h := termi.Size()
ed := &Editor{
col: 0,
row: 0,
vrow: 0,
w: w,
h: h,
x: 0,
y: 0,
lines: make([]string, 1),
inp: NewInput(),
mode: ModeCommand,
path: path,
message: "",
parser: NewParser(),
quit: false,
}
if path != "" {
_, err := os.Stat(path)
if err == nil { // file exists
ed.Load(path)
}
}
termi.Raw()
return ed
}
func (ed *Editor) Save(path string) {
text := strings.Join(ed.lines, "\n") + "\n"
err := os.WriteFile(path, []byte(text), 0644)
if err != nil {
panic(err)
}
ed.path = path
}
func (ed *Editor) Finish() {
termi.Clear()
termi.HomeCursor()
termi.Cooked()
termi.ShowCursor()
if ed.path != "" {
ed.Save(ed.path)
}
}
func (ed *Editor) Line(row int) string {
if ed.mode == ModeInsert && row == ed.row {
return ed.inp.Line()
} else {
return ed.lines[row]
}
}
func (ed *Editor) CurrentLine() string {
return ed.Line(ed.row)
}
func (ed *Editor) RuneCount() int {
return utf8.RuneCountInString(ed.CurrentLine())
}
func (ed *Editor) Confine() {
if ed.mode != ModeCommand {
panic("invalid state")
}
n := len(ed.lines)
if ed.row < 0 {
ed.row = 0
} else if ed.row >= n {
ed.row = max(n-1, 0)
}
rc := ed.RuneCount()
if ed.col < 0 {
ed.col = 0
} else if ed.col >= rc {
ed.col = max(rc-1, 0)
}
}
func (ed *Editor) InsertRune(r rune) {
if ed.mode != ModeInsert {
panic("invalid state")
}
ed.inp.WriteRune(r)
ed.col = ed.inp.Column()
}
func (ed *Editor) EnsureCommand() {
if ed.mode == ModeCommand {
return
}
if ed.mode == ModeInsert {
ed.lines[ed.row] = ed.inp.Line()
ed.inp.Reset()
ed.mode = ModeCommand
ed.MoveLeft(1)
return
}
}
func (ed *Editor) Ring(message string) {
ed.message = message
}
func (ed *Editor) Unimplemented(name string) {
ed.Ring("not implemented (" + name + ")")
}