turbo-editors/turbo-corepublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

buffer.go · 214 lines · 5.9 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 20h ago1package buffer
2
3import "strings"
4
5// DefaultTabWidth is how many columns a tab character spans on screen unless
6// the caller says otherwise. Eight matches gofmt's own assumption.
7const DefaultTabWidth = 8
8
9// Buffer is the text of one file, held as a slice of lines.
10//
11// Every buffer has at least one line, so an empty buffer is a single empty
12// line rather than no line at all. That invariant removes a special case from
13// every method below.
14//
15// A Buffer is not safe for concurrent use.
16type Buffer struct {
17 lines [][]rune
18
19 path string
20 modified bool
21 crlf bool // the file used \r\n line endings when it was read
22 finalNewline bool // the file ended with a line terminator
23
24 cursor Position
25 anchor *Position // start of the selection, nil when nothing is selected
26
27 tabWidth int
28
29 undoStack []edit
30 redoStack []edit
31 coalesce bool // the next edit may merge into the previous undo entry
32
33 revision int // bumped on every change, so caches know when to rebuild
34}
35
36// New returns an empty buffer holding a single empty line.
37//
38// Its text is the empty string, not a lone line feed: the buffer holds exactly
39// what was put into it, and nothing is put into a new one. Pressing Enter at
40// the end of the last line is what gives a file its trailing newline, here as
41// in any other editor.
42//
43// b := buffer.New()
44// b.Insert("package main\n")
45// fmt.Println(b.LineCount()) // 2
46func New() *Buffer {
47 return &Buffer{
48 lines: [][]rune{{}},
49 tabWidth: DefaultTabWidth,
50 }
51}
52
53// NewFromString returns a buffer holding text, split on line feeds.
54//
55// A trailing line feed is remembered rather than turned into an extra empty
56// line, so that saving the buffer reproduces the input byte for byte.
57//
58// b := buffer.NewFromString("package main\n\nfunc main() {}\n")
59// fmt.Println(b.LineCount()) // 3
60func NewFromString(text string) *Buffer {
61 b := New()
62 b.SetText(text)
63 b.modified = false
64 b.clearHistory()
65 return b
66}
67
68// SetText replaces the whole content of the buffer, resets the cursor to the
69// start and drops the selection. The undo history is kept, so loading a
70// template over a buffer stays undoable.
71func (b *Buffer) SetText(text string) {
72 b.crlf = strings.Contains(text, "\r\n")
73 if b.crlf {
74 text = strings.ReplaceAll(text, "\r\n", "\n")
75 }
76
77 b.finalNewline = strings.HasSuffix(text, "\n")
78 if b.finalNewline {
79 text = strings.TrimSuffix(text, "\n")
80 }
81
82 b.ReplaceRange(b.wholeRange(), text)
83 b.SetCursor(Position{})
84 b.ClearSelection()
85}
86
87// Text returns the whole content of the buffer, with the line endings the file
88// was read with and the trailing newline it had, if any.
89func (b *Buffer) Text() string {
90 sep := "\n"
91 if b.crlf {
92 sep = "\r\n"
93 }
94
95 parts := make([]string, len(b.lines))
96 for i, line := range b.lines {
97 parts[i] = string(line)
98 }
99
100 text := strings.Join(parts, sep)
101 if b.finalNewline {
102 text += sep
103 }
104 return text
105}
106
107// LineCount returns the number of lines, always at least one.
108func (b *Buffer) LineCount() int {
109 return len(b.lines)
110}
111
112// Line returns line i as a string, or the empty string if i is out of range.
113func (b *Buffer) Line(i int) string {
114 if i < 0 || i >= len(b.lines) {
115 return ""
116 }
117 return string(b.lines[i])
118}
119
120// LineRunes returns a copy of line i. Callers may modify the result freely; it
121// is disconnected from the buffer.
122func (b *Buffer) LineRunes(i int) []rune {
123 if i < 0 || i >= len(b.lines) {
124 return nil
125 }
126 out := make([]rune, len(b.lines[i]))
127 copy(out, b.lines[i])
128 return out
129}
130
131// LineLen returns the number of runes on line i, or zero if i is out of range.
132func (b *Buffer) LineLen(i int) int {
133 if i < 0 || i >= len(b.lines) {
134 return 0
135 }
136 return len(b.lines[i])
137}
138
139// Path returns the file this buffer was read from, or the empty string for a
140// buffer that has never been associated with a file.
141func (b *Buffer) Path() string { return b.path }
142
143// SetPath records the file this buffer belongs to, as Save As does.
144func (b *Buffer) SetPath(path string) { b.path = path }
145
146// Modified reports whether the buffer has unsaved changes. Undoing back to the
147// state that was last saved still counts as modified: the flag errs towards
148// offering to save rather than towards losing work.
149func (b *Buffer) Modified() bool { return b.modified }
150
151// Revision is a counter bumped on every change to the text. Two reads that
152// return the same number are guaranteed to have seen the same text, which is
153// what lets the syntax highlighter skip work between redraws.
154func (b *Buffer) Revision() int { return b.revision }
155
156// TabWidth returns how many columns a tab character spans on screen.
157func (b *Buffer) TabWidth() int { return b.tabWidth }
158
159// SetTabWidth sets how many columns a tab character spans on screen. Values
160// below one are ignored, since a zero-width tab would make columns ambiguous.
161func (b *Buffer) SetTabWidth(width int) {
162 if width > 0 {
163 b.tabWidth = width
164 }
165}
166
167// wholeRange returns the range covering every character in the buffer.
168func (b *Buffer) wholeRange() Range {
169 last := len(b.lines) - 1
170 return Range{
171 Start: Position{},
172 End: Position{Line: last, Col: len(b.lines[last])},
173 }
174}
175
176// lineAt returns line i, clamped into range. A buffer always holds at least
177// one line, so the result is never nil.
178func (b *Buffer) lineAt(i int) []rune {
179 if i < 0 {
180 return b.lines[0]
181 }
182 if i >= len(b.lines) {
183 return b.lines[len(b.lines)-1]
184 }
185 return b.lines[i]
186}
187
188// clamp moves p to the nearest position that actually exists in the buffer.
189func (b *Buffer) clamp(p Position) Position {
190 if p.Line < 0 {
191 return Position{}
192 }
193 if p.Line >= len(b.lines) {
194 last := len(b.lines) - 1
195 return Position{Line: last, Col: len(b.lines[last])}
196 }
197 if p.Col < 0 {
198 p.Col = 0
199 }
200 if p.Col > len(b.lines[p.Line]) {
201 p.Col = len(b.lines[p.Line])
202 }
203 return p
204}
205
206// splitLines turns text into lines of runes, always returning at least one.
207func splitLines(text string) [][]rune {
208 parts := strings.Split(text, "\n")
209 lines := make([][]rune, len(parts))
210 for i, part := range parts {
211 lines[i] = []rune(part)
212 }
213 return lines
214}