Refactor
This commit is contained in:
@@ -9,7 +9,7 @@ import (
|
||||
type Key int
|
||||
|
||||
const (
|
||||
KeyNormal = iota
|
||||
KeyNormal Key = iota
|
||||
KeyUp
|
||||
KeyDown
|
||||
KeyRight
|
||||
|
||||
@@ -8,9 +8,9 @@ func (ed *Editor) enterInsert() {
|
||||
}
|
||||
|
||||
func (ed *Editor) enterInsertAfter() {
|
||||
len := ed.runeCount()
|
||||
if ed.col >= len-1 {
|
||||
ed.col = len
|
||||
rc := ed.runeCount()
|
||||
if ed.col >= rc-1 {
|
||||
ed.col = rc
|
||||
ed.head = ed.lines[ed.row]
|
||||
ed.tail = ""
|
||||
ed.mode = modeInsert
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
type mode int
|
||||
|
||||
const (
|
||||
modeCommand = iota
|
||||
modeCommand mode = iota
|
||||
modeInsert
|
||||
)
|
||||
|
||||
@@ -27,44 +27,63 @@ type Editor struct {
|
||||
path string
|
||||
}
|
||||
|
||||
func (ed *Editor) load() {
|
||||
if ed.path == "" {
|
||||
return
|
||||
}
|
||||
_, err := os.Stat(ed.path)
|
||||
if err != nil { // file not exists
|
||||
return
|
||||
}
|
||||
data, err := ioutil.ReadFile(ed.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")
|
||||
}
|
||||
|
||||
func Init(args []string) *Editor {
|
||||
var path string
|
||||
var lines []string
|
||||
if len(args) > 1 {
|
||||
path = args[1]
|
||||
_, err := os.Stat(path)
|
||||
if err == nil { // file exists
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(data) > 0 {
|
||||
if data[len(data)-1] == '\n' {
|
||||
data = data[:len(data)-1]
|
||||
}
|
||||
lines = strings.Split(string(data), "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(lines) < 1 {
|
||||
lines = make([]string, 1)
|
||||
}
|
||||
|
||||
console.Raw()
|
||||
|
||||
return &Editor{
|
||||
ed := &Editor{
|
||||
col: 0,
|
||||
row: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
vrow: 0,
|
||||
lines: lines,
|
||||
lines: make([]string, 1),
|
||||
head: "",
|
||||
tail: "",
|
||||
insert: new(strings.Builder),
|
||||
mode: modeCommand,
|
||||
path: path,
|
||||
}
|
||||
|
||||
ed.load()
|
||||
|
||||
console.Raw()
|
||||
return ed
|
||||
}
|
||||
|
||||
func (ed *Editor) save() {
|
||||
if ed.path == "" {
|
||||
return
|
||||
}
|
||||
text := strings.Join(ed.lines, "\n") + "\n"
|
||||
err := ioutil.WriteFile(ed.path, []byte(text), 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Editor) Finish() {
|
||||
@@ -73,238 +92,14 @@ func (ed *Editor) Finish() {
|
||||
console.Cooked()
|
||||
console.ShowCursor()
|
||||
|
||||
if ed.path != "" {
|
||||
text := strings.Join(ed.lines, "\n") + "\n"
|
||||
err := ioutil.WriteFile(ed.path, []byte(text), 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
ed.save()
|
||||
}
|
||||
|
||||
func (ed *Editor) runeCount() int {
|
||||
return utf8.RuneCountInString(ed.lines[ed.row])
|
||||
}
|
||||
|
||||
func (ed *Editor) lineHeight(line string) int {
|
||||
w, _ := console.Size()
|
||||
rc := utf8.RuneCountInString(line)
|
||||
width := console.StringWidth(line, rc)
|
||||
return 1 + max(width-1, 0)/w
|
||||
}
|
||||
|
||||
func (ed *Editor) drawBuffer() {
|
||||
_, h := console.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
|
||||
} else {
|
||||
line = ed.lines[i]
|
||||
}
|
||||
|
||||
console.MoveCursor(0, y)
|
||||
console.Print(line)
|
||||
|
||||
y += ed.lineHeight(line)
|
||||
if y >= h-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for ; y < h-1; y++ {
|
||||
console.MoveCursor(0, y)
|
||||
console.Print("~")
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Editor) drawStatus() {
|
||||
_, h := console.Size()
|
||||
|
||||
console.MoveCursor(0, h-1)
|
||||
switch ed.mode {
|
||||
case modeCommand:
|
||||
console.Print("c")
|
||||
case modeInsert:
|
||||
console.Print("i")
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Editor) updateCursor() {
|
||||
w, h := console.Size()
|
||||
|
||||
var dy int
|
||||
switch ed.mode {
|
||||
case modeCommand:
|
||||
ed.row = min(max(ed.row, 0), max(len(ed.lines)-1, 0))
|
||||
len := ed.runeCount()
|
||||
ed.col = min(ed.col, max(len-1, 0))
|
||||
|
||||
// XXX approximation
|
||||
width := console.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)
|
||||
ed.x = width % w
|
||||
dy = width / w
|
||||
}
|
||||
|
||||
if ed.row < ed.vrow {
|
||||
ed.vrow = ed.row
|
||||
}
|
||||
|
||||
y := 0
|
||||
for i := ed.vrow; i < ed.row; i++ {
|
||||
y += ed.lineHeight(ed.lines[i])
|
||||
}
|
||||
ed.y = y + dy
|
||||
|
||||
for ed.y >= h-1 {
|
||||
ed.vrow++
|
||||
|
||||
y := 0
|
||||
for i := ed.vrow; i < ed.row; i++ {
|
||||
y += ed.lineHeight(ed.lines[i])
|
||||
}
|
||||
ed.y = y + dy
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Editor) repaint() {
|
||||
console.HideCursor()
|
||||
|
||||
console.Clear()
|
||||
console.HomeCursor()
|
||||
|
||||
ed.updateCursor()
|
||||
|
||||
ed.drawBuffer()
|
||||
ed.drawStatus()
|
||||
|
||||
console.MoveCursor(ed.x, ed.y)
|
||||
|
||||
console.ShowCursor()
|
||||
}
|
||||
|
||||
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) insertNewline() {
|
||||
before := make([]string, 0, len(ed.lines)+1)
|
||||
before = append(before, ed.lines[:ed.row]...)
|
||||
var after []string
|
||||
if ed.row+1 < len(ed.lines) {
|
||||
after = ed.lines[ed.row+1:]
|
||||
} else {
|
||||
after = []string{}
|
||||
}
|
||||
newLines := []string{
|
||||
ed.head + ed.insert.String(),
|
||||
ed.tail,
|
||||
}
|
||||
ed.lines = append(append(before, newLines...), after...)
|
||||
|
||||
ed.row++
|
||||
|
||||
ed.col = 0
|
||||
ed.head = ""
|
||||
ed.insert.Reset()
|
||||
}
|
||||
|
||||
func (ed *Editor) deleteBefore() {
|
||||
if ed.insert.Len() < 1 {
|
||||
return // TODO ring
|
||||
}
|
||||
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) insertRune(r rune) {
|
||||
ed.insert.WriteRune(r)
|
||||
ed.col++
|
||||
}
|
||||
|
||||
func (ed *Editor) Main() {
|
||||
for {
|
||||
ed.repaint()
|
||||
|
||||
k, r := console.ReadKey()
|
||||
switch ed.mode {
|
||||
case modeCommand:
|
||||
switch k {
|
||||
case console.KeyNormal:
|
||||
switch r {
|
||||
case 'q':
|
||||
return
|
||||
case 'i':
|
||||
ed.enterInsert()
|
||||
case 'a':
|
||||
ed.enterInsertAfter()
|
||||
case 'h':
|
||||
ed.moveLeft(1)
|
||||
case 'l':
|
||||
ed.moveRight(1)
|
||||
case 'j':
|
||||
ed.moveDown(1)
|
||||
case 'k':
|
||||
ed.moveUp(1)
|
||||
case 'x':
|
||||
ed.deleteRune(1)
|
||||
}
|
||||
case console.KeyUp:
|
||||
ed.moveUp(1)
|
||||
case console.KeyDown:
|
||||
ed.moveDown(1)
|
||||
case console.KeyRight:
|
||||
ed.moveRight(1)
|
||||
case console.KeyLeft:
|
||||
ed.moveLeft(1)
|
||||
default:
|
||||
// TODO ring
|
||||
}
|
||||
case modeInsert:
|
||||
switch k {
|
||||
case console.KeyNormal:
|
||||
switch r {
|
||||
case console.RuneEscape:
|
||||
ed.exitInsert()
|
||||
case console.RuneEnter:
|
||||
ed.insertNewline()
|
||||
case console.RuneBackspace:
|
||||
ed.deleteBefore()
|
||||
case console.RuneDelete:
|
||||
ed.deleteBefore()
|
||||
default:
|
||||
ed.insertRune(r)
|
||||
}
|
||||
case console.KeyUp:
|
||||
ed.exitInsert()
|
||||
ed.moveUp(1)
|
||||
case console.KeyDown:
|
||||
ed.exitInsert()
|
||||
ed.moveDown(1)
|
||||
case console.KeyRight:
|
||||
ed.exitInsert()
|
||||
ed.moveRight(1)
|
||||
case console.KeyLeft:
|
||||
ed.exitInsert()
|
||||
ed.moveLeft(1)
|
||||
default:
|
||||
// TODO ring
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
121
internal/editor/main.go
Normal file
121
internal/editor/main.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package editor
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"tea.kareha.org/lab/levi/internal/console"
|
||||
)
|
||||
|
||||
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) insertNewline() {
|
||||
before := make([]string, 0, len(ed.lines)+1)
|
||||
before = append(before, ed.lines[:ed.row]...)
|
||||
var after []string
|
||||
if ed.row+1 < len(ed.lines) {
|
||||
after = ed.lines[ed.row+1:]
|
||||
} else {
|
||||
after = []string{}
|
||||
}
|
||||
newLines := []string{
|
||||
ed.head + ed.insert.String(),
|
||||
ed.tail,
|
||||
}
|
||||
ed.lines = append(append(before, newLines...), after...)
|
||||
|
||||
ed.row++
|
||||
|
||||
ed.col = 0
|
||||
ed.head = ""
|
||||
ed.insert.Reset()
|
||||
}
|
||||
|
||||
func (ed *Editor) deleteBefore() {
|
||||
if ed.insert.Len() < 1 {
|
||||
return // TODO ring
|
||||
}
|
||||
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()
|
||||
|
||||
k, r := console.ReadKey()
|
||||
switch ed.mode {
|
||||
case modeCommand:
|
||||
switch k {
|
||||
case console.KeyNormal:
|
||||
switch r {
|
||||
case 'q':
|
||||
return
|
||||
case 'i':
|
||||
ed.enterInsert()
|
||||
case 'a':
|
||||
ed.enterInsertAfter()
|
||||
case 'h':
|
||||
ed.moveLeft(1)
|
||||
case 'l':
|
||||
ed.moveRight(1)
|
||||
case 'j':
|
||||
ed.moveDown(1)
|
||||
case 'k':
|
||||
ed.moveUp(1)
|
||||
case 'x':
|
||||
ed.deleteRune(1)
|
||||
}
|
||||
case console.KeyUp:
|
||||
ed.moveUp(1)
|
||||
case console.KeyDown:
|
||||
ed.moveDown(1)
|
||||
case console.KeyRight:
|
||||
ed.moveRight(1)
|
||||
case console.KeyLeft:
|
||||
ed.moveLeft(1)
|
||||
default:
|
||||
// TODO ring
|
||||
}
|
||||
case modeInsert:
|
||||
switch k {
|
||||
case console.KeyNormal:
|
||||
switch r {
|
||||
case console.RuneEscape:
|
||||
ed.exitInsert()
|
||||
case console.RuneEnter:
|
||||
ed.insertNewline()
|
||||
case console.RuneBackspace:
|
||||
ed.deleteBefore()
|
||||
case console.RuneDelete:
|
||||
ed.deleteBefore()
|
||||
default:
|
||||
ed.insertRune(r)
|
||||
}
|
||||
case console.KeyUp:
|
||||
ed.exitInsert()
|
||||
ed.moveUp(1)
|
||||
case console.KeyDown:
|
||||
ed.exitInsert()
|
||||
ed.moveDown(1)
|
||||
case console.KeyRight:
|
||||
ed.exitInsert()
|
||||
ed.moveRight(1)
|
||||
case console.KeyLeft:
|
||||
ed.exitInsert()
|
||||
ed.moveLeft(1)
|
||||
default:
|
||||
// TODO ring
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
113
internal/editor/view.go
Normal file
113
internal/editor/view.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package editor
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"tea.kareha.org/lab/levi/internal/console"
|
||||
)
|
||||
|
||||
func (ed *Editor) lineHeight(line string) int {
|
||||
w, _ := console.Size()
|
||||
rc := utf8.RuneCountInString(line)
|
||||
width := console.StringWidth(line, rc)
|
||||
return 1 + max(width-1, 0)/w
|
||||
}
|
||||
|
||||
func (ed *Editor) drawBuffer() {
|
||||
_, h := console.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
|
||||
} else {
|
||||
line = ed.lines[i]
|
||||
}
|
||||
|
||||
console.MoveCursor(0, y)
|
||||
console.Print(line)
|
||||
|
||||
y += ed.lineHeight(line)
|
||||
if y >= h-1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for ; y < h-1; y++ {
|
||||
console.MoveCursor(0, y)
|
||||
console.Print("~")
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Editor) drawStatus() {
|
||||
var m string
|
||||
switch ed.mode {
|
||||
case modeCommand:
|
||||
m = "c"
|
||||
case modeInsert:
|
||||
m = "i"
|
||||
}
|
||||
|
||||
_, h := console.Size()
|
||||
console.MoveCursor(0, h-1)
|
||||
console.Printf("%s %d,%d %s", m, ed.row, ed.col, ed.path)
|
||||
}
|
||||
|
||||
func (ed *Editor) updateCursor() {
|
||||
w, h := console.Size()
|
||||
|
||||
var dy int
|
||||
switch ed.mode {
|
||||
case modeCommand:
|
||||
ed.row = min(max(ed.row, 0), max(len(ed.lines)-1, 0))
|
||||
len := ed.runeCount()
|
||||
ed.col = min(ed.col, max(len-1, 0))
|
||||
|
||||
// XXX approximation
|
||||
width := console.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)
|
||||
ed.x = width % w
|
||||
dy = width / w
|
||||
}
|
||||
|
||||
if ed.row < ed.vrow {
|
||||
ed.vrow = ed.row
|
||||
}
|
||||
|
||||
y := 0
|
||||
for i := ed.vrow; i < ed.row; i++ {
|
||||
y += ed.lineHeight(ed.lines[i])
|
||||
}
|
||||
ed.y = y + dy
|
||||
|
||||
for ed.y >= h-1 {
|
||||
ed.vrow++
|
||||
|
||||
y := 0
|
||||
for i := ed.vrow; i < ed.row; i++ {
|
||||
y += ed.lineHeight(ed.lines[i])
|
||||
}
|
||||
ed.y = y + dy
|
||||
}
|
||||
}
|
||||
|
||||
func (ed *Editor) repaint() {
|
||||
console.HideCursor()
|
||||
|
||||
console.Clear()
|
||||
console.HomeCursor()
|
||||
|
||||
ed.updateCursor()
|
||||
|
||||
ed.drawBuffer()
|
||||
ed.drawStatus()
|
||||
|
||||
console.MoveCursor(ed.x, ed.y)
|
||||
|
||||
console.ShowCursor()
|
||||
}
|
||||
Reference in New Issue
Block a user