package buffer import "strings" // DefaultTabWidth is how many columns a tab character spans on screen unless // the caller says otherwise. Eight matches gofmt's own assumption. const DefaultTabWidth = 8 // Buffer is the text of one file, held as a slice of lines. // // Every buffer has at least one line, so an empty buffer is a single empty // line rather than no line at all. That invariant removes a special case from // every method below. // // A Buffer is not safe for concurrent use. type Buffer struct { lines [][]rune path string modified bool crlf bool // the file used \r\n line endings when it was read finalNewline bool // the file ended with a line terminator cursor Position anchor *Position // start of the selection, nil when nothing is selected tabWidth int undoStack []edit redoStack []edit coalesce bool // the next edit may merge into the previous undo entry revision int // bumped on every change, so caches know when to rebuild } // New returns an empty buffer holding a single empty line. // // Its text is the empty string, not a lone line feed: the buffer holds exactly // what was put into it, and nothing is put into a new one. Pressing Enter at // the end of the last line is what gives a file its trailing newline, here as // in any other editor. // // b := buffer.New() // b.Insert("package main\n") // fmt.Println(b.LineCount()) // 2 func New() *Buffer { return &Buffer{ lines: [][]rune{{}}, tabWidth: DefaultTabWidth, } } // NewFromString returns a buffer holding text, split on line feeds. // // A trailing line feed is remembered rather than turned into an extra empty // line, so that saving the buffer reproduces the input byte for byte. // // b := buffer.NewFromString("package main\n\nfunc main() {}\n") // fmt.Println(b.LineCount()) // 3 func NewFromString(text string) *Buffer { b := New() b.SetText(text) b.modified = false b.clearHistory() return b } // SetText replaces the whole content of the buffer, resets the cursor to the // start and drops the selection. The undo history is kept, so loading a // template over a buffer stays undoable. func (b *Buffer) SetText(text string) { b.crlf = strings.Contains(text, "\r\n") if b.crlf { text = strings.ReplaceAll(text, "\r\n", "\n") } b.finalNewline = strings.HasSuffix(text, "\n") if b.finalNewline { text = strings.TrimSuffix(text, "\n") } b.ReplaceRange(b.wholeRange(), text) b.SetCursor(Position{}) b.ClearSelection() } // Text returns the whole content of the buffer, with the line endings the file // was read with and the trailing newline it had, if any. func (b *Buffer) Text() string { sep := "\n" if b.crlf { sep = "\r\n" } parts := make([]string, len(b.lines)) for i, line := range b.lines { parts[i] = string(line) } text := strings.Join(parts, sep) if b.finalNewline { text += sep } return text } // LineCount returns the number of lines, always at least one. func (b *Buffer) LineCount() int { return len(b.lines) } // Line returns line i as a string, or the empty string if i is out of range. func (b *Buffer) Line(i int) string { if i < 0 || i >= len(b.lines) { return "" } return string(b.lines[i]) } // LineRunes returns a copy of line i. Callers may modify the result freely; it // is disconnected from the buffer. func (b *Buffer) LineRunes(i int) []rune { if i < 0 || i >= len(b.lines) { return nil } out := make([]rune, len(b.lines[i])) copy(out, b.lines[i]) return out } // LineLen returns the number of runes on line i, or zero if i is out of range. func (b *Buffer) LineLen(i int) int { if i < 0 || i >= len(b.lines) { return 0 } return len(b.lines[i]) } // Path returns the file this buffer was read from, or the empty string for a // buffer that has never been associated with a file. func (b *Buffer) Path() string { return b.path } // SetPath records the file this buffer belongs to, as Save As does. func (b *Buffer) SetPath(path string) { b.path = path } // Modified reports whether the buffer has unsaved changes. Undoing back to the // state that was last saved still counts as modified: the flag errs towards // offering to save rather than towards losing work. func (b *Buffer) Modified() bool { return b.modified } // Revision is a counter bumped on every change to the text. Two reads that // return the same number are guaranteed to have seen the same text, which is // what lets the syntax highlighter skip work between redraws. func (b *Buffer) Revision() int { return b.revision } // TabWidth returns how many columns a tab character spans on screen. func (b *Buffer) TabWidth() int { return b.tabWidth } // SetTabWidth sets how many columns a tab character spans on screen. Values // below one are ignored, since a zero-width tab would make columns ambiguous. func (b *Buffer) SetTabWidth(width int) { if width > 0 { b.tabWidth = width } } // wholeRange returns the range covering every character in the buffer. func (b *Buffer) wholeRange() Range { last := len(b.lines) - 1 return Range{ Start: Position{}, End: Position{Line: last, Col: len(b.lines[last])}, } } // lineAt returns line i, clamped into range. A buffer always holds at least // one line, so the result is never nil. func (b *Buffer) lineAt(i int) []rune { if i < 0 { return b.lines[0] } if i >= len(b.lines) { return b.lines[len(b.lines)-1] } return b.lines[i] } // clamp moves p to the nearest position that actually exists in the buffer. func (b *Buffer) clamp(p Position) Position { if p.Line < 0 { return Position{} } if p.Line >= len(b.lines) { last := len(b.lines) - 1 return Position{Line: last, Col: len(b.lines[last])} } if p.Col < 0 { p.Col = 0 } if p.Col > len(b.lines[p.Line]) { p.Col = len(b.lines[p.Line]) } return p } // splitLines turns text into lines of runes, always returning at least one. func splitLines(text string) [][]rune { parts := strings.Split(text, "\n") lines := make([][]rune, len(parts)) for i, part := range parts { lines[i] = []rune(part) } return lines }