From 2bf960b08540b6d6c0594cc6c2cfe6971381d3db Mon Sep 17 00:00:00 2001 From: EnumDev Date: Thu, 19 Mar 2026 11:53:01 +0200 Subject: [PATCH] Switch buffer contents to string slice --- src/buffer.go | 504 +++++++++++++++++++++++++++++++--------------- src/command.go | 38 ++-- src/line_index.go | 10 +- src/position.go | 24 +++ src/top_menu.go | 19 +- src/window.go | 460 +++++++++++------------------------------- 6 files changed, 514 insertions(+), 541 deletions(-) create mode 100644 src/position.go diff --git a/src/buffer.go b/src/buffer.go index 3fe74d4..677b83a 100644 --- a/src/buffer.go +++ b/src/buffer.go @@ -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: "", } diff --git a/src/command.go b/src/command.go index d300f1b..cd88e9c 100644 --- a/src/command.go +++ b/src/command.go @@ -18,6 +18,20 @@ var commands = make(map[string]*Command) func initCommands() { // Setup commands + selectAll := Command{ + cmd: "select-all", + run: func(window *Window, args ...string) { + // Select entire buffer content + lastLine := window.CurrentBuffer.Contents[len(window.CurrentBuffer.Contents)-1] + window.CurrentBuffer.Selection = &Selection{ + selectionStart: Position{0, 0}, + selectionEnd: Position{len(window.CurrentBuffer.Contents) - 1, len(lastLine) - 1}, + } + + PrintMessage(window, "Selected all text.") + }, + } + cutCmd := Command{ cmd: "cut", run: func(window *Window, args ...string) { @@ -49,7 +63,7 @@ func initCommands() { if copyingMethod == 0 { PrintMessage(window, "Copied line to clipboard.") } else { - PrintMessage(window, "Copied selection to clipboard.") + PrintMessage(window, "Copied selection to clipboard. ") } }, } @@ -59,7 +73,7 @@ func initCommands() { run: func(window *Window, args ...string) { if window.Clipboard != "" { window.CurrentBuffer.PasteText(window, window.Clipboard) - PrintMessage(window, "Pasted text to buffer.") + PrintMessage(window, "Pasted text to buffer. ") } }, } @@ -142,7 +156,6 @@ func initCommands() { log.Fatalf("Could not reload buffer: %s", err) } - window.SetCursorPos(window.CurrentBuffer.CursorPos) PrintMessage(window, "Buffer reloaded.") }, } @@ -158,8 +171,8 @@ func initCommands() { } pos := window.CurrentBuffer.FindSubstring(input, window.CurrentBuffer.CursorPos) - if pos >= 0 { - window.SetCursorPos(pos) + if pos.X >= 0 && pos.Y >= 0 { + window.CurrentBuffer.CursorPos = pos PrintMessage(window, "Match found.") } else { PrintMessage(window, fmt.Sprintf("'%s' not found in buffer!", input)) @@ -177,8 +190,8 @@ func initCommands() { } pos := window.CurrentBuffer.FindSubstring(input, window.CurrentBuffer.CursorPos) - if pos >= 0 { - window.SetCursorPos(pos) + if pos.X >= 0 && pos.Y >= 0 { + window.CurrentBuffer.CursorPos = pos PrintMessage(window, "Match found.") } else { PrintMessage(window, fmt.Sprintf("'%s' not found in buffer!", input)) @@ -199,8 +212,8 @@ func initCommands() { } pos := window.CurrentBuffer.FindAndReplaceSubstring(findStr, replaceStr, window.CurrentBuffer.CursorPos) - if pos >= 0 { - window.SetCursorPos(pos) + if pos.X >= 0 && pos.Y >= 0 { + window.CurrentBuffer.CursorPos = pos PrintMessage(window, "Match replaced successfully.") } else { PrintMessage(window, fmt.Sprintf("'%s' not found in buffer!", findStr)) @@ -220,8 +233,8 @@ func initCommands() { replaceStr := <-inputChannel pos := window.CurrentBuffer.FindAndReplaceSubstring(findStr, replaceStr, window.CurrentBuffer.CursorPos) - if pos >= 0 { - window.SetCursorPos(pos) + if pos.X >= 0 && pos.Y >= 0 { + window.CurrentBuffer.CursorPos = pos PrintMessage(window, "Match replaced successfully.") } else { PrintMessage(window, fmt.Sprintf("'%s' not found in buffer!", findStr)) @@ -243,7 +256,6 @@ func initCommands() { replacements := window.CurrentBuffer.FindAndReplaceAll(findStr, replaceStr) if replacements > 0 { - window.SetCursorPos(window.CurrentBuffer.CursorPos) PrintMessage(window, fmt.Sprintf("Replaced all %d matches successfully.", replacements)) } else { PrintMessage(window, fmt.Sprintf("'%s' not found in buffer!", findStr)) @@ -264,7 +276,6 @@ func initCommands() { replacements := window.CurrentBuffer.FindAndReplaceAll(findStr, replaceStr) if replacements > 0 { - window.SetCursorPos(window.CurrentBuffer.CursorPos) PrintMessage(window, fmt.Sprintf("Replaced all %d matches successfully.", replacements)) } else { PrintMessage(window, fmt.Sprintf("'%s' not found in buffer!", findStr)) @@ -492,6 +503,7 @@ func initCommands() { } // Register commands + commands["select-all"] = &selectAll commands["cut"] = &cutCmd commands["copy"] = ©Cmd commands["paste"] = &pasteCmd diff --git a/src/line_index.go b/src/line_index.go index 33ada80..932dd29 100644 --- a/src/line_index.go +++ b/src/line_index.go @@ -1,9 +1,9 @@ package main import ( - "github.com/gdamore/tcell/v2" "strconv" - "strings" + + "github.com/gdamore/tcell/v2" ) func drawLineIndex(window *Window) { @@ -16,9 +16,9 @@ func drawLineIndex(window *Window) { _, bufferY1, _, bufferY2 := window.GetTextAreaDimensions() - lineIndex := 1 + buffer.OffsetY + lineIndex := 1 + buffer.Offset.Y for y := bufferY1; y <= bufferY2; y++ { - if lineIndex > strings.Count(buffer.Contents, "\n")+1 { + if lineIndex > len(buffer.Contents) { if Config.ExtendLineIndex { for x := 0; x < lineIndexSize; x++ { screen.SetContent(x, y, ' ', nil, lineIndexStyle) @@ -42,7 +42,7 @@ func drawLineIndex(window *Window) { } func getLineIndexSize(window *Window) int { - i := strings.Count(window.CurrentBuffer.Contents, "\n") + 1 + i := len(window.CurrentBuffer.Contents) if i == 0 { return 4 } diff --git a/src/position.go b/src/position.go new file mode 100644 index 0000000..e1e8ac5 --- /dev/null +++ b/src/position.go @@ -0,0 +1,24 @@ +package main + +type Position struct { + X int + Y int +} + +func (position *Position) Equals(x, y int) bool { + return position.X == x && position.Y == y +} + +func ComparePositions(pos1, pos2 Position) int { + if pos1.Y < pos2.Y { + return -1 + } else if pos1.Y > pos2.Y { + return 1 + } else if pos1.X < pos2.X { + return -1 + } else if pos1.X > pos2.X { + return 1 + } else { + return 0 + } +} diff --git a/src/top_menu.go b/src/top_menu.go index ad90eac..3380c4d 100644 --- a/src/top_menu.go +++ b/src/top_menu.go @@ -2,10 +2,11 @@ package main import ( "fmt" - "github.com/gdamore/tcell/v2" "path/filepath" "strconv" "strings" + + "github.com/gdamore/tcell/v2" ) type TopMenuButton struct { @@ -140,22 +141,18 @@ func getBufferInfoMsg(window *Window) string { filename = filepath.Base(window.CurrentBuffer.filename) } - cursorPos := window.CurrentBuffer.CursorPos - cursorX, cursorY := window.GetCursorPos2D() - cursorX++ - cursorY++ - - chars := len(window.CurrentBuffer.Contents) - words := len(strings.Fields(window.CurrentBuffer.Contents)) + contents := window.CurrentBuffer.GetContentsAsString() + chars := len(contents) + words := len(strings.Fields(contents)) ret := Config.BufferInfoMessage ret = strings.ReplaceAll(ret, "\n", " ") ret = strings.ReplaceAll(ret, "%F", pathToFile) ret = strings.ReplaceAll(ret, "%f", filename) - ret = strings.ReplaceAll(ret, "%x", strconv.Itoa(cursorX)) - ret = strings.ReplaceAll(ret, "%y", strconv.Itoa(cursorY)) - ret = strings.ReplaceAll(ret, "%p", strconv.Itoa(cursorPos)) + ret = strings.ReplaceAll(ret, "%x", strconv.Itoa(window.CurrentBuffer.CursorPos.X+1)) + ret = strings.ReplaceAll(ret, "%y", strconv.Itoa(window.CurrentBuffer.CursorPos.Y+1)) + ret = strings.ReplaceAll(ret, "%p", strconv.Itoa(window.CurrentBuffer.PositionToAbsolutePosition(window.CurrentBuffer.CursorPos)+1)) ret = strings.ReplaceAll(ret, "%c", strconv.Itoa(chars)) ret = strings.ReplaceAll(ret, "%w", strconv.Itoa(words)) diff --git a/src/window.go b/src/window.go index fe9c611..7ec5079 100644 --- a/src/window.go +++ b/src/window.go @@ -1,13 +1,14 @@ package main import ( - "github.com/gdamore/tcell/v2" "log" "slices" "strconv" "strings" "time" "unicode" + + "github.com/gdamore/tcell/v2" ) type CursorMode uint8 @@ -98,6 +99,9 @@ func (window *Window) Draw() { // Clear screen window.screen.Clear() + // Sync buffer offset + window.SyncBufferOffset() + // Draw top menu if window.ShowTopMenu { drawTopMenu(window) @@ -161,25 +165,24 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { if ev.Modifiers()&tcell.ModCtrl != 0 { // Move cursor to start of word // Set variable to one character right of current position - endOfWord := pos + 1 - if endOfWord >= len(window.CurrentBuffer.Contents) { - endOfWord = len(window.CurrentBuffer.Contents) - } + window.CurrentBuffer.MoveRight(1) // Skip all spaces - for endOfWord < len(window.CurrentBuffer.Contents) && unicode.IsSpace(rune(window.CurrentBuffer.Contents[endOfWord])) { - endOfWord++ + for unicode.IsSpace(window.CurrentBuffer.GetCharAtPosition(window.CurrentBuffer.CursorPos)) { + if !window.CurrentBuffer.MoveRight(1) { + break + } } // Find end of word - for endOfWord < len(window.CurrentBuffer.Contents) && !unicode.IsSpace(rune(window.CurrentBuffer.Contents[endOfWord])) { - endOfWord++ + for !unicode.IsSpace(window.CurrentBuffer.GetCharAtPosition(window.CurrentBuffer.CursorPos)) { + if !window.CurrentBuffer.MoveRight(1) { + break + } } - - window.SetCursorPos(endOfWord) } else { - // Move cursor one character backwards - window.SetCursorPos(window.CurrentBuffer.CursorPos + 1) + // Move cursor one character forwards + window.CurrentBuffer.MoveRight(1) } // Add to selection @@ -187,7 +190,7 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { if window.CurrentBuffer.Selection == nil { // Cancel cursor movement when creating selection without holding ctrl if ev.Modifiers()&tcell.ModCtrl == 0 { - window.SetCursorPos(pos) + window.CurrentBuffer.CursorPos = pos } window.CurrentBuffer.Selection = &Selection{ @@ -197,10 +200,6 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { } else { window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos } - // Prevent selecting dummy character at the end of the buffer - if window.CurrentBuffer.Selection.selectionEnd >= len(window.CurrentBuffer.Contents) { - window.CurrentBuffer.Selection.selectionEnd = len(window.CurrentBuffer.Contents) - 1 - } } else if window.CurrentBuffer.Selection != nil { // Unset selection window.CurrentBuffer.Selection = nil @@ -214,28 +213,33 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { if ev.Modifiers()&tcell.ModCtrl != 0 { // Move cursor to start of word // Set variable to one character left of current position - startOfWord := pos - 1 - if startOfWord < 0 { - startOfWord = 0 - } + window.CurrentBuffer.MoveLeft(1) // Skip all spaces - for startOfWord >= 0 && len(window.CurrentBuffer.Contents) != 0 && unicode.IsSpace(rune(window.CurrentBuffer.Contents[startOfWord])) { - startOfWord-- + for unicode.IsSpace(window.CurrentBuffer.GetCharAtPosition(window.CurrentBuffer.CursorPos)) { + if !window.CurrentBuffer.MoveLeft(1) { + break + } } - // Find start of word - for startOfWord >= 0 && len(window.CurrentBuffer.Contents) != 0 && !unicode.IsSpace(rune(window.CurrentBuffer.Contents[startOfWord])) { - startOfWord-- + // Find end of word + for { + char := window.CurrentBuffer.GetCharAtPosition(window.CurrentBuffer.CursorPos) + if char == 0 || unicode.IsSpace(char) { + break + } + if !window.CurrentBuffer.MoveLeft(1) { + break + } } - // Move one character to the right - startOfWord++ - - window.SetCursorPos(startOfWord) + // Move one character to the right if not selecting + if window.CurrentBuffer.CursorPos.X != 0 { + window.CurrentBuffer.MoveRight(1) + } } else { // Move cursor one character backwards - window.SetCursorPos(window.CurrentBuffer.CursorPos - 1) + window.CurrentBuffer.MoveLeft(1) } // Add to selection @@ -243,7 +247,7 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { if window.CurrentBuffer.Selection == nil { // Cancel cursor movement when creating selection without holding ctrl if ev.Modifiers()&tcell.ModCtrl == 0 { - window.SetCursorPos(pos) + window.CurrentBuffer.CursorPos = pos } window.CurrentBuffer.Selection = &Selection{ @@ -267,11 +271,11 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { if ev.Modifiers()&tcell.ModCtrl != 0 { // Move cursor to top of buffer - window.SetCursorPos(0) + window.CurrentBuffer.CursorPos.X = 0 + window.CurrentBuffer.CursorPos.Y = 0 } else { // Move cursor one line up - x, y := window.GetCursorPos2D() - window.SetCursorPos2D(x, y-1) + window.CurrentBuffer.MoveUp(1) } // Add to selection @@ -318,11 +322,11 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { if ev.Modifiers()&tcell.ModCtrl != 0 { // Move cursor to bottom of buffer - window.SetCursorPos(len(window.CurrentBuffer.Contents)) + window.CurrentBuffer.CursorPos.Y = len(window.CurrentBuffer.Contents) - 1 + window.CurrentBuffer.CursorPos.X = len(window.CurrentBuffer.Contents[window.CurrentBuffer.CursorPos.Y]) } else { // Move cursor one line down - x, y := window.GetCursorPos2D() - window.SetCursorPos2D(x, y+1) + window.CurrentBuffer.MoveDown(1) } // Add to selection @@ -337,9 +341,9 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { window.CurrentBuffer.Selection.selectionEnd = window.CurrentBuffer.CursorPos } // Prevent selecting dummy character at the end of the buffer - if window.CurrentBuffer.Selection.selectionEnd >= len(window.CurrentBuffer.Contents) { - window.CurrentBuffer.Selection.selectionEnd = len(window.CurrentBuffer.Contents) - 1 - } + //if window.CurrentBuffer.Selection.selectionEnd >= len(window.CurrentBuffer.Contents) { + // window.CurrentBuffer.Selection.selectionEnd = len(window.CurrentBuffer.Contents) - 1 + //} } else if window.CurrentBuffer.Selection != nil { // Unset selection window.CurrentBuffer.Selection = nil @@ -389,25 +393,12 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { } // Typing - if ev.Key() == tcell.KeyBackspace2 { + if ev.Key() == tcell.KeyBackspace || ev.Key() == tcell.KeyBackspace2 { if window.CursorMode == CursorModeBuffer { - str := window.CurrentBuffer.Contents - index := window.CurrentBuffer.CursorPos - if window.CurrentBuffer.Selection != nil { - edge1, edge2 := window.CurrentBuffer.GetSelectionEdges() - if edge2 == len(window.CurrentBuffer.Contents) { - edge2 = len(window.CurrentBuffer.Contents) - 1 - } - - str = str[:edge1] + str[edge2+1:] - window.CurrentBuffer.Contents = str - window.SetCursorPos(edge1) - window.CurrentBuffer.Selection = nil - } else if index != 0 { - str = str[:index-1] + str[index:] - window.CurrentBuffer.Contents = str - window.SetCursorPos(window.CurrentBuffer.CursorPos - 1) + window.CurrentBuffer.CutText(window) + } else { + window.CurrentBuffer.Delete(1) } } else if window.CursorMode == CursorModeInputBar { str := currentInputRequest.input @@ -421,57 +412,21 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { } } else if ev.Key() == tcell.KeyTab { if window.CursorMode == CursorModeBuffer { - str := window.CurrentBuffer.Contents - // Remove selected text if window.CurrentBuffer.Selection != nil { - edge1, edge2 := window.CurrentBuffer.GetSelectionEdges() - if edge2 == len(window.CurrentBuffer.Contents) { - edge2 = len(window.CurrentBuffer.Contents) - 1 - } - - str = str[:edge1] + str[edge2+1:] - window.CurrentBuffer.Contents = str - window.SetCursorPos(edge1) - window.CurrentBuffer.Selection = nil + window.CurrentBuffer.CutText(window) } - index := window.CurrentBuffer.CursorPos - - if index == len(str) { - str += "\t" - } else { - str = str[:index] + "\t" + str[index:] - } - window.CurrentBuffer.Contents = str - window.SetCursorPos(window.CurrentBuffer.CursorPos + 1) + window.CurrentBuffer.WriteRune('\t') } } else if ev.Key() == tcell.KeyEnter { if window.CursorMode == CursorModeBuffer { - str := window.CurrentBuffer.Contents - // Remove selected text if window.CurrentBuffer.Selection != nil { - edge1, edge2 := window.CurrentBuffer.GetSelectionEdges() - if edge2 == len(window.CurrentBuffer.Contents) { - edge2 = len(window.CurrentBuffer.Contents) - 1 - } - - str = str[:edge1] + str[edge2+1:] - window.CurrentBuffer.Contents = str - window.SetCursorPos(edge1) - window.CurrentBuffer.Selection = nil + window.CurrentBuffer.CutText(window) } - index := window.CurrentBuffer.CursorPos - - if index == len(str) { - str += "\n" - } else { - str = str[:index] + "\n" + str[index:] - } - window.CurrentBuffer.Contents = str - window.SetCursorPos(window.CurrentBuffer.CursorPos + 1) + window.CurrentBuffer.WriteRune('\n') } else if window.CursorMode == CursorModeInputBar { if currentInputRequest.input == "" && slices.Index(inputHistory, currentInputRequest.input) == -1 { inputHistory = append(inputHistory, currentInputRequest.input) @@ -485,30 +440,12 @@ func (window *Window) handleKeyInput(ev *tcell.EventKey) { } } else if ev.Key() == tcell.KeyRune { if window.CursorMode == CursorModeBuffer { - str := window.CurrentBuffer.Contents - // Remove selected text if window.CurrentBuffer.Selection != nil { - edge1, edge2 := window.CurrentBuffer.GetSelectionEdges() - if edge2 == len(window.CurrentBuffer.Contents) { - edge2 = len(window.CurrentBuffer.Contents) - 1 - } - - str = str[:edge1] + str[edge2+1:] - window.CurrentBuffer.Contents = str - window.SetCursorPos(edge1) - window.CurrentBuffer.Selection = nil + window.CurrentBuffer.CutText(window) } - index := window.CurrentBuffer.CursorPos - - if index == len(str) { - str += string(ev.Rune()) - } else { - str = str[:index] + string(ev.Rune()) + str[index:] - } - window.CurrentBuffer.Contents = str - window.SetCursorPos(window.CurrentBuffer.CursorPos + 1) + window.CurrentBuffer.WriteRune(ev.Rune()) } else if window.CursorMode == CursorModeInputBar { str := currentInputRequest.input index := currentInputRequest.cursorPos @@ -535,14 +472,44 @@ func (window *Window) handleMouseInput(ev *tcell.EventMouse) { // Ensure click was in buffer area x1, y1, x2, y2 := window.GetTextAreaDimensions() if mouseX >= x1 && mouseY >= y1 && mouseX <= x2 && mouseY <= y2 { - currentX, currentY := window.GetCursorPos2D() - bufferMouseX, bufferMouseY := window.AbsolutePosToCursorPos2D(mouseX, mouseY) + currentPos := window.CurrentBuffer.CursorPos + mouseBufferPos := Position{mouseX + window.CurrentBuffer.Offset.X - x1, mouseY + window.CurrentBuffer.Offset.Y - y1} + + // Keep mouse Y in bounds + if mouseBufferPos.Y >= len(window.CurrentBuffer.Contents) { + mouseBufferPos.Y = len(window.CurrentBuffer.Contents) - 1 + } + + // Offset mouse X for each tab character in line + posInLine := make([]int, 0) + for i, r := range window.CurrentBuffer.Contents[mouseBufferPos.Y] + " " { + if r == '\t' { + for j := 0; j < Config.TabIndentation; j++ { + posInLine = append(posInLine, i) + } + } else { + posInLine = append(posInLine, i) + } + } + if len(posInLine) == 0 { + mouseBufferPos.X = 0 + } else if mouseBufferPos.X >= len(posInLine) { + mouseBufferPos.X = posInLine[len(posInLine)-1] + } else { + mouseBufferPos.X = posInLine[mouseBufferPos.X] + } + + // Keep mouse X in bounds + if mouseBufferPos.X > len(window.CurrentBuffer.Contents[mouseBufferPos.Y]) { + mouseBufferPos.X = len(window.CurrentBuffer.Contents[mouseBufferPos.Y]) + } + if mouseHeld { // Add to selection if window.CurrentBuffer.Selection == nil { window.CurrentBuffer.Selection = &Selection{ selectionStart: window.CurrentBuffer.CursorPos, - selectionEnd: window.CursorPos2DToCursorPos(bufferMouseX, bufferMouseY), + selectionEnd: mouseBufferPos, } // Set last click time @@ -550,22 +517,19 @@ func (window *Window) handleMouseInput(ev *tcell.EventMouse) { return } else { - window.CurrentBuffer.Selection.selectionEnd = window.CursorPos2DToCursorPos(bufferMouseX, bufferMouseY) + window.CurrentBuffer.Selection.selectionEnd = mouseBufferPos } - // Prevent selecting dummy character at the end of the buffer - if window.CurrentBuffer.Selection.selectionEnd >= len(window.CurrentBuffer.Contents) { - window.CurrentBuffer.Selection.selectionEnd = len(window.CurrentBuffer.Contents) - 1 - } - } else if currentX == bufferMouseX && currentY == bufferMouseY && window.CurrentBuffer.CursorPos < len(window.CurrentBuffer.Contents) && time.Since(lastClickTime).Milliseconds() < 300 { + } else if currentPos == mouseBufferPos && time.Since(lastClickTime).Milliseconds() < 300 { selectedText := window.CurrentBuffer.GetSelectedText() if window.CurrentBuffer.Selection == nil || strings.HasSuffix(selectedText, "\n") { // Select word - startOfWord := window.CurrentBuffer.CursorPos - endOfWord := window.CurrentBuffer.CursorPos + cursorPos := window.CurrentBuffer.CursorPos + startOfWord := window.CurrentBuffer.CursorPos.X + endOfWord := window.CurrentBuffer.CursorPos.X // Find end of word - for i := window.CurrentBuffer.CursorPos + 1; i < len(window.CurrentBuffer.Contents); i++ { - currentRune := rune(window.CurrentBuffer.Contents[i]) + for i := cursorPos.X + 1; i < len(window.CurrentBuffer.Contents[cursorPos.Y]); i++ { + currentRune := rune(window.CurrentBuffer.Contents[cursorPos.Y][i]) if unicode.IsLetter(currentRune) || unicode.IsDigit(currentRune) || currentRune == '_' { endOfWord++ } else { @@ -574,8 +538,8 @@ func (window *Window) handleMouseInput(ev *tcell.EventMouse) { } // Find start of word - for i := window.CurrentBuffer.CursorPos - 1; i >= 0; i-- { - currentRune := rune(window.CurrentBuffer.Contents[i]) + for i := cursorPos.X - 1; i >= 0; i-- { + currentRune := rune(window.CurrentBuffer.Contents[cursorPos.Y][i]) if unicode.IsLetter(currentRune) || unicode.IsDigit(currentRune) || currentRune == '_' { startOfWord-- } else { @@ -585,38 +549,17 @@ func (window *Window) handleMouseInput(ev *tcell.EventMouse) { // Add to selection window.CurrentBuffer.Selection = &Selection{ - selectionStart: startOfWord, - selectionEnd: endOfWord, + selectionStart: Position{startOfWord, cursorPos.Y}, + selectionEnd: Position{endOfWord, cursorPos.Y}, } } else { // Select line - startOfLine := window.CurrentBuffer.CursorPos - endOfLine := window.CurrentBuffer.CursorPos - - // Find end of line - for i := window.CurrentBuffer.CursorPos + 1; i < len(window.CurrentBuffer.Contents); i++ { - currentLetter := window.CurrentBuffer.Contents[i] - - endOfLine++ - if currentLetter == '\n' { - break - } - } - - // Find start of line - for i := window.CurrentBuffer.CursorPos - 1; i >= 0; i-- { - currentLetter := window.CurrentBuffer.Contents[i] - if currentLetter != '\n' { - startOfLine-- - } else { - break - } - } + cursorPos := window.CurrentBuffer.CursorPos // Add to selection window.CurrentBuffer.Selection = &Selection{ - selectionStart: startOfLine, - selectionEnd: endOfLine, + selectionStart: Position{0, cursorPos.Y}, + selectionEnd: Position{len(window.CurrentBuffer.Contents[cursorPos.Y]), cursorPos.Y}, } } @@ -631,7 +574,7 @@ func (window *Window) handleMouseInput(ev *tcell.EventMouse) { } } // Move cursor - window.SetCursorPos2D(bufferMouseX, bufferMouseY) + window.CurrentBuffer.CursorPos = mouseBufferPos // Set last click time lastClick = time.Now().UnixMilli() @@ -667,196 +610,19 @@ func (window *Window) GetTextAreaDimensions() (int, int, int, int) { return x1, y1, x2 - 1, y2 - 2 } -func (window *Window) CursorPos2DToCursorPos(x, y int) int { - // Ensure x and y are positive - x = max(x, 0) - y = max(y, 0) - - // Set cursor position to 0 buffer is empty - if len(window.CurrentBuffer.Contents) == 0 { - return 0 - } - - // Create line slice from buffer contents - lines := make([]struct { - charIndex int - str string - }, 0) - - var str string - for i, char := range window.CurrentBuffer.Contents { - str += string(char) - if char == '\n' || i == len(window.CurrentBuffer.Contents)-1 { - lines = append(lines, struct { - charIndex int - str string - }{charIndex: i - len(str) + 1, str: str}) - str = "" - } - } - - // Append extra character or line - if window.CurrentBuffer.Contents[len(window.CurrentBuffer.Contents)-1] == '\n' { - lines = append(lines, struct { - charIndex int - str string - }{charIndex: len(window.CurrentBuffer.Contents), str: " "}) - } else { - lines[len(lines)-1].str += " " - } - - // Limit x and y - y = min(y, len(lines)-1) - x = min(x, len(lines[y].str)-1) - - return lines[y].charIndex + x -} - -func (window *Window) AbsolutePosToCursorPos2D(x, y int) (int, int) { - x1, y1, _, _ := window.GetTextAreaDimensions() - - x -= x1 - y -= y1 - - x += window.CurrentBuffer.OffsetX - y += window.CurrentBuffer.OffsetY - - if x < 0 { - x = 0 - } - if y < 0 { - y = 0 - } - - split := strings.SplitAfter(window.CurrentBuffer.Contents+" ", "\n") - - if y >= len(split) { - y = len(split) - 1 - } - line := split[y] - - posInLine := make([]int, 0) - for i, char := range []rune(line) { - if char == '\t' { - for j := 0; j < Config.TabIndentation; j++ { - posInLine = append(posInLine, i) - } - } else { - posInLine = append(posInLine, i) - } - } - - if len(posInLine) == 0 { - x = 0 - } else if x >= len(posInLine) { - x = posInLine[len(posInLine)-1] - } else { - x = posInLine[x] - } - - return x, y -} - -func (window *Window) GetAbsoluteCursorPos() (int, int) { - cursorX, cursorY := window.GetCursorPos2D() - - x1, y1, _, _ := window.GetTextAreaDimensions() - cursorX += x1 - cursorY += y1 - - return cursorX, cursorY -} - -func (window *Window) GetCursorPos2D() (int, int) { - cursorX := 0 - cursorY := 0 - - for i := 0; i < window.CurrentBuffer.CursorPos; i++ { - char := window.CurrentBuffer.Contents[i] - if char == '\n' { - cursorY++ - cursorX = 0 - } else { - cursorX++ - } - } - - return cursorX, cursorY -} - -func (window *Window) SetCursorPos(position int) { - window.CurrentBuffer.CursorPos = position - - if window.CurrentBuffer.CursorPos < 0 { - window.CurrentBuffer.CursorPos = 0 - } - - if window.CurrentBuffer.CursorPos > len(window.CurrentBuffer.Contents) { - window.CurrentBuffer.CursorPos = len(window.CurrentBuffer.Contents) - } - - window.SyncBufferOffset() -} - -func (window *Window) SetCursorPos2D(x, y int) { - // Ensure x and y are positive - x = max(x, 0) - y = max(y, 0) - - // Set cursor position to 0 buffer is empty - if len(window.CurrentBuffer.Contents) == 0 { - window.SetCursorPos(0) - return - } - - // Create line slice from buffer contents - lines := make([]struct { - charIndex int - str string - }, 0) - - var str string - for i, char := range window.CurrentBuffer.Contents { - str += string(char) - if char == '\n' || i == len(window.CurrentBuffer.Contents)-1 { - lines = append(lines, struct { - charIndex int - str string - }{charIndex: i - len(str) + 1, str: str}) - str = "" - } - } - - // Append extra character or line - if window.CurrentBuffer.Contents[len(window.CurrentBuffer.Contents)-1] == '\n' { - lines = append(lines, struct { - charIndex int - str string - }{charIndex: len(window.CurrentBuffer.Contents), str: " "}) - } else { - lines[len(lines)-1].str += " " - } - - // Limit x and y - y = min(y, len(lines)-1) - x = min(x, len(lines[y].str)-1) - - window.SetCursorPos(lines[y].charIndex + x) -} - func (window *Window) SyncBufferOffset() { - x, y := window.GetCursorPos2D() + cursorPos := window.CurrentBuffer.CursorPos bufferX1, bufferY1, bufferX2, bufferY2 := window.GetTextAreaDimensions() - if y < window.CurrentBuffer.OffsetY { - window.CurrentBuffer.OffsetY = y - } else if y > window.CurrentBuffer.OffsetY+(bufferY2-bufferY1) { - window.CurrentBuffer.OffsetY = y - (bufferY2 - bufferY1) + if cursorPos.Y < window.CurrentBuffer.Offset.Y { + window.CurrentBuffer.Offset.Y = cursorPos.Y + } else if cursorPos.Y > window.CurrentBuffer.Offset.Y+(bufferY2-bufferY1) { + window.CurrentBuffer.Offset.Y = cursorPos.Y - (bufferY2 - bufferY1) } - if x < window.CurrentBuffer.OffsetX { - window.CurrentBuffer.OffsetX = x - } else if x > window.CurrentBuffer.OffsetX+(bufferX2-bufferX1) { - window.CurrentBuffer.OffsetX = x - (bufferX2 - bufferX1) + if cursorPos.X < window.CurrentBuffer.Offset.X { + window.CurrentBuffer.Offset.X = cursorPos.X + } else if cursorPos.X > window.CurrentBuffer.Offset.X+(bufferX2-bufferX1) { + window.CurrentBuffer.Offset.X = cursorPos.X - (bufferX2 - bufferX1) } }