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}