Refactor Insert

This commit is contained in:
2026-03-27 10:34:17 +09:00
parent 3fb8660720
commit b6066ed3ec
6 changed files with 233 additions and 129 deletions

View File

@@ -1,45 +1,67 @@
package editor
func (ed *Editor) enterInsert() {
rs := []rune(ed.lines[ed.row])
ed.head = string(rs[:ed.col])
ed.tail = string(rs[ed.col:])
ed.mode = modeInsert
// key: i
func (ed *Editor) Insert() {
if ed.mode == ModeInsert {
panic("invalid state")
}
ed.ins.Enter(ed.lines[ed.row], ed.col)
ed.mode = ModeInsert
}
func (ed *Editor) enterInsertAfter() {
rc := ed.runeCount()
// key: a
func (ed *Editor) InsertAfter() {
if ed.mode == ModeInsert {
panic("invalid state")
}
rc := ed.RuneCount()
if ed.col >= rc-1 {
ed.col = rc
ed.head = ed.lines[ed.row]
ed.tail = ""
ed.mode = modeInsert
return
} else {
ed.MoveRight(1)
}
ed.moveRight(1)
ed.enterInsert()
ed.Insert()
}
func (ed *Editor) moveLeft(n int) {
// key: h
func (ed *Editor) MoveLeft(n int) {
if ed.mode != ModeCommand {
panic("invalid state")
}
ed.col = max(ed.col-n, 0)
}
func (ed *Editor) moveRight(n int) {
ed.col = min(ed.col+n, max(ed.runeCount()-1, 0))
// key: l
func (ed *Editor) MoveRight(n int) {
if ed.mode != ModeCommand {
panic("invalid state")
}
ed.col = min(ed.col+n, max(ed.RuneCount()-1, 0))
}
func (ed *Editor) moveDown(n int) {
// key: j
func (ed *Editor) MoveDown(n int) {
if ed.mode != ModeCommand {
panic("invalid state")
}
ed.row = min(ed.row+n, max(len(ed.lines)-1, 0))
}
func (ed *Editor) moveUp(n int) {
// key: k
func (ed *Editor) MoveUp(n int) {
if ed.mode != ModeCommand {
panic("invalid state")
}
ed.row = max(ed.row-n, 0)
}
func (ed *Editor) deleteRune(n int) {
// key: x
func (ed *Editor) DeleteRune(n int) {
if ed.mode != ModeCommand {
panic("invalid state")
}
if len(ed.lines[ed.row]) < 1 {
ed.ring()
ed.Ring()
return
}
rs := []rune(ed.lines[ed.row])
@@ -50,7 +72,7 @@ func (ed *Editor) deleteRune(n int) {
tail := string(rs[ed.col+1:])
ed.lines[ed.row] = head + tail
}
rc := ed.runeCount()
rc := ed.RuneCount()
if ed.col >= rc {
ed.col = max(rc-1, 0)
}

View File

@@ -8,26 +8,25 @@ import (
"tea.kareha.org/lab/termi"
)
type mode int
type Mode int
const (
modeCommand mode = iota
modeInsert
ModeCommand Mode = iota
ModeInsert
)
type Editor struct {
col, row int
x, y int
vrow int
lines []string
head, tail string
insert *strings.Builder
mode mode
path string
bell bool
col, row int
x, y int
vrow int
lines []string
ins *Insert
mode Mode
path string
bell bool
}
func (ed *Editor) load() {
func (ed *Editor) Load() {
if ed.path == "" {
return
}
@@ -56,27 +55,25 @@ func Init(args []string) *Editor {
}
ed := &Editor{
col: 0,
row: 0,
x: 0,
y: 0,
vrow: 0,
lines: make([]string, 1),
head: "",
tail: "",
insert: new(strings.Builder),
mode: modeCommand,
path: path,
bell: false,
col: 0,
row: 0,
x: 0,
y: 0,
vrow: 0,
lines: make([]string, 1),
ins: NewInsert(),
mode: ModeCommand,
path: path,
bell: false,
}
ed.load()
ed.Load()
termi.Raw()
return ed
}
func (ed *Editor) save() {
func (ed *Editor) Save() {
if ed.path == "" {
return
}
@@ -93,18 +90,18 @@ func (ed *Editor) Finish() {
termi.Cooked()
termi.ShowCursor()
ed.save()
ed.Save()
}
func (ed *Editor) runeCount() int {
func (ed *Editor) RuneCount() int {
return utf8.RuneCountInString(ed.lines[ed.row])
}
func (ed *Editor) insertRune(r rune) {
ed.insert.WriteRune(r)
func (ed *Editor) InsertRune(r rune) {
ed.ins.Write(r)
ed.col++
}
func (ed *Editor) ring() {
func (ed *Editor) Ring() {
ed.bell = true
}

71
internal/editor/insert.go Normal file
View File

@@ -0,0 +1,71 @@
package editor
import (
"unicode/utf8"
"tea.kareha.org/lab/termi"
)
type Insert struct {
head, tail string
body *RuneBuf
}
const maxBodyLen = 1024
func NewInsert() *Insert {
return &Insert{
head: "",
tail: "",
body: new(RuneBuf),
}
}
func (ins *Insert) Reset() {
ins.head = ""
ins.tail = ""
if ins.body.Len() > maxBodyLen {
ins.body = new(RuneBuf)
} else {
ins.body.Reset()
}
}
func (ins *Insert) Write(r rune) {
ins.body.WriteRune(r)
}
func (ins *Insert) Enter(line string, col int) {
rs := []rune(line)
ins.head = string(rs[:col])
if col < len(rs) {
ins.tail = string(rs[col:])
} else {
ins.tail = ""
}
}
func (ins *Insert) Line() string {
return ins.head + ins.body.String() + ins.tail
}
func (ins *Insert) Newline() []string {
lines := []string{
ins.head + ins.body.String(),
ins.tail,
}
ins.head = ""
ins.body.Reset()
// tail is intentionally preserved
return lines
}
func (ins *Insert) Width() int {
s := ins.head + ins.body.String()
rc := utf8.RuneCountInString(s)
return termi.StringWidth(s, rc)
}
func (ins *Insert) Backspace() bool {
return ins.body.Backspace()
}

View File

@@ -1,20 +1,17 @@
package editor
import (
"unicode/utf8"
"tea.kareha.org/lab/termi"
)
func (ed *Editor) exitInsert() {
ed.lines[ed.row] = ed.head + ed.insert.String() + ed.tail
ed.tail = ""
ed.insert.Reset()
ed.mode = modeCommand
ed.moveLeft(1)
func (ed *Editor) ExitInsert() {
ed.lines[ed.row] = ed.ins.Line()
ed.ins.Reset()
ed.mode = ModeCommand
ed.MoveLeft(1)
}
func (ed *Editor) insertNewline() {
func (ed *Editor) InsertNewline() {
before := make([]string, 0, len(ed.lines)+1)
before = append(before, ed.lines[:ed.row]...)
var after []string
@@ -23,101 +20,89 @@ func (ed *Editor) insertNewline() {
} else {
after = []string{}
}
newLines := []string{
ed.head + ed.insert.String(),
ed.tail,
}
ed.lines = append(append(before, newLines...), after...)
lines := ed.ins.Newline()
ed.lines = append(append(before, lines...), after...)
ed.row++
ed.col = 0
ed.head = ""
ed.insert.Reset()
}
func (ed *Editor) deleteBefore() {
if ed.insert.Len() < 1 {
ed.ring()
func (ed *Editor) DeleteBefore() {
if !ed.ins.Backspace() {
ed.Ring()
return
}
insert := ed.insert.String()
_, size := utf8.DecodeLastRuneInString(insert)
insert = insert[:len(insert)-size]
ed.insert.Reset()
ed.insert.WriteString(insert)
ed.col--
}
func (ed *Editor) Main() {
for {
ed.repaint()
ed.Repaint()
key := termi.ReadKey()
switch ed.mode {
case modeCommand:
case ModeCommand:
switch key.Kind {
case termi.KeyRune:
switch key.Rune {
case 'q':
return
case 'i':
ed.enterInsert()
ed.Insert()
case 'a':
ed.enterInsertAfter()
ed.InsertAfter()
case 'h':
ed.moveLeft(1)
ed.MoveLeft(1)
case 'l':
ed.moveRight(1)
ed.MoveRight(1)
case 'j':
ed.moveDown(1)
ed.MoveDown(1)
case 'k':
ed.moveUp(1)
ed.MoveUp(1)
case 'x':
ed.deleteRune(1)
ed.DeleteRune(1)
default:
ed.ring()
ed.Ring()
}
case termi.KeyUp:
ed.moveUp(1)
ed.MoveUp(1)
case termi.KeyDown:
ed.moveDown(1)
ed.MoveDown(1)
case termi.KeyRight:
ed.moveRight(1)
ed.MoveRight(1)
case termi.KeyLeft:
ed.moveLeft(1)
ed.MoveLeft(1)
default:
ed.ring()
ed.Ring()
}
case modeInsert:
case ModeInsert:
switch key.Kind {
case termi.KeyRune:
switch key.Rune {
case termi.RuneEscape:
ed.exitInsert()
ed.ExitInsert()
case termi.RuneEnter:
ed.insertNewline()
ed.InsertNewline()
case termi.RuneBackspace:
ed.deleteBefore()
ed.DeleteBefore()
case termi.RuneDelete:
ed.deleteBefore()
ed.DeleteBefore()
default:
ed.insertRune(key.Rune)
ed.InsertRune(key.Rune)
}
case termi.KeyUp:
ed.exitInsert()
ed.moveUp(1)
ed.ExitInsert()
ed.MoveUp(1)
case termi.KeyDown:
ed.exitInsert()
ed.moveDown(1)
ed.ExitInsert()
ed.MoveDown(1)
case termi.KeyRight:
ed.exitInsert()
ed.moveRight(1)
ed.ExitInsert()
ed.MoveRight(1)
case termi.KeyLeft:
ed.exitInsert()
ed.moveLeft(1)
ed.ExitInsert()
ed.MoveLeft(1)
default:
ed.ring()
ed.Ring()
}
}
}

View File

@@ -0,0 +1,29 @@
package editor
type RuneBuf struct {
buf []rune
}
func (b *RuneBuf) WriteRune(r rune) {
b.buf = append(b.buf, r)
}
func (b *RuneBuf) Backspace() bool {
if len(b.buf) == 0 {
return false
}
b.buf = b.buf[:len(b.buf)-1]
return true
}
func (b *RuneBuf) String() string {
return string(b.buf)
}
func (b *RuneBuf) Reset() {
b.buf = b.buf[:0]
}
func (b *RuneBuf) Len() int {
return len(b.buf)
}

View File

@@ -6,21 +6,21 @@ import (
"tea.kareha.org/lab/termi"
)
func (ed *Editor) lineHeight(line string) int {
func (ed *Editor) LineHeight(line string) int {
w, _ := termi.Size()
rc := utf8.RuneCountInString(line)
width := termi.StringWidth(line, rc)
return 1 + max(width-1, 0)/w
}
func (ed *Editor) drawBuffer() {
func (ed *Editor) DrawBuffer() {
_, h := termi.Size()
y := 0
for i := ed.vrow; i < len(ed.lines); i++ {
var line string
if ed.mode == modeInsert && i == ed.row {
line = ed.head + ed.insert.String() + ed.tail
if ed.mode == ModeInsert && i == ed.row {
line = ed.ins.Line()
} else {
line = ed.lines[i]
}
@@ -28,7 +28,7 @@ func (ed *Editor) drawBuffer() {
termi.MoveCursor(0, y)
termi.Draw(line)
y += ed.lineHeight(line)
y += ed.LineHeight(line)
if y >= h-1 {
break
}
@@ -40,12 +40,12 @@ func (ed *Editor) drawBuffer() {
}
}
func (ed *Editor) drawStatus() {
func (ed *Editor) DrawStatus() {
var m string
switch ed.mode {
case modeCommand:
case ModeCommand:
m = "c"
case modeInsert:
case ModeInsert:
m = "i"
}
@@ -61,23 +61,23 @@ func (ed *Editor) drawStatus() {
ed.bell = false
}
func (ed *Editor) updateCursor() {
func (ed *Editor) UpdateCursor() {
w, h := termi.Size()
var dy int
switch ed.mode {
case modeCommand:
case ModeCommand:
ed.row = min(max(ed.row, 0), max(len(ed.lines)-1, 0))
len := ed.runeCount()
len := ed.RuneCount()
ed.col = min(ed.col, max(len-1, 0))
// XXX approximation
width := termi.StringWidth(ed.lines[ed.row], ed.col)
ed.x = width % w
dy = width / w
case modeInsert:
case ModeInsert:
// XXX approximation
width := termi.StringWidth(ed.head+ed.insert.String(), ed.col)
width := ed.ins.Width()
ed.x = width % w
dy = width / w
}
@@ -88,7 +88,7 @@ func (ed *Editor) updateCursor() {
y := 0
for i := ed.vrow; i < ed.row; i++ {
y += ed.lineHeight(ed.lines[i])
y += ed.LineHeight(ed.lines[i])
}
ed.y = y + dy
@@ -97,22 +97,22 @@ func (ed *Editor) updateCursor() {
y := 0
for i := ed.vrow; i < ed.row; i++ {
y += ed.lineHeight(ed.lines[i])
y += ed.LineHeight(ed.lines[i])
}
ed.y = y + dy
}
}
func (ed *Editor) repaint() {
func (ed *Editor) Repaint() {
termi.HideCursor()
termi.Clear()
termi.HomeCursor()
ed.updateCursor()
ed.UpdateCursor()
ed.drawBuffer()
ed.drawStatus()
ed.DrawBuffer()
ed.DrawStatus()
termi.MoveCursor(ed.x, ed.y)