1package main
2
3// Data structures and methods for managing a file buffer, its content (lines of
4// runes), and multiple cursors.
5
6import (
7 "strings"
8 "time"
9)
10
11// Cursor represents a position in the buffer.
12type Cursor struct {
13 X int // Column index (0-based).
14 Y int // Row index (0-based).
15 PreferredCol int // Remembers the intended column when moving up/down.
16}
17
18// HistoryState stores a snapshot of the buffer and cursors for undo/redo.
19type HistoryState struct {
20 buffer [][]rune
21 cursors []Cursor
22}
23
24// Buffer represents an open file and its associated editor state.
25type Buffer struct {
26 buffer [][]rune // Slice of lines, where each line is a slice of runes.
27 cursors []Cursor // Support for multiple cursors.
28 scrollX int // Horizontal scroll offset.
29 scrollY int // Vertical scroll offset.
30 filename string // Path to the file on disk.
31 modified bool // True if changes haven't been saved.
32 readOnly bool // True if the buffer cannot be edited.
33 undoStack []HistoryState // For undo functionality.
34 redoStack []HistoryState // For redo functionality.
35 fileType *FileType // Language-specific settings.
36 lspClient *LSPClient // Associated LSP client for this buffer.
37 diagnostics []Diagnostic // Errors/warnings for this buffer.
38 syntax *SyntaxHighlighter // Syntax highlighting engine.
39 lastModTime time.Time // Last modified time of the file on disk.
40}
41
42// PrimaryCursor returns the first cursor in the list.
43func (b *Buffer) PrimaryCursor() *Cursor {
44 if len(b.cursors) == 0 {
45 b.cursors = append(b.cursors, Cursor{X: 0, Y: 0})
46 }
47 // The first cursor is usually the main one used for most operations.
48 return &b.cursors[0]
49}
50
51// AddCursor adds a new cursor at the specified position.
52func (b *Buffer) AddCursor(x, y int) {
53 b.cursors = append(b.cursors, Cursor{X: x, Y: y, PreferredCol: x})
54}
55
56// ClearCursors removes all cursors except the primary one.
57func (b *Buffer) ClearCursors() {
58 if len(b.cursors) > 1 {
59 primary := b.cursors[0]
60 b.cursors = []Cursor{primary}
61 }
62}
63
64// getLineByteOffset calculates the byte index for a given column in a line of
65// runes.
66func (b *Buffer) getLineByteOffset(line []rune, col int) uint32 {
67 // Rune indices != Byte indices for multi-byte characters (UTF-8).
68 if col > len(line) {
69 col = len(line)
70 }
71 return uint32(len(string(line[:col])))
72}
73
74// getByteOffset calculates the total byte offset from the start of the buffer
75// to (row, col).
76func (b *Buffer) getByteOffset(row, col int) uint32 {
77 var offset uint32
78 for i := 0; i < row && i < len(b.buffer); i++ {
79 offset += uint32(len(string(b.buffer[i]))) + 1 // +1 for the newline character.
80 }
81
82 if row < len(b.buffer) {
83 offset += b.getLineByteOffset(b.buffer[row], col)
84 }
85 return offset
86}
87
88// toString converts the entire buffer (slice of lines) into a single string.
89func (b *Buffer) toString() string {
90 var result strings.Builder
91 for i, line := range b.buffer {
92 result.WriteString(string(line))
93 if i < len(b.buffer)-1 {
94 result.WriteString("\n")
95 }
96 }
97 return result.String()
98}
99
100// handleEdit is a placeholder for incremental syntax highlighting updates.
101func (b *Buffer) handleEdit(startRow, startCol int, bytesRemoved, bytesAdded uint32, oldEndRow int, oldEndColBytes uint32, newEndRow int, newEndColBytes uint32) {
102 if b.syntax == nil {
103 return
104 }
105
106 // We batch syntax updates in editor.go via Reparse, so we don't need
107 // incremental updates here.
108}