More moving stuff around

This commit is contained in:
Timothy Warren 2021-04-02 14:52:44 -04:00
parent 1cc35c95a7
commit c99cf3faf9
18 changed files with 157 additions and 129 deletions

View File

@ -5,25 +5,26 @@ import (
"log" "log"
"os" "os"
"timshome.page/gilo/internal/buffer" "timshome.page/gilo/internal/buffer"
"timshome.page/gilo/internal/gilo"
) )
type document struct { type Document struct {
dirty bool dirty bool
filename string filename string
rows []*row rows []*Row
} }
func newDocument() *document { func NewDocument() *Document {
var rows []*row var rows []*Row
return &document{ return &Document{
false, false,
"", "",
rows, rows,
} }
} }
func (d *document) open(filename string) { func (d *Document) Open(filename string) {
file, err := os.Open(filename) file, err := os.Open(filename)
if err != nil { if err != nil {
log.Fatalf("failed to open file") log.Fatalf("failed to open file")
@ -35,13 +36,13 @@ func (d *document) open(filename string) {
scanner.Split(bufio.ScanLines) scanner.Split(bufio.ScanLines)
for scanner.Scan() { for scanner.Scan() {
d.appendRow(scanner.Text()) d.AppendRow(scanner.Text())
} }
d.dirty = false d.dirty = false
} }
func (d *document) save() int { func (d *Document) Save() int {
if d.filename == "" { if d.filename == "" {
return 0 return 0
} }
@ -52,7 +53,7 @@ func (d *document) save() int {
} }
defer file.Close() defer file.Close()
fileStr := d.toString() fileStr := d.ToString()
fileLen := len(fileStr) fileLen := len(fileStr)
err = file.Truncate(int64(fileLen)) err = file.Truncate(int64(fileLen))
@ -63,7 +64,7 @@ func (d *document) save() int {
size, err := file.WriteString(fileStr) size, err := file.WriteString(fileStr)
if err != nil { if err != nil {
panic("failed to save document to file") panic("failed to save Document to file")
} }
if fileLen == size { if fileLen == size {
@ -75,7 +76,11 @@ func (d *document) save() int {
return 0 return 0
} }
func (d *document) appendRow(s string) { func (d *Document) Row(at int) *Row {
return d.rows[at]
}
func (d *Document) AppendRow(s string) {
newRow := newRow(s) newRow := newRow(s)
newRow.update() newRow.update()
d.rows = append(d.rows, newRow) d.rows = append(d.rows, newRow)
@ -83,7 +88,7 @@ func (d *document) appendRow(s string) {
d.dirty = true d.dirty = true
} }
func (d *document) toString() string { func (d *Document) ToString() string {
buf := buffer.NewBuffer() buf := buffer.NewBuffer()
for _, row := range d.rows { for _, row := range d.rows {
@ -94,6 +99,18 @@ func (d *document) toString() string {
return buf.ToString() return buf.ToString()
} }
func (d *document) rowCount() int { func (d *Document) RowCount() int {
return len(d.rows) return len(d.rows)
} }
func (d *Document) InsertChar(at *gilo.Point, ch rune) {
d.rows[at.Y].insertRune(ch, at.X)
}
func (d *Document) DelChar(at *gilo.Point) {
d.rows[at.Y].deleteRune(at.X)
}
func (d *Document) IsDirty() bool {
return d.dirty
}

View File

@ -3,30 +3,30 @@ package editor
import "testing" import "testing"
func TestNewDocument(t *testing.T) { func TestNewDocument(t *testing.T) {
d := newDocument() d := NewDocument()
if d == nil { if d == nil {
t.Errorf("Failed to create document") t.Errorf("Failed to create Document")
} }
} }
func TestAppendRow(t *testing.T) { func TestAppendRow(t *testing.T) {
d := newDocument() d := NewDocument()
d.appendRow("Test Row") d.AppendRow("Test Row")
got := len(d.rows) got := len(d.rows)
want := 1 want := 1
if got != want { if got != want {
t.Errorf("Failed to add a row to the document") t.Errorf("Failed to add a row to the Document")
} }
} }
func TestRowCount(t *testing.T) { func TestRowCount(t *testing.T) {
d := newDocument() d := NewDocument()
d.appendRow("Test Row") d.AppendRow("Test Row")
got := d.rowCount() got := d.RowCount()
want := 1 want := 1
if got != want { if got != want {

View File

@ -6,7 +6,7 @@ import (
"strings" "strings"
"time" "time"
"timshome.page/gilo/internal/buffer" "timshome.page/gilo/internal/buffer"
"timshome.page/gilo/terminal" "timshome.page/gilo/internal/terminal"
) )
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -25,7 +25,7 @@ func (e *editor) RefreshScreen() {
e.drawStatusBar(ab) e.drawStatusBar(ab)
e.drawMessageBar(ab) e.drawMessageBar(ab)
ab.Append(terminal.MoveCursor(e.renderX-e.offset.x, e.cursor.y-e.offset.y)) ab.Append(terminal.MoveCursor(e.renderX-e.offset.X, e.cursor.Y-e.offset.Y))
ab.Append(terminal.ShowCursor) ab.Append(terminal.ShowCursor)
@ -35,46 +35,46 @@ func (e *editor) RefreshScreen() {
func (e *editor) scroll() { func (e *editor) scroll() {
e.renderX = 0 e.renderX = 0
if e.cursor.y < e.document.rowCount() { if e.cursor.Y < e.document.RowCount() {
e.renderX = e.document.rows[e.cursor.y].cursorXToRenderX(e.cursor.x) e.renderX = e.document.Row(e.cursor.Y).cursorXToRenderX(e.cursor.X)
} }
if e.cursor.y < e.offset.y { if e.cursor.Y < e.offset.Y {
e.offset.y = e.cursor.y e.offset.Y = e.cursor.Y
} }
if e.cursor.y >= e.offset.y+e.screen.Rows { if e.cursor.Y >= e.offset.Y+e.screen.Rows {
e.offset.y = e.cursor.y - e.screen.Rows + 1 e.offset.Y = e.cursor.Y - e.screen.Rows + 1
} }
if e.renderX < e.offset.x { if e.renderX < e.offset.X {
e.offset.x = e.renderX e.offset.X = e.renderX
} }
if e.renderX >= e.offset.x+e.screen.Cols { if e.renderX >= e.offset.X+e.screen.Cols {
e.offset.x = e.renderX - e.screen.Cols e.offset.X = e.renderX - e.screen.Cols
} }
} }
func (e *editor) drawRows(ab *buffer.Buffer) { func (e *editor) drawRows(ab *buffer.Buffer) {
for y := 0; y < e.screen.Rows; y++ { for y := 0; y < e.screen.Rows; y++ {
fileRow := y + e.offset.y fileRow := y + e.offset.Y
if fileRow >= e.document.rowCount() { if fileRow >= e.document.RowCount() {
e.drawPlaceholderRow(y, ab) e.drawPlaceholderRow(y, ab)
} else { } else {
rawRow := e.document.rows[fileRow] rawRow := e.document.Row(fileRow)
// If the column offset is greater than the length of the row, // If the column offset is greater than the length of the row,
// just display an empty row // just display an empty row
if e.offset.x > rawRow.size() { if e.offset.X > rawRow.Size() {
ab.Append("") ab.Append("")
continue continue
} }
rowLen := e.document.rows[fileRow].rSize() - e.offset.x rowLen := e.document.Row(fileRow).RenderSize() - e.offset.X
outputRow := truncate(string(e.document.rows[fileRow].render[e.offset.x:]), rowLen) outputRow := truncate(string(e.document.Row(fileRow).render[e.offset.X:]), rowLen)
ab.Append(outputRow) ab.Append(outputRow)
} }
@ -83,7 +83,7 @@ func (e *editor) drawRows(ab *buffer.Buffer) {
} }
func (e *editor) drawPlaceholderRow(y int, ab *buffer.Buffer) { func (e *editor) drawPlaceholderRow(y int, ab *buffer.Buffer) {
if e.document.rowCount() == 0 && y == e.screen.Rows/3 { if e.document.RowCount() == 0 && y == e.screen.Rows/3 {
welcome := fmt.Sprintf("Gilo editor -- version %s", KiloVersion) welcome := fmt.Sprintf("Gilo editor -- version %s", KiloVersion)
if len(welcome) > e.screen.Cols { if len(welcome) > e.screen.Cols {
welcome = truncate(welcome, e.screen.Cols) welcome = truncate(welcome, e.screen.Cols)
@ -120,7 +120,7 @@ func (e *editor) drawStatusBar(ab *buffer.Buffer) {
modified = "(modified)" modified = "(modified)"
} }
leftStatus := fmt.Sprintf("%.20s - %d lines %s", fileName, e.document.rowCount(), modified) leftStatus := fmt.Sprintf("%.20s - %d lines %s", fileName, e.document.RowCount(), modified)
length := len(leftStatus) length := len(leftStatus)
if length > cols { if length > cols {
leftStatus = truncate(leftStatus, cols) leftStatus = truncate(leftStatus, cols)
@ -131,7 +131,7 @@ func (e *editor) drawStatusBar(ab *buffer.Buffer) {
return return
} }
rightStatus := fmt.Sprintf("%d/%d", e.cursor.y+1, e.document.rowCount()) rightStatus := fmt.Sprintf("%d/%d", e.cursor.Y+1, e.document.RowCount())
rlength := len(rightStatus) rlength := len(rightStatus)
statusLength := length + rlength statusLength := length + rlength

View File

@ -4,18 +4,14 @@ package editor
import ( import (
"fmt" "fmt"
"time" "time"
"timshome.page/gilo/terminal" "timshome.page/gilo/internal/gilo"
"timshome.page/gilo/internal/terminal"
) )
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// !Editor // !Editor
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
type point struct {
x int
y int
}
type statusMsg struct { type statusMsg struct {
message string message string
created time.Time created time.Time
@ -23,9 +19,9 @@ type statusMsg struct {
type editor struct { type editor struct {
screen *terminal.Screen screen *terminal.Screen
cursor *point cursor *gilo.Point
offset *point offset *gilo.Point
document *document document *Document
status *statusMsg status *statusMsg
quitTimes uint8 quitTimes uint8
renderX int renderX int
@ -37,9 +33,9 @@ func New() *editor {
// Subtract rows for status bar and message bar/prompt // Subtract rows for status bar and message bar/prompt
screen.Rows -= 2 screen.Rows -= 2
cursor := &point{0, 0} cursor := gilo.DefaultPoint()
offset := &point{0, 0} offset := gilo.DefaultPoint()
document := newDocument() document := NewDocument()
status := &statusMsg{ status := &statusMsg{
"", "",
time.Now(), time.Now(),
@ -57,11 +53,11 @@ func New() *editor {
} }
func (e *editor) Open(filename string) { func (e *editor) Open(filename string) {
e.document.open(filename) e.document.Open(filename)
} }
func (e *editor) Save() { func (e *editor) Save() {
size := e.document.save() size := e.document.Save()
if size > 0 { if size > 0 {
e.SetStatusMessage("%d bytes written to disk", size) e.SetStatusMessage("%d bytes written to disk", size)
@ -84,23 +80,23 @@ func (e *editor) ProcessKeypress() bool {
} }
func (e *editor) insertChar(ch rune) { func (e *editor) insertChar(ch rune) {
if e.cursor.y == e.document.rowCount() { if e.cursor.Y == e.document.RowCount() {
e.document.appendRow("") e.document.AppendRow("")
} }
e.document.rows[e.cursor.y].insertRune(ch, e.cursor.x) e.document.Row(e.cursor.Y).insertRune(ch, e.cursor.X)
e.cursor.x += 1 e.cursor.X += 1
e.document.dirty = true e.document.dirty = true
} }
func (e *editor) delChar() { func (e *editor) delChar() {
if e.cursor.y == e.document.rowCount() { if e.cursor.Y == e.document.RowCount() {
return return
} }
if e.cursor.x > 0 { if e.cursor.X > 0 {
e.document.rows[e.cursor.y].deleteRune(e.cursor.x - 1) e.document.Row(e.cursor.Y).deleteRune(e.cursor.X - 1)
e.cursor.x -= 1 e.cursor.X -= 1
} }
e.document.dirty = true e.document.dirty = true

View File

@ -14,12 +14,12 @@ func TestInsertChar(t *testing.T) {
e := New() e := New()
e.insertChar('q') e.insertChar('q')
if e.document.rowCount() != 1 { if e.document.RowCount() != 1 {
t.Errorf("A row was not created when the character was inserted") t.Errorf("A row was not created when the character was inserted")
} }
row := e.document.rows[0] row := e.document.Row(0)
if row.size() != 1 { if row.Size() != 1 {
t.Errorf("Failed to add character to row. Row: %v", row) t.Errorf("Failed to add character to row. Row: %v", row)
} }
} }

View File

@ -1,8 +1,8 @@
package editor package editor
import ( import (
"timshome.page/gilo/key" "timshome.page/gilo/internal/key"
"timshome.page/gilo/terminal" "timshome.page/gilo/internal/terminal"
) )
/** /**
@ -81,71 +81,71 @@ func (e *editor) processKeypressStr(key string) {
} }
func (e *editor) moveCursor(key string) { func (e *editor) moveCursor(key string) {
var row *row var row *Row
if e.cursor.y >= e.document.rowCount() { if e.cursor.Y >= e.document.RowCount() {
row = nil row = nil
} else { } else {
row = e.document.rows[e.cursor.y] row = e.document.Row(e.cursor.Y)
} }
switch key { switch key {
case keyLeft: case keyLeft:
if e.cursor.x != 0 { if e.cursor.X != 0 {
e.cursor.x -= 1 e.cursor.X -= 1
} }
// Move from beginning of current row to end of previous row // Move from beginning of current row to end of previous row
if e.cursor.y > 0 { if e.cursor.Y > 0 {
e.cursor.y -= 1 e.cursor.Y -= 1
e.cursor.x = e.document.rows[e.cursor.y].size() e.cursor.X = e.document.Row(e.cursor.Y).Size()
} }
case keyRight: case keyRight:
if row != nil && e.cursor.x < row.size() { if row != nil && e.cursor.X < row.Size() {
e.cursor.x += 1 e.cursor.X += 1
} }
// Move from end of current line to beginning of next line // Move from end of current line to beginning of next line
if row != nil && e.cursor.x == row.size() && e.cursor.y < e.document.rowCount()-1 { if row != nil && e.cursor.X == row.Size() && e.cursor.Y < e.document.RowCount()-1 {
e.cursor.y += 1 e.cursor.Y += 1
e.cursor.x = 0 e.cursor.X = 0
} }
case keyUp: case keyUp:
if e.cursor.y != 0 { if e.cursor.Y != 0 {
e.cursor.y -= 1 e.cursor.Y -= 1
} }
case keyDown: case keyDown:
if e.cursor.y < e.document.rowCount() { if e.cursor.Y < e.document.RowCount() {
e.cursor.y += 1 e.cursor.Y += 1
} }
case keyPageUp: case keyPageUp:
if e.cursor.y > e.screen.Rows { if e.cursor.Y > e.screen.Rows {
e.cursor.y -= e.screen.Rows e.cursor.Y -= e.screen.Rows
} else { } else {
e.cursor.y = 0 e.cursor.Y = 0
} }
case keyPageDown: case keyPageDown:
if e.cursor.y+e.screen.Rows > e.document.rowCount() { if e.cursor.Y+e.screen.Rows > e.document.RowCount() {
e.cursor.y += e.screen.Rows e.cursor.Y += e.screen.Rows
} else { } else {
e.cursor.y = e.document.rowCount() - 1 e.cursor.Y = e.document.RowCount() - 1
} }
case keyHome: case keyHome:
e.cursor.x = 0 e.cursor.X = 0
case keyEnd: case keyEnd:
if row != nil { if row != nil {
e.cursor.x = row.size() e.cursor.X = row.Size()
} }
} }
if e.cursor.y < e.document.rowCount() { if e.cursor.Y < e.document.RowCount() {
row = e.document.rows[e.cursor.y] row = e.document.Row(e.cursor.Y)
rowLen := row.size() rowLen := row.Size()
// Snap to the end of a shorter line from a longer one // Snap to the end of a shorter line from a longer one
if e.cursor.x > rowLen { if e.cursor.X > rowLen {
e.cursor.x = rowLen e.cursor.X = rowLen
} }
} }
} }

View File

@ -2,23 +2,24 @@ package editor
import ( import (
"testing" "testing"
"timshome.page/gilo/key" "timshome.page/gilo/internal/gilo"
"timshome.page/gilo/internal/key"
) )
type moveCursor struct { type moveCursor struct {
keys []string keys []string
withFile bool withFile bool
cursor *point cursor *gilo.Point
} }
var cursorTests = []moveCursor{ var cursorTests = []moveCursor{
{[]string{string(rune(key.Esc))}, false, &point{0, 0}}, {[]string{string(rune(key.Esc))}, false, gilo.DefaultPoint()},
{[]string{keyRight}, true, &point{1, 0}}, {[]string{keyRight}, true, gilo.NewPoint(1, 0)},
{[]string{keyDown, keyEnd}, true, &point{14, 1}}, {[]string{keyDown, keyEnd}, true, gilo.NewPoint(14, 1)},
{[]string{keyEnd, keyHome}, true, &point{0, 0}}, {[]string{keyEnd, keyHome}, true, gilo.DefaultPoint()},
{[]string{keyRight, keyLeft}, true, &point{0, 0}}, {[]string{keyRight, keyLeft}, true, gilo.DefaultPoint()},
{[]string{keyDown, keyUp}, true, &point{0, 0}}, {[]string{keyDown, keyUp}, true, gilo.DefaultPoint()},
// {[]string{keyPageUp}, true, &point{0, 0}}, // {[]string{keyPageUp}, true, gilo.DefaultPoint()},
} }
func TestMoveCursor(t *testing.T) { func TestMoveCursor(t *testing.T) {
@ -35,14 +36,14 @@ func TestMoveCursor(t *testing.T) {
want := test.cursor want := test.cursor
got := e.cursor got := e.cursor
if got.x != want.x || got.y != want.y { if got.X != want.X || got.Y != want.Y {
t.Errorf("Output %v not equal to expected %v for input %q", got, want, test.keys) t.Errorf("Output %v not equal to expected %v for input %q", got, want, test.keys)
} }
} }
} }
type seqTest struct { type seqTest struct {
input string input string
expected string expected string
} }

View File

@ -4,12 +4,12 @@ import (
"strings" "strings"
) )
type row struct { type Row struct {
chars []rune chars []rune
render []rune render []rune
} }
func newRow(s string) *row { func newRow(s string) *Row {
var chars []rune var chars []rune
var render []rune var render []rune
@ -18,21 +18,21 @@ func newRow(s string) *row {
render = append(render, ch) render = append(render, ch)
} }
return &row{chars, render} return &Row{chars, render}
} }
func (r *row) size() int { func (r *Row) Size() int {
return len(r.chars) return len(r.chars)
} }
func (r *row) rSize() int { func (r *Row) RenderSize() int {
return len(r.render) return len(r.render)
} }
func (r *row) insertRune(ch rune, at int) { func (r *Row) insertRune(ch rune, at int) {
// If insertion index is invalid, just // If insertion index is invalid, just
// append the rune to the end of the array // append the rune to the end of the array
if at < 0 || at >= r.size() { if at < 0 || at >= r.Size() {
r.chars = append(r.chars, ch) r.chars = append(r.chars, ch)
r.update() r.update()
@ -43,7 +43,7 @@ func (r *row) insertRune(ch rune, at int) {
// Split the character array at the insertion point // Split the character array at the insertion point
start := r.chars[0:at] start := r.chars[0:at]
end := r.chars[at:r.size()] end := r.chars[at:r.Size()]
// Splice it back together // Splice it back together
newSlice = append(newSlice, start...) newSlice = append(newSlice, start...)
@ -54,8 +54,8 @@ func (r *row) insertRune(ch rune, at int) {
r.update() r.update()
} }
func (r *row) deleteRune(at int) { func (r *Row) deleteRune(at int) {
if at < 0 || at >= r.size() { if at < 0 || at >= r.Size() {
return return
} }
@ -63,7 +63,7 @@ func (r *row) deleteRune(at int) {
// Split the character array at the insertion point // Split the character array at the insertion point
start := r.chars[0:at] start := r.chars[0:at]
end := r.chars[at+1 : r.size()] // Skip the index in question end := r.chars[at+1 : r.Size()] // Skip the index in question
// Splice it back together // Splice it back together
newSlice = append(newSlice, start...) newSlice = append(newSlice, start...)
@ -73,7 +73,7 @@ func (r *row) deleteRune(at int) {
r.update() r.update()
} }
func (r *row) update() { func (r *Row) update() {
r.render = r.render[:0] r.render = r.render[:0]
replacement := strings.Repeat(" ", KiloTabStop) replacement := strings.Repeat(" ", KiloTabStop)
@ -83,11 +83,11 @@ func (r *row) update() {
} }
} }
func (r *row) toString() string { func (r *Row) toString() string {
return string(r.chars) return string(r.chars)
} }
func (r *row) cursorXToRenderX(cursorX int) int { func (r *Row) cursorXToRenderX(cursorX int) int {
renderX := 0 renderX := 0
i := 0 i := 0

View File

@ -18,7 +18,7 @@ func TestRowSize(t *testing.T) {
str := "abcdefg" str := "abcdefg"
row := newRow(str) row := newRow(str)
got := row.size() got := row.Size()
want := 7 want := 7
if got != want { if got != want {
@ -31,7 +31,7 @@ func TestRenderSize(t *testing.T) {
row := newRow(str) row := newRow(str)
row.update() row.update()
got := row.rSize() got := row.RenderSize()
want := 11 want := 11
if got != want { if got != want {
@ -58,8 +58,8 @@ func TestInsertRune(t *testing.T) {
row := newRow(test.initial) row := newRow(test.initial)
row.insertRune(test.ch, test.at) row.insertRune(test.ch, test.at)
if row.size() != 5 { if row.Size() != 5 {
t.Errorf("Row size after inserting rune at index [%d] is %d, should be %d", test.at, row.size(), 5) t.Errorf("Row size after inserting rune at index [%d] is %d, should be %d", test.at, row.Size(), 5)
} }
got := row.toString() got := row.toString()

View File

@ -5,7 +5,7 @@ import (
"golang.org/x/term" "golang.org/x/term"
"os" "os"
"timshome.page/gilo/editor" "timshome.page/gilo/editor"
"timshome.page/gilo/terminal" "timshome.page/gilo/internal/terminal"
) )
func cleanup(oldState *term.State) { func cleanup(oldState *term.State) {

14
internal/gilo/shared.go Normal file
View File

@ -0,0 +1,14 @@
package gilo
type Point struct {
X int
Y int
}
func DefaultPoint() *Point {
return &Point{0, 0}
}
func NewPoint(x, y int) *Point {
return &Point{x, y}
}