diff --git a/internal/console/input.go b/internal/console/input.go index 053e283..43ef138 100644 --- a/internal/console/input.go +++ b/internal/console/input.go @@ -9,7 +9,7 @@ import ( type Key int const ( - KeyNormal = iota + KeyNormal Key = iota KeyUp KeyDown KeyRight diff --git a/internal/editor/command.go b/internal/editor/command.go index 019627b..47d47b2 100644 --- a/internal/editor/command.go +++ b/internal/editor/command.go @@ -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 diff --git a/internal/editor/editor.go b/internal/editor/editor.go index b4448c0..9786bee 100644 --- a/internal/editor/editor.go +++ b/internal/editor/editor.go @@ -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 - } - } - } -} diff --git a/internal/editor/main.go b/internal/editor/main.go new file mode 100644 index 0000000..0b5078c --- /dev/null +++ b/internal/editor/main.go @@ -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 + } + } + } +} diff --git a/internal/editor/view.go b/internal/editor/view.go new file mode 100644 index 0000000..f6f7e52 --- /dev/null +++ b/internal/editor/view.go @@ -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() +}