Frees everything the buffer owns and leaves it empty.
#import "Extensions/Text_Buffer";
An editable UTF-8 text buffer with undo, for editors.
The text sits in a piece table, so inserting or deleting in a large file costs the number of edits
made so far, not the size of the file. Lines are found by binary search. Offsets are byte offsets;
move the cursor on character boundaries with next_grapheme and previous_grapheme.
#import "Extensions/Text_Buffer";
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "hello\nworld");
insert(*buffer, 5, ", there");
erase(*buffer, 0, 1);
assert(line_text(*buffer, 0) == "ello, there");
undo(*buffer);
assert(line_text(*buffer, 0) == "hello, there");Procedures#
General#
Replaces the whole text with a copy of text and forgets the undo history.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "one\ntwo");
assert(line_count(*buffer) == 2);↗The number of lines: one more than the newlines, so an empty buffer has one empty line.
buffer: Text_Buffer;
defer free_buffer(*buffer);
assert(line_count(*buffer) == 1);
set_text(*buffer, "a\nb\nc");
assert(line_count(*buffer) == 3);↗The offset of the first byte of line. A line past the end gives the last line.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "ab\ncd");
assert(line_start(*buffer, 1) == 3);↗The offset just past the last byte of line, before its line break (\n or \r\n).
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "ab\r\ncd");
assert(line_end(*buffer, 0) == 2);
assert(line_end(*buffer, 1) == 6);↗The line that holds offset, or the last line for an offset at the end.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "ab\ncd");
assert(line_of_offset(*buffer, 4) == 1);↗The byte at offset, or 0 outside the text.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "abc");
assert(byte_at(*buffer, 1) == #char "b");
assert(byte_at(*buffer, 9) == 0);↗The text of line without its line break. It points into the buffer when the line sits in one
piece and is a temporary copy otherwise; either way it is valid only until the next edit.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "first\nsecond");
assert(line_text(*buffer, 1) == "second");↗The bytes from start up to end. Like line_text, it is a view or a temporary copy, valid until
the next edit. Use copy_range to keep it.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "hello world");
assert(text_range(*buffer, 6, 11) == "world");↗A copy of the bytes from start up to end, made with allocator.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "hello world");
word := copy_range(*buffer, 0, 5, temp);
insert(*buffer, 0, ">> ");
assert(word == "hello");↗The whole text as a new string, made with allocator. Free it unless the allocator is temporary.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "abc");
insert(*buffer, 3, "def");
assert(buffer_to_string(*buffer, temp) == "abcdef");↗Inserts text at offset. Undo can take it back; typing one character after another merges into
a single undo step.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "held");
insert(*buffer, 2, "llo wor");
assert(buffer_to_string(*buffer, temp) == "hello world");↗Deletes count bytes from offset. Undo can take it back.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "hello world");
erase(*buffer, 5, 6);
assert(buffer_to_string(*buffer, temp) == "hello");↗Replaces count bytes at offset with text, as one undo step.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "hello world");
replace(*buffer, 6, 5, "there");
assert(buffer_to_string(*buffer, temp) == "hello there");
undo(*buffer);
assert(buffer_to_string(*buffer, temp) == "hello world");↗The offset of the first needle at or after from, or -1.
Graphemes#
The offset after the grapheme cluster (what a reader sees as one character) that starts at index.
Returns text.count at the end.
text := "e\xCC\x81x"; // e + combining acute accent, then x
assert(next_grapheme(text, 0) == 3);
assert(next_grapheme(text, 3) == 4);↗The offset where the grapheme cluster that ends at index starts. Returns 0 at the start.
text := "e\xCC\x81x";
assert(previous_grapheme(text, 4) == 3);
assert(previous_grapheme(text, 3) == 0);↗The class of a code point for word movement. A run of one class is a word.
The offset where the word right of index ends. Spaces are skipped first, then a run of word
characters or of punctuation. Line breaks are the caller's to handle.
The offset where the word left of index starts.
assert(previous_word("hello world", 11) == 6);
assert(previous_word("hello world", 6) == 0);↗The number of grapheme clusters in text.
assert(grapheme_count("e\xCC\x81x") == 2);↗Decodes the code point at index: its value and its length in bytes. A malformed byte decodes as
U+FFFD, one byte at a time, so a scan always advances.
The start of the code point that ends at index.
History#
Frees the stacks and the text of every edit in them.
Makes every edit until the matching end_group undo as one step. Groups nest.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "ab");
begin_group(*buffer);
insert(*buffer, 0, "x");
insert(*buffer, 3, "y");
end_group(*buffer);
undo(*buffer);
assert(buffer_to_string(*buffer, temp) == "ab");↗Ends the group that begin_group started.
Stops the next typed character from merging into the previous undo step. Call it when the cursor moves, so undo steps follow what the user did.
buffer: Text_Buffer;
defer free_buffer(*buffer);
insert(*buffer, 0, "a");
insert(*buffer, 1, "b");
seal_undo(*buffer);
insert(*buffer, 2, "c");
undo(*buffer);
assert(buffer_to_string(*buffer, temp) == "ab");↗Whether undo has anything to undo.
buffer: Text_Buffer;
defer free_buffer(*buffer);
assert(!can_undo(*buffer));
insert(*buffer, 0, "a");
assert(can_undo(*buffer));↗Whether redo has anything to redo.
Undoes the last step. Returns whether anything was undone and the offset where the cursor belongs.
buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "hello");
erase(*buffer, 0, 5);
ok, cursor := undo(*buffer);
assert(ok && cursor == 5);
assert(buffer_to_string(*buffer, temp) == "hello");↗Redoes the last undone step. Returns whether anything was redone and the offset where the cursor belongs.
buffer: Text_Buffer;
defer free_buffer(*buffer);
insert(*buffer, 0, "hi");
undo(*buffer);
assert(can_redo(*buffer));
redo(*buffer);
assert(buffer_to_string(*buffer, temp) == "hi");↗Types#
A run of bytes in one of the buffer's two backing arrays. The text is the pieces in order.
- from_added: bool
- false: the original text, true: the added text
- start: int
- length: int
The text being edited. A zero value is an empty buffer; release it with free_buffer. Change it
only through the procedures here. version goes up with every edit, so a cache keyed on the
text can tell when to refresh.
What a code point counts as when moving by words.
- SPACE
- WORD
- PUNCTUATION
One recorded change: removed was replaced by inserted at offset. Edits in one group undo together.
- offset: int
- removed: string
- heap copies, released with the history
- inserted: string
- group: int
A buffer's undo and redo stacks. Use undo, redo, begin_group and end_group instead of reading it.