Refactor Insert
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
71
internal/editor/insert.go
Normal 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()
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
29
internal/editor/runebuf.go
Normal file
29
internal/editor/runebuf.go
Normal 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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user