Switch buffer contents to string slice

This commit is contained in:
2026-03-19 11:53:01 +02:00
parent 1b5ce6b7f8
commit 2bf960b085
6 changed files with 514 additions and 541 deletions
+339 -165
View File
@@ -2,18 +2,20 @@ package main
import (
"fmt"
"github.com/gdamore/tcell/v2"
"os"
"path/filepath"
"slices"
"strings"
"github.com/gdamore/tcell/v2"
)
type Buffer struct {
Name string
Contents string
Contents []string
CursorPos int
OffsetX, OffsetY int
CursorPos Position
Offset Position
Selection *Selection
@@ -22,8 +24,7 @@ type Buffer struct {
}
type Selection struct {
selectionStart int
selectionEnd int
selectionStart, selectionEnd Position
}
var Buffers = make([]*Buffer, 0)
@@ -53,43 +54,50 @@ func drawBuffer(window *Window) {
bufferX, bufferY, _, _ := window.GetTextAreaDimensions()
for i, r := range buffer.Contents + " " {
if x-buffer.OffsetX >= bufferX && y-buffer.OffsetY >= bufferY {
// Default style
style := tcell.StyleDefault.Background(CurrentStyle.BufferAreaBg).Foreground(CurrentStyle.BufferAreaFg)
for lineIndex, line := range buffer.Contents {
for runeIndex, r := range line + " " {
drawPosition := Position{runeIndex, lineIndex}
// Change background if under cursor
if i == buffer.CursorPos {
style = style.Background(CurrentStyle.BufferAreaSel)
}
if x-buffer.Offset.X >= bufferX && y-buffer.Offset.Y >= bufferY {
// Default style
style := tcell.StyleDefault.Background(CurrentStyle.BufferAreaBg).Foreground(CurrentStyle.BufferAreaFg)
// Change background if selected
if buffer.Selection != nil {
if edge1, edge2 := buffer.GetSelectionEdges(); i >= edge1 && i <= edge2 {
// Change background if under cursor
if buffer.CursorPos.Equals(runeIndex, lineIndex) {
style = style.Background(CurrentStyle.BufferAreaSel)
}
// Show selection on entire tab space
if r == '\t' {
for j := 0; j < int(Config.TabIndentation); j++ {
window.screen.SetContent(x+j-buffer.OffsetX, y-buffer.OffsetY, r, nil, style)
// Change background if selected
if buffer.Selection != nil {
edge1, edge2 := buffer.GetSelectionEdges()
if ComparePositions(drawPosition, edge1) >= 0 && ComparePositions(drawPosition, edge2) <= 0 {
style = style.Background(CurrentStyle.BufferAreaSel)
// Show selection on entire tab space
if r == '\t' {
for j := 0; j < int(Config.TabIndentation); j++ {
window.screen.SetContent(x+j-buffer.Offset.X, y-buffer.Offset.Y, r, nil, style)
}
}
}
}
window.screen.SetContent(x-buffer.Offset.X, y-buffer.Offset.Y, r, nil, style)
}
window.screen.SetContent(x-buffer.OffsetX, y-buffer.OffsetY, r, nil, style)
}
// Change position for next character
if r == '\n' {
x = bufferX
y++
} else if r == '\t' {
x += int(Config.TabIndentation)
} else {
x++
// Change position for next character
if r == '\t' {
x += int(Config.TabIndentation)
} else {
x++
}
}
// Draw new line
x = bufferX
y++
}
}
func (buffer *Buffer) Load() error {
@@ -113,7 +121,7 @@ func (buffer *Buffer) Load() error {
return err
}
buffer.Contents = string(content)
buffer.Contents = strings.Split(string(content), "\n")
return nil
}
@@ -133,12 +141,13 @@ func (buffer *Buffer) Save() error {
buffer.filename = filepath.Join(homedir, buffer.filename[2:])
}
// Append new line character at end of buffer contents if not present
if buffer.Contents == "" || buffer.Contents[len(buffer.Contents)-1] != '\n' {
buffer.Contents += "\n"
// Add newline at the end of buffer Contents
line := buffer.Contents[len(buffer.Contents)-1]
if len(line) != 0 {
buffer.Contents = append(buffer.Contents, "")
}
err := os.WriteFile(buffer.filename, []byte(buffer.Contents), 0644)
err := os.WriteFile(buffer.filename, []byte(buffer.GetContentsAsString()), 0644)
if err != nil {
return err
}
@@ -146,12 +155,60 @@ func (buffer *Buffer) Save() error {
return nil
}
func (buffer *Buffer) GetSelectionEdges() (int, int) {
if buffer.Selection == nil {
return -1, -1
func (buffer *Buffer) GetContentsAsString() string {
finalText := strings.Builder{}
for i, line := range buffer.Contents {
for _, rune := range line {
finalText.WriteRune(rune)
}
if i != len(buffer.Contents)-1 {
finalText.WriteRune('\n')
}
}
if buffer.Selection.selectionStart < buffer.Selection.selectionEnd {
return finalText.String()
}
func (buffer *Buffer) PositionToAbsolutePosition(position Position) int {
i := 0
for lineIndex, line := range buffer.Contents {
if len(line) == 0 {
line += " "
}
for runeIndex, _ := range line + " " {
if position.Equals(runeIndex, lineIndex) {
return i
}
i++
}
}
return i
}
func (buffer *Buffer) AbsolutePositionToPosition(absolutePosition int) Position {
i := 0
for lineIndex, line := range buffer.Contents {
for runeIndex, _ := range line + " " {
if i == absolutePosition {
return Position{runeIndex, lineIndex}
}
i++
}
}
lastLine := buffer.Contents[len(buffer.Contents)-1]
return Position{len(buffer.Contents) - 1, len(lastLine) - 1}
}
func (buffer *Buffer) GetSelectionEdges() (Position, Position) {
if buffer.Selection == nil {
return Position{-1, -1}, Position{-1, -1}
}
if ComparePositions(buffer.Selection.selectionStart, buffer.Selection.selectionEnd) == -1 {
return buffer.Selection.selectionStart, buffer.Selection.selectionEnd
} else {
return buffer.Selection.selectionEnd, buffer.Selection.selectionStart
@@ -162,113 +219,75 @@ func (buffer *Buffer) GetSelectedText() string {
if buffer.Selection == nil {
return ""
}
if len(buffer.Contents) == 0 {
return ""
}
start := buffer.Selection.selectionStart
end := buffer.Selection.selectionEnd
edge1, edge2 := buffer.GetSelectionEdges()
if start >= len(buffer.Contents) {
start = len(buffer.Contents) - 1
}
if end >= len(buffer.Contents) {
end = len(buffer.Contents) - 1
selectedText := strings.Builder{}
if r := buffer.GetCharAtPosition(edge1); r != 0 {
selectedText.WriteRune(r)
}
if start <= end {
return buffer.Contents[start : end+1]
} else {
return buffer.Contents[end : start+1]
for ComparePositions(edge1, edge2) < 0 {
edge1.X++
if edge1.X > len(buffer.Contents[edge1.Y]) {
if edge1.Y < len(buffer.Contents) {
edge1.Y++
edge1.X = 0
} else {
edge1.X = len(buffer.Contents[edge1.Y])
break
}
}
if r := buffer.GetCharAtPosition(edge1); r != 0 {
selectedText.WriteRune(buffer.GetCharAtPosition(edge1))
}
}
return selectedText.String()
}
func (buffer *Buffer) CutText(window *Window) (string, int) {
if buffer.Selection == nil {
// Copy line
copiedText := ""
startOfLine := window.CurrentBuffer.CursorPos
endOfLine := window.CurrentBuffer.CursorPos
// Add current letter to copied text
if buffer.CursorPos < len(buffer.Contents) {
copiedText = string(buffer.Contents[buffer.CursorPos])
}
// Find end of line
for i := buffer.CursorPos + 1; i < len(buffer.Contents); i++ {
currentLetter := buffer.Contents[i]
endOfLine++
copiedText += string(currentLetter)
if currentLetter == '\n' {
break
}
}
// Find start of line
for i := buffer.CursorPos - 1; i >= 0; i-- {
currentLetter := buffer.Contents[i]
if currentLetter != '\n' {
startOfLine--
copiedText = string(currentLetter) + copiedText
} else {
break
}
}
// Cut current line
cutText := buffer.Contents[buffer.CursorPos.Y] + "\n"
// Remove line from buffer contents
buffer.Contents = buffer.Contents[:startOfLine] + buffer.Contents[endOfLine+1:]
return copiedText, 0
} else {
// Copy selection
copiedText := buffer.GetSelectedText()
// Remove selected text
edge1, edge2 := buffer.GetSelectionEdges()
if edge2 == len(buffer.Contents) {
edge2 = len(buffer.Contents) - 1
if len(buffer.Contents) == 1 {
buffer.Contents[0] = ""
} else {
buffer.Contents = slices.Delete(buffer.Contents, buffer.CursorPos.Y, buffer.CursorPos.Y+1)
}
buffer.Contents = buffer.Contents[:edge1] + buffer.Contents[edge2+1:]
window.SetCursorPos(edge1)
buffer.CursorPos.Y -= 1
if buffer.CursorPos.Y < 0 {
buffer.CursorPos = Position{0, 0}
}
return cutText, 0
} else {
// Cut selection
cutText := buffer.GetSelectedText()
// Remove selected text
_, edge2 := buffer.GetSelectionEdges()
buffer.CursorPos = edge2
buffer.Delete(len(cutText))
// Remove selection
buffer.Selection = nil
return copiedText, 1
return cutText, 1
}
}
func (buffer *Buffer) CopyText() (string, int) {
if buffer.Selection == nil {
// Copy line
copiedText := ""
// Add current letter to copied text
if buffer.CursorPos < len(buffer.Contents) {
copiedText = string(buffer.Contents[buffer.CursorPos])
}
// Find end of line
for i := buffer.CursorPos + 1; i < len(buffer.Contents); i++ {
currentLetter := buffer.Contents[i]
copiedText += string(currentLetter)
if currentLetter == '\n' {
break
}
}
// Find start of line
for i := buffer.CursorPos - 1; i >= 0; i-- {
currentLetter := buffer.Contents[i]
if currentLetter != '\n' {
copiedText = string(currentLetter) + copiedText
} else {
break
}
}
// Cut current line
copiedText := buffer.Contents[buffer.CursorPos.Y] + "\n"
return copiedText, 0
} else {
@@ -278,77 +297,232 @@ func (buffer *Buffer) CopyText() (string, int) {
}
func (buffer *Buffer) PasteText(window *Window, text string) {
str := buffer.Contents
contents := buffer.GetContentsAsString()
// Remove selected text
if buffer.Selection != nil {
edge1, edge2 := buffer.GetSelectionEdges()
if edge2 == len(buffer.Contents) {
edge2 = len(buffer.Contents) - 1
absEdge1 := buffer.PositionToAbsolutePosition(edge1)
absEdge2 := buffer.PositionToAbsolutePosition(edge2)
if absEdge2 == len(buffer.Contents) {
absEdge2 = len(buffer.Contents) - 1
}
str = str[:edge1] + str[edge2+1:]
buffer.Contents = str
window.SetCursorPos(edge1)
contents = contents[:absEdge1] + contents[absEdge2+1:]
buffer.Contents = strings.Split(contents, "\n")
buffer.CursorPos = buffer.AbsolutePositionToPosition(absEdge1)
buffer.Selection = nil
}
index := buffer.CursorPos
if index == len(str) {
str += text
} else {
str = str[:index] + text + str[index:]
}
buffer.Contents = str
window.SetCursorPos(buffer.CursorPos + len(text))
buffer.WriteString(text)
}
func (buffer *Buffer) FindSubstring(substring string, afterPos int) int {
func (buffer *Buffer) FindSubstring(substring string, afterPos Position) Position {
// Return no match if afterPos is larger than the buffer contents size
if afterPos >= len(buffer.Contents) {
return -1
contents := buffer.GetContentsAsString()
absAfterPos := buffer.PositionToAbsolutePosition(afterPos)
if absAfterPos >= len(contents) {
return Position{-1, -1}
}
index := strings.Index(buffer.Contents[afterPos+1:], substring)
index := strings.Index(contents[absAfterPos+1:], substring)
if index != -1 {
index += afterPos + 1
index += absAfterPos + 1
}
return index
return buffer.AbsolutePositionToPosition(index)
}
func (buffer *Buffer) FindAndReplaceSubstring(substring, replacement string, afterPos int) int {
index := buffer.FindSubstring(substring, afterPos)
func (buffer *Buffer) FindAndReplaceSubstring(substring, replacement string, afterPos Position) Position {
// Return no match if afterPos is larger than the buffer contents size
contents := buffer.GetContentsAsString()
absAfterPos := buffer.PositionToAbsolutePosition(afterPos)
// Return if substring isn't found
if index == -1 {
return -1
if absAfterPos >= len(contents) {
return Position{-1, -1}
}
index := strings.Index(contents[absAfterPos+1:], substring)
if index != -1 {
index += absAfterPos + 1
}
// Replace substring with replacement string
buffer.Contents = buffer.Contents[:index] + replacement + buffer.Contents[index+len(substring):]
contents = contents[:index] + replacement + contents[index+len(substring):]
return index
buffer.Contents = strings.Split(contents, "\n")
return buffer.AbsolutePositionToPosition(index)
}
func (buffer *Buffer) FindAndReplaceAll(substring, replacement string) int {
replacements := 0
index := 0
for index != -1 {
index = buffer.FindAndReplaceSubstring(substring, replacement, index)
if index != -1 {
position := Position{}
for position.X != -1 && position.Y != -1 {
position = buffer.FindAndReplaceSubstring(substring, replacement, position)
if position.X != -1 && position.Y != -1 {
replacements++
}
if index == 0 {
index++
}
}
return replacements
}
func (buffer *Buffer) MoveUp(i int) bool {
buffer.CursorPos.Y -= i
if buffer.CursorPos.Y < 0 {
buffer.CursorPos.Y = 0
return false
}
if buffer.CursorPos.X >= len(buffer.Contents[buffer.CursorPos.Y]) {
buffer.CursorPos.X = len(buffer.Contents[buffer.CursorPos.Y])
}
return true
}
func (buffer *Buffer) MoveDown(i int) bool {
buffer.CursorPos.Y += i
if buffer.CursorPos.Y >= len(buffer.Contents) {
buffer.CursorPos.Y = len(buffer.Contents) - 1
return false
}
if buffer.CursorPos.X >= len(buffer.Contents[buffer.CursorPos.Y]) {
buffer.CursorPos.X = len(buffer.Contents[buffer.CursorPos.Y])
}
return true
}
func (buffer *Buffer) MoveLeft(i int) bool {
remainingSteps := i
for remainingSteps > 0 {
buffer.CursorPos.X--
if buffer.CursorPos.X < 0 {
if buffer.CursorPos.Y > 0 {
buffer.CursorPos.Y--
buffer.CursorPos.X = len(buffer.Contents[buffer.CursorPos.Y])
} else {
buffer.CursorPos.X = 0
return false
}
}
remainingSteps--
}
return true
}
func (buffer *Buffer) MoveRight(i int) bool {
remainingSteps := i
for remainingSteps > 0 {
buffer.CursorPos.X++
if buffer.CursorPos.X > len(buffer.Contents[buffer.CursorPos.Y]) {
if buffer.CursorPos.Y < len(buffer.Contents)-1 {
buffer.CursorPos.Y++
buffer.CursorPos.X = 0
} else {
buffer.CursorPos.X = len(buffer.Contents[buffer.CursorPos.Y])
return false
}
}
remainingSteps--
}
return true
}
func (buffer *Buffer) WriteRune(r rune) {
if r == '\n' {
if buffer.CursorPos.Y == len(buffer.Contents) {
buffer.Contents = append(buffer.Contents, "")
} else {
buffer.Contents = slices.Insert(buffer.Contents, buffer.CursorPos.Y+1, "")
}
// Move line content after cursor X to the new line
line := buffer.Contents[buffer.CursorPos.Y]
buffer.Contents[buffer.CursorPos.Y+1] = line[buffer.CursorPos.X:] + buffer.Contents[buffer.CursorPos.Y+1]
buffer.Contents[buffer.CursorPos.Y] = line[:buffer.CursorPos.X]
buffer.MoveDown(1)
buffer.CursorPos.X = 0
} else {
line := buffer.Contents[buffer.CursorPos.Y]
buffer.Contents[buffer.CursorPos.Y] = line[:buffer.CursorPos.X] + string(r) + line[buffer.CursorPos.X:]
buffer.MoveRight(1)
}
}
func (buffer *Buffer) WriteString(str string) {
for _, r := range str {
buffer.WriteRune(r)
}
}
func (buffer *Buffer) Delete(i int) bool {
remainingSteps := i
for remainingSteps > 0 {
buffer.CursorPos.X--
if buffer.CursorPos.X < 0 {
if buffer.CursorPos.Y > 0 {
// Save deleted line text
deletedLine := buffer.Contents[buffer.CursorPos.Y]
buffer.CursorPos.Y--
// Delete line
buffer.Contents = slices.Delete(buffer.Contents, buffer.CursorPos.Y+1, buffer.CursorPos.Y+2)
// Append deleted line text to end of current line
buffer.Contents[buffer.CursorPos.Y] += deletedLine
buffer.CursorPos.X = len(buffer.Contents[buffer.CursorPos.Y]) - len(deletedLine)
} else {
buffer.CursorPos.X = 0
return false
}
} else {
line := buffer.Contents[buffer.CursorPos.Y]
buffer.Contents[buffer.CursorPos.Y] = line[:buffer.CursorPos.X] + line[buffer.CursorPos.X+1:]
}
remainingSteps--
}
return true
}
func (buffer *Buffer) GetCharAtPosition(position Position) rune {
if position.Y < 0 || position.Y >= len(buffer.Contents) {
return 0
}
line := buffer.Contents[position.Y]
if position.X == len(line) {
// Do not return newline for last line if it's empty
if position.Y == len(buffer.Contents)-1 && len(line) == 0 {
return 0
}
return '\n'
} else if position.X < 0 || position.X > len(line) {
return 0
}
return rune(line[position.X])
}
func GetOpenFileBuffer(filename string) *Buffer {
// Replace tilde with home directory
if filename != "~" && strings.HasPrefix(filename, "~/") {
@@ -415,8 +589,8 @@ func CreateFileBuffer(filename string, openNonExistentFile bool) (*Buffer, error
buffer := Buffer{
Name: filename,
Contents: "",
CursorPos: 0,
Contents: make([]string, 1),
CursorPos: Position{0, 0},
canSave: true,
filename: abs,
}
@@ -438,8 +612,8 @@ func CreateFileBuffer(filename string, openNonExistentFile bool) (*Buffer, error
func CreateBuffer(bufferName string) (*Buffer, error) {
buffer := Buffer{
Name: bufferName,
Contents: "",
CursorPos: 0,
Contents: make([]string, 1),
CursorPos: Position{0, 0},
canSave: true,
filename: "",
}