Modules

Text_Buffer

moduleextensionexperimentalSource

Available on macOS, Linux, Windows, Wasm·since 0.31.0

#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.

Example↗
#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#

line_startexperimentalsince 0.31.0Source
(buffer: *Text_Buffer, line: int) -> int

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);
↗
line_endexperimentalsince 0.31.0Source
(buffer: *Text_Buffer, line: int) -> int

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);
↗
line_textexperimentalsince 0.31.0Source
(buffer: *Text_Buffer, line: int) -> string

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");
↗
text_rangeexperimentalsince 0.31.0Source
(buffer: *Text_Buffer, start: int, end: int) -> string

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");
↗
copy_rangeexperimentalsince 0.31.0Source
(buffer: *Text_Buffer, start: int, end: int, allocator: Allocator) -> string

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");
↗
buffer_to_stringexperimentalsince 0.31.0Source
(buffer: *Text_Buffer, allocator: Allocator = context.allocator) -> string

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");
↗
insertexperimentalsince 0.31.0Source
(buffer: *Text_Buffer, offset: int, text: string)

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");
↗
find_textexperimentalsince 0.31.0Source
(buffer: *Text_Buffer, needle: string, from: int = 0) -> int

The offset of the first needle at or after from, or -1.

buffer: Text_Buffer;
defer free_buffer(*buffer);
set_text(*buffer, "to be or not to be");
assert(find_text(*buffer, "be") == 3);
assert(find_text(*buffer, "be", 4) == 16);
assert(find_text(*buffer, "maybe") == -1);
↗

Graphemes#

next_graphemeexperimentalsince 0.31.0Source
(text: string, index: int) -> int

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);
↗
word_classexperimentalsince 0.31.0Source
(c: u32) -> Word_Class

The class of a code point for word movement. A run of one class is a word.

next_wordexperimentalsince 0.31.0Source
(text: string, index: int) -> int

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.

assert(next_word("hello world", 0) == 5);
assert(next_word("hello world", 5) == 11);
↗
decode_atexperimentalsince 0.31.0Source
(text: string, index: int) -> (code: u32, size: int)

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.

code, size := decode_at("é", 0);
assert(code == 0xE9 && size == 2);
↗
previous_code_pointexperimentalsince 0.31.0Source
(text: string, index: int) -> int

The start of the code point that ends at index.

History#

undoexperimentalsince 0.31.0Source
(buffer: *Text_Buffer) -> (ok: bool, cursor: int)

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");
↗
redoexperimentalsince 0.31.0Source
(buffer: *Text_Buffer) -> (ok: bool, cursor: int)

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#

Piecestructexperimentalsince 0.31.0Source
:: struct

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
Text_Bufferstructexperimentalsince 0.31.0Source
:: struct

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.

original: [..] u8
added: [..] u8
pieces: [..] Piece
length: int
bytes in the whole text
line_starts: [..] int
offset of the start of each line; line 0 starts at 0 (empty until first use)
version: int
counts edits, so caches keyed on the text know when it changed
history: History
Word_Classenumexperimentalsince 0.31.0Source
:: enum

What a code point counts as when moving by words.

SPACE
WORD
PUNCTUATION
Editstructexperimentalsince 0.31.0Source
:: struct

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
Historystructexperimentalsince 0.31.0Source
:: struct

A buffer's undo and redo stacks. Use undo, redo, begin_group and end_group instead of reading it.

undo_stack: [..] Edit
redo_stack: [..] Edit
next_group: int
open_depth: int
begin_group nesting
open_group: int
mergeable: bool
the last recorded edit may absorb the next one