2021-04-02 15:36:43 -04:00
|
|
|
package document
|
2021-03-30 16:05:33 -04:00
|
|
|
|
2021-04-01 16:17:13 -04:00
|
|
|
import (
|
|
|
|
"strings"
|
2021-04-07 13:10:40 -04:00
|
|
|
"timshome.page/gilo/editor/highlight"
|
2021-04-13 14:43:31 -04:00
|
|
|
"timshome.page/gilo/internal/gilo"
|
|
|
|
"timshome.page/gilo/key"
|
2021-04-07 12:32:22 -04:00
|
|
|
"unicode"
|
2021-04-01 16:17:13 -04:00
|
|
|
)
|
2021-03-31 14:32:43 -04:00
|
|
|
|
2021-04-02 14:52:44 -04:00
|
|
|
type Row struct {
|
2023-10-03 17:02:22 -04:00
|
|
|
parent *Document
|
2021-04-01 16:47:32 -04:00
|
|
|
chars []rune
|
2021-03-31 14:32:43 -04:00
|
|
|
render []rune
|
2021-04-07 12:32:22 -04:00
|
|
|
Hl []int
|
2021-03-30 16:05:33 -04:00
|
|
|
}
|
|
|
|
|
2023-10-03 17:02:22 -04:00
|
|
|
func newRow(parent *Document, s string) *Row {
|
2021-03-30 18:00:06 -04:00
|
|
|
var chars []rune
|
2021-03-31 14:32:43 -04:00
|
|
|
var render []rune
|
2021-03-30 18:00:06 -04:00
|
|
|
|
|
|
|
for _, ch := range s {
|
|
|
|
chars = append(chars, ch)
|
2021-03-31 14:32:43 -04:00
|
|
|
render = append(render, ch)
|
2021-03-30 18:00:06 -04:00
|
|
|
}
|
|
|
|
|
2023-10-03 17:02:22 -04:00
|
|
|
return &Row{parent, chars, render, []int{}}
|
2021-03-30 16:05:33 -04:00
|
|
|
}
|
|
|
|
|
2021-04-02 14:52:44 -04:00
|
|
|
func (r *Row) Size() int {
|
2021-03-30 16:05:33 -04:00
|
|
|
return len(r.chars)
|
|
|
|
}
|
|
|
|
|
2023-10-03 17:02:22 -04:00
|
|
|
// RenderSize is a convenient equivalent of row->rsize in kilo
|
2021-04-02 14:52:44 -04:00
|
|
|
func (r *Row) RenderSize() int {
|
2021-03-31 14:32:43 -04:00
|
|
|
return len(r.render)
|
|
|
|
}
|
|
|
|
|
2021-04-13 14:43:31 -04:00
|
|
|
func (r *Row) Render(at *gilo.Point) string {
|
2021-04-02 15:36:43 -04:00
|
|
|
return string(r.render[at.X:])
|
|
|
|
}
|
|
|
|
|
2021-04-06 10:59:24 -04:00
|
|
|
func (r *Row) Search(query string) int {
|
|
|
|
return strings.Index(string(r.render), query)
|
|
|
|
}
|
|
|
|
|
2021-04-02 14:52:44 -04:00
|
|
|
func (r *Row) insertRune(ch rune, at int) {
|
2021-04-01 16:17:13 -04:00
|
|
|
// If insertion index is invalid, just
|
|
|
|
// append the rune to the end of the array
|
2021-04-02 14:52:44 -04:00
|
|
|
if at < 0 || at >= r.Size() {
|
2021-04-01 16:17:13 -04:00
|
|
|
r.chars = append(r.chars, ch)
|
|
|
|
r.update()
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var newSlice []rune
|
|
|
|
|
|
|
|
// Split the character array at the insertion point
|
|
|
|
start := r.chars[0:at]
|
2021-04-02 14:52:44 -04:00
|
|
|
end := r.chars[at:r.Size()]
|
2021-04-01 16:17:13 -04:00
|
|
|
|
|
|
|
// Splice it back together
|
|
|
|
newSlice = append(newSlice, start...)
|
|
|
|
newSlice = append(newSlice, ch)
|
|
|
|
newSlice = append(newSlice, end...)
|
|
|
|
|
|
|
|
r.chars = newSlice
|
|
|
|
r.update()
|
|
|
|
}
|
|
|
|
|
2021-04-02 16:14:19 -04:00
|
|
|
func (r *Row) appendString(str string) {
|
|
|
|
for _, ch := range str {
|
|
|
|
r.chars = append(r.chars, ch)
|
|
|
|
}
|
|
|
|
|
|
|
|
r.update()
|
|
|
|
}
|
|
|
|
|
2021-04-02 14:52:44 -04:00
|
|
|
func (r *Row) deleteRune(at int) {
|
|
|
|
if at < 0 || at >= r.Size() {
|
2021-04-02 10:48:51 -04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var newSlice []rune
|
|
|
|
|
|
|
|
// Split the character array at the insertion point
|
|
|
|
start := r.chars[0:at]
|
2021-04-02 14:52:44 -04:00
|
|
|
end := r.chars[at+1 : r.Size()] // Skip the index in question
|
2021-04-02 10:48:51 -04:00
|
|
|
|
|
|
|
// Splice it back together
|
|
|
|
newSlice = append(newSlice, start...)
|
|
|
|
newSlice = append(newSlice, end...)
|
|
|
|
|
|
|
|
r.chars = newSlice
|
|
|
|
r.update()
|
|
|
|
}
|
|
|
|
|
2021-04-02 14:52:44 -04:00
|
|
|
func (r *Row) update() {
|
2021-03-31 14:32:43 -04:00
|
|
|
r.render = r.render[:0]
|
2021-04-13 14:43:31 -04:00
|
|
|
replacement := strings.Repeat(" ", gilo.TabSize)
|
2021-03-31 14:32:43 -04:00
|
|
|
|
|
|
|
str := strings.ReplaceAll(string(r.chars), "\t", replacement)
|
|
|
|
for _, ch := range str {
|
|
|
|
r.render = append(r.render, ch)
|
|
|
|
}
|
2021-04-07 12:32:22 -04:00
|
|
|
|
|
|
|
r.updateSyntax()
|
|
|
|
}
|
|
|
|
|
2023-10-03 17:02:22 -04:00
|
|
|
// updateSyntax is the equivalent of editorUpdateSyntax in kilo
|
2021-04-07 12:32:22 -04:00
|
|
|
func (r *Row) updateSyntax() {
|
2021-04-07 16:26:05 -04:00
|
|
|
i := 0
|
2023-10-03 17:02:22 -04:00
|
|
|
s := r.parent.Syntax
|
2021-04-07 16:33:00 -04:00
|
|
|
r.Hl = make([]int, r.RenderSize())
|
2023-10-03 17:02:22 -04:00
|
|
|
for x := range r.Hl {
|
|
|
|
r.Hl[x] = highlight.Normal
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't bother updating the syntax if there isn't any
|
|
|
|
if s == nil {
|
|
|
|
return
|
|
|
|
}
|
2021-04-07 16:33:00 -04:00
|
|
|
|
2023-10-04 14:22:51 -04:00
|
|
|
prevSep := true
|
|
|
|
inString := '0'
|
2021-04-07 16:26:05 -04:00
|
|
|
for i < r.RenderSize() {
|
|
|
|
ch := r.render[i]
|
2021-04-13 14:43:31 -04:00
|
|
|
prevHl := highlight.Normal
|
|
|
|
|
|
|
|
if i > 0 {
|
2021-04-16 11:46:16 -04:00
|
|
|
prevHl = r.Hl[i-1]
|
2021-04-13 14:43:31 -04:00
|
|
|
}
|
|
|
|
|
2023-10-04 14:22:51 -04:00
|
|
|
ip1 := i + 1
|
|
|
|
|
|
|
|
if s.Flags&highlight.HighlightStrings == highlight.HighlightStrings {
|
|
|
|
// At the start of a string literal
|
|
|
|
if inString == '0' && (ch == '"' || ch == '\'') {
|
|
|
|
inString = ch
|
|
|
|
r.Hl[i] = highlight.String
|
|
|
|
i++
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// In an existing string
|
|
|
|
if inString != '0' {
|
|
|
|
r.Hl[i] = highlight.String
|
|
|
|
|
|
|
|
// Handle when a quote is escaped inside a string
|
|
|
|
if ch == '\\' && ip1 < r.RenderSize() {
|
|
|
|
r.Hl[ip1] = highlight.String
|
|
|
|
i += 2
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// This quote mark matches the beginning of the string
|
|
|
|
// so now the string is completed
|
|
|
|
if ch == inString {
|
|
|
|
inString = '0'
|
|
|
|
}
|
|
|
|
|
|
|
|
i++
|
|
|
|
prevSep = true
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.Flags&highlight.HighlightNumbers == highlight.HighlightNumbers {
|
2023-10-03 17:02:22 -04:00
|
|
|
if (unicode.IsDigit(ch) && (prevSep || prevHl == highlight.Number)) ||
|
|
|
|
(ch == '.' && prevHl == highlight.Number) {
|
|
|
|
r.Hl[i] = highlight.Number
|
|
|
|
i += 1
|
|
|
|
prevSep = false
|
|
|
|
continue
|
|
|
|
}
|
2021-04-07 12:32:22 -04:00
|
|
|
}
|
2021-04-07 16:26:05 -04:00
|
|
|
|
2021-04-13 14:43:31 -04:00
|
|
|
prevSep = key.IsSeparator(ch)
|
2021-04-07 16:26:05 -04:00
|
|
|
i++
|
2021-04-07 12:32:22 -04:00
|
|
|
}
|
2021-03-31 14:32:43 -04:00
|
|
|
}
|
2021-03-30 16:05:33 -04:00
|
|
|
|
2021-04-02 14:52:44 -04:00
|
|
|
func (r *Row) toString() string {
|
2021-04-01 16:17:13 -04:00
|
|
|
return string(r.chars)
|
|
|
|
}
|
|
|
|
|
2023-10-03 17:02:22 -04:00
|
|
|
// CursorXToRenderX is the equivalent of editorRowCxToRx in kilo
|
2021-04-02 15:36:43 -04:00
|
|
|
func (r *Row) CursorXToRenderX(cursorX int) (renderX int) {
|
|
|
|
renderX = 0
|
2021-03-31 14:56:46 -04:00
|
|
|
|
2021-04-07 12:02:08 -04:00
|
|
|
for i := 0; i < cursorX; i++ {
|
2021-03-31 14:56:46 -04:00
|
|
|
if r.chars[i] == '\t' {
|
2021-04-13 14:43:31 -04:00
|
|
|
renderX += (gilo.TabSize - 1) - (renderX % gilo.TabSize)
|
2021-03-31 14:56:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
renderX += 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return renderX
|
2021-04-01 16:17:13 -04:00
|
|
|
}
|
2021-04-06 10:59:24 -04:00
|
|
|
|
2023-10-03 17:02:22 -04:00
|
|
|
// RenderXtoCursorX is the equivalent of editorRowRxToCx in kilo
|
2021-04-06 10:59:24 -04:00
|
|
|
func (r *Row) RenderXtoCursorX(renderX int) (cursorX int) {
|
|
|
|
currentRenderX := 0
|
|
|
|
cursorX = 0
|
|
|
|
|
|
|
|
for cursorX = 0; cursorX < r.Size(); cursorX++ {
|
|
|
|
if r.chars[cursorX] == '\t' {
|
2021-04-13 14:43:31 -04:00
|
|
|
currentRenderX += (gilo.TabSize - 1) - (currentRenderX % gilo.TabSize)
|
2021-04-06 10:59:24 -04:00
|
|
|
} else {
|
|
|
|
currentRenderX += 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if currentRenderX > renderX {
|
|
|
|
return cursorX
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return cursorX
|
2021-04-07 12:32:57 -04:00
|
|
|
}
|