AxlTextBuffer — Editable Text Store

See AxlData — Data Structures for an overview of all data modules.

A growable, editable byte buffer with an integral line index, tuned for an interactive text editor: load a file once, then many small inserts/deletes near a moving cursor, with O(log n) byte-offset ↔ line mapping on every keystroke and repaint. Storage is a gap buffer; the line index is maintained incrementally (never a full rescan) and queried by binary search. The store is byte-oriented — '\n' is the only special byte — so the caller owns any UTF-8 / codepoint policy.

Header: <axl/axl-text-buffer.h>

API Reference

Typedefs

typedef struct AxlTextBuffer AxlTextBuffer

axl-text-buffer.h:

A growable, editable byte buffer with an integral line index, tuned for an interactive text editor: load a file once, then many small inserts/deletes near a moving cursor, and — every keystroke and every repaint — map between byte offsets and line numbers.

Storage is a gap buffer (O(1) amortized insert/delete at the gap; moving the gap to the next edit site is a single memmove). Because the bytes are not contiguous, callers read ranges out via axl_text_buffer_get rather than holding a pointer.

The store is byte-oriented and encoding-agnostic: ‘

’ (0x0A) is the only special byte (the line delimiter). Any UTF-8 / codepoint policy is the caller’s to apply on top.

Line index: a sorted index of newline offsets, maintained incrementally on every edit (never a full rescan). Offset->line and line->bounds are O(log n) binary searches — the editor calls them on every keystroke and once per visible line per frame.

Functions

AxlTextBuffer *axl_text_buffer_new(size_t initial_capacity)

Create an empty text buffer.

initial_capacity is a hint for the initial gap size (0 = a small default); the buffer grows as needed.

Parameters:
  • initial_capacity – initial byte capacity hint (0 = default)

Returns:

new buffer (length 0, line count 1), or NULL on OOM. Free with axl_text_buffer_free().

void axl_text_buffer_free(AxlTextBuffer *tb)

Free a text buffer and its storage. NULL-safe.

Parameters:
  • tb – buffer (NULL-safe)

int axl_text_buffer_set_bytes(AxlTextBuffer *tb, const char *data, size_t len)

Replace the entire contents with data.

Rebuilds the line index from scratch (this is the one O(n) operation — it is a full load, not an incremental edit).

Parameters:
  • tb – buffer

  • data – replacement bytes (may be NULL iff len is 0)

  • len – number of bytes

Returns:

AXL_OK on success, AXL_ERR on OOM or invalid args.

size_t axl_text_buffer_length(const AxlTextBuffer *tb)

Total byte length of the buffer’s contents.

Parameters:
  • tb – buffer

int axl_text_buffer_insert(AxlTextBuffer *tb, size_t offset, const char *data, size_t len)

Insert len bytes at byte offset.

offset is clamped to the current length (so length is an append). The line index is updated incrementally.

Parameters:
  • tb – buffer

  • offset – byte offset to insert at (clamped to length)

  • data – bytes to insert

  • len – number of bytes

Returns:

AXL_OK on success, AXL_ERR on OOM or invalid args (NULL data with len > 0).

int axl_text_buffer_delete(AxlTextBuffer *tb, size_t offset, size_t len)

Delete len bytes starting at byte offset.

offset past the end deletes nothing; len is clamped so the range never extends past the end. The line index is updated incrementally.

Parameters:
  • tb – buffer

  • offset – byte offset to delete from

  • len – number of bytes to delete

Returns:

AXL_OK on success (including the no-op cases), AXL_ERR on invalid args.

size_t axl_text_buffer_get(const AxlTextBuffer *tb, size_t offset, size_t len, char *out, size_t cap)

Copy a byte range out into out.

Copies up to min(len, cap) bytes of [offset, offset+len), clamped to the buffer length. A gap buffer is not contiguous, so this copy-out is how callers read content.

Parameters:
  • tb – buffer

  • offset – byte offset to read from

  • len – bytes requested

  • out – destination buffer

  • cap – capacity of out

Returns:

number of bytes copied (may be < len at end-of-buffer or when cap is the limit).

int axl_text_buffer_byte_at(const AxlTextBuffer *tb, size_t offset)

Read a single byte.

Parameters:
  • tb – buffer

  • offset – byte offset

Returns:

the byte (0–255) at offset, or -1 if offset is out of range.

size_t axl_text_buffer_line_count(const AxlTextBuffer *tb)

Number of lines.

Defined as (newline count + 1): an empty buffer is 1 line, and a trailing ‘

’ yields a real (empty) final line.

Parameters:
  • tb – buffer

size_t axl_text_buffer_line_of_offset(const AxlTextBuffer *tb, size_t offset)

The line number (0-based) containing byte offset.

offset

is clamped to the buffer length. A ‘

’ byte belongs to the line it terminates. O(log n).

Parameters:
  • tb – buffer

  • offset – byte offset

Returns:

0-based line number.

int axl_text_buffer_line_bounds(const AxlTextBuffer *tb, size_t line, size_t *start, size_t *end)

Byte range [start, end) of line line.

end

excludes the line’s terminating ‘

’ (so it is the offset of that ‘

‘, or the buffer length for the last line). O(log n).

Parameters:
  • tb – buffer

  • line – 0-based line number

  • start – [out] first byte of the line

  • end

    [out] one past the last byte, excluding ‘

Returns:

AXL_OK if line is valid, AXL_ERR if line >= line count.

char *axl_text_buffer_get_alloc(const AxlTextBuffer *tb, size_t offset, size_t len)

Copy a byte range out into a fresh NUL-terminated buffer.

The allocating counterpart of axl_text_buffer_get (mirrors axl_piece_tree_get_alloc): allocates (clamped length + 1) bytes, copies [offset, offset+len) clamped to the buffer, and NUL-terminates. Raw bytes (embedded NULs preserved; a C-string view stops at the first).

Parameters:
  • tb – buffer

  • offset – byte offset

  • len – bytes requested (clamped to the buffer)

Returns:

newly allocated buffer (free with axl_free; an empty range yields a 1-byte “”), or NULL on OOM / NULL tb.

size_t axl_text_buffer_cp_align(const AxlTextBuffer *tb, size_t offset)

Round offset down to the start of the UTF-8 codepoint that contains it (mirror of axl_piece_tree_cp_align).

Returns offset unchanged on a codepoint boundary (or at 0 / the buffer end); otherwise steps back over continuation bytes. Use it to snap an arbitrary byte offset to a valid caret position.

Parameters:
  • tb – buffer

  • offset – byte offset

size_t axl_text_buffer_cp_next(const AxlTextBuffer *tb, size_t offset)

Offset of the next UTF-8 codepoint boundary after offset (caret “move right”; clamped at the buffer end).

Parameters:
  • tb – buffer

  • offset – byte offset

size_t axl_text_buffer_cp_prev(const AxlTextBuffer *tb, size_t offset)

Offset of the previous UTF-8 codepoint boundary before offset (caret “move left”; clamped at 0).

Parameters:
  • tb – buffer

  • offset – byte offset

bool axl_text_buffer_find(AxlTextBuffer *tb, const char *needle, size_t needle_len, size_t from_offset, uint32_t flags, AxlMatch *out)

Search for a byte substring (mirror of axl_piece_tree_find).

Finds needle starting the scan at from_offset. Forward (default) returns the lowest match with start >= from_offset; AXL_FIND_BACKWARD returns the highest match with start <= from_offset. AXL_FIND_CASE_INSENSITIVE folds ASCII case; AXL_FIND_WHOLE_WORD requires non-word bytes (anything but [A-Za-z0-9_]) on both sides. Matches that straddle the internal gap are handled. Wrap-around is the caller’s job.

Thin wrapper over axl_find_in_source (see AxlByteReader).

Parameters:
  • tb – buffer

  • needle – bytes to find

  • needle_len – length of needle

  • from_offset – where to start scanning

  • flags – AxlFindFlags

  • out – [out] match on success

Returns:

true and fills out on a match; false if not found (or needle_len is 0).