Download ide/native/editor/buffer.c from Snapkitty/sovereign-engine-v2: direct link, hf CLI and curl.
- Browser
- Download file 13.5 kB
-
https://huggingface.co/Snapkitty/sovereign-engine-v2/resolve/main/ide/native/editor/buffer.c
- Command line
-
hf download hf://Snapkitty/sovereign-engine-v2/ide/native/editor/buffer.c
-
curl -L -o buffer.c https://huggingface.co/Snapkitty/sovereign-engine-v2/resolve/main/ide/native/editor/buffer.c
13.5 kB
| /* | |
| * Sovereign IDE — Piece Table Buffer | |
| * Supports insert, delete, undo/redo, line indexing. | |
| */ | |
| typedef enum PieceSource { PIECE_ORIGINAL, PIECE_ADD } PieceSource; | |
| typedef struct Piece { | |
| PieceSource source; | |
| size_t start; | |
| size_t length; | |
| struct Piece *prev; | |
| struct Piece *next; | |
| } Piece; | |
| typedef enum UndoKind { UNDO_INSERT, UNDO_DELETE } UndoKind; | |
| typedef struct UndoEntry { | |
| UndoKind kind; | |
| size_t offset; | |
| size_t len; | |
| char *deleted_text; | |
| } UndoEntry; | |
| struct Buffer { | |
| Arena arena; | |
| char *original; | |
| size_t original_len; | |
| char *add_buf; | |
| size_t add_len; | |
| size_t add_cap; | |
| Piece sentinel; | |
| size_t total_len; | |
| size_t line_count; | |
| UndoEntry undo_stack[UNDO_STACK_CAP]; | |
| int undo_top; | |
| int redo_top; | |
| bool dirty; | |
| }; | |
| static const char *piece_data(const Buffer *b, const Piece *p) { | |
| return (p->source == PIECE_ORIGINAL) | |
| ? b->original + p->start | |
| : b->add_buf + p->start; | |
| } | |
| static size_t count_newlines(const char *text, size_t len) { | |
| size_t n = 0; | |
| for (size_t i = 0; i < len; i++) { | |
| if (text[i] == '\n') n++; | |
| } | |
| return n; | |
| } | |
| static void append_to_add(Buffer *b, const char *text, size_t len) { | |
| if (b->add_len + len > b->add_cap) { | |
| size_t new_cap = b->add_cap * 2; | |
| while (new_cap < b->add_len + len) new_cap *= 2; | |
| char *new_buf = (char *)arena_alloc(&b->arena, new_cap); | |
| memcpy(new_buf, b->add_buf, b->add_len); | |
| b->add_buf = new_buf; | |
| b->add_cap = new_cap; | |
| } | |
| memcpy(b->add_buf + b->add_len, text, len); | |
| b->add_len += len; | |
| } | |
| static Piece *alloc_piece(Buffer *b) { | |
| return (Piece *)arena_alloc(&b->arena, sizeof(Piece)); | |
| } | |
| static void find_piece_at(Buffer *b, size_t offset, Piece **out_piece, size_t *out_local) { | |
| Piece *p = b->sentinel.next; | |
| size_t pos = 0; | |
| while (p != &b->sentinel && pos + p->length <= offset) { | |
| pos += p->length; | |
| p = p->next; | |
| } | |
| *out_piece = p; | |
| *out_local = offset - pos; | |
| } | |
| Buffer *buffer_create(const char *text, size_t len) { | |
| Arena a; | |
| arena_init(&a, 8 * 1024 * 1024); | |
| Buffer *b = (Buffer *)arena_alloc_zero(&a, sizeof(Buffer)); | |
| b->arena = a; | |
| b->original = (char *)arena_alloc(&b->arena, len + 1); | |
| memcpy(b->original, text, len); | |
| b->original[len] = '\0'; | |
| b->original_len = len; | |
| b->add_cap = ADD_BUFFER_INITIAL_CAP; | |
| b->add_buf = (char *)arena_alloc(&b->arena, b->add_cap); | |
| b->add_len = 0; | |
| if (len > 0) { | |
| Piece *first = alloc_piece(b); | |
| first->source = PIECE_ORIGINAL; | |
| first->start = 0; | |
| first->length = len; | |
| b->sentinel.next = first; | |
| b->sentinel.prev = first; | |
| first->prev = &b->sentinel; | |
| first->next = &b->sentinel; | |
| } else { | |
| b->sentinel.next = &b->sentinel; | |
| b->sentinel.prev = &b->sentinel; | |
| } | |
| b->total_len = len; | |
| b->line_count = 1 + count_newlines(text, len); | |
| b->undo_top = 0; | |
| b->redo_top = 0; | |
| b->dirty = false; | |
| return b; | |
| } | |
| void buffer_destroy(Buffer *b) { | |
| for (int i = 0; i < b->undo_top; i++) { | |
| free(b->undo_stack[i].deleted_text); | |
| } | |
| Arena a = b->arena; | |
| arena_destroy(&a); | |
| } | |
| size_t buffer_length(const Buffer *b) { | |
| return b->total_len; | |
| } | |
| size_t buffer_line_count(const Buffer *b) { | |
| return b->line_count; | |
| } | |
| bool buffer_is_dirty(const Buffer *b) { | |
| return b->dirty; | |
| } | |
| void buffer_mark_clean(Buffer *b) { | |
| b->dirty = false; | |
| } | |
| bool buffer_can_undo(const Buffer *b) { | |
| return b->undo_top > 0; | |
| } | |
| bool buffer_can_redo(const Buffer *b) { | |
| return b->redo_top > 0; | |
| } | |
| static void push_undo(Buffer *b, UndoKind kind, size_t offset, size_t len, const char *text) { | |
| if (b->undo_top >= UNDO_STACK_CAP) { | |
| free(b->undo_stack[0].deleted_text); | |
| memmove(&b->undo_stack[0], &b->undo_stack[1], (UNDO_STACK_CAP - 1) * sizeof(UndoEntry)); | |
| b->undo_top--; | |
| } | |
| /* discard redo stack on new edit */ | |
| for (int i = b->undo_top; i < b->undo_top + b->redo_top; i++) { | |
| if (i < UNDO_STACK_CAP) free(b->undo_stack[i].deleted_text); | |
| } | |
| b->redo_top = 0; | |
| UndoEntry *e = &b->undo_stack[b->undo_top++]; | |
| e->kind = kind; | |
| e->offset = offset; | |
| e->len = len; | |
| /* always copy the text — inserts need it for redo, deletes need it for undo */ | |
| if (text && len > 0) { | |
| e->deleted_text = (char *)malloc(len); | |
| memcpy(e->deleted_text, text, len); | |
| } else { | |
| e->deleted_text = NULL; | |
| } | |
| } | |
| void buffer_insert(Buffer *b, size_t offset, const char *text, size_t len) { | |
| if (len == 0) return; | |
| if (offset > b->total_len) offset = b->total_len; | |
| size_t add_start = b->add_len; | |
| append_to_add(b, text, len); | |
| Piece *p; | |
| size_t local; | |
| find_piece_at(b, offset, &p, &local); | |
| Piece *new_piece = alloc_piece(b); | |
| new_piece->source = PIECE_ADD; | |
| new_piece->start = add_start; | |
| new_piece->length = len; | |
| if (p == &b->sentinel || local == 0) { | |
| new_piece->prev = p->prev; | |
| new_piece->next = p; | |
| p->prev->next = new_piece; | |
| p->prev = new_piece; | |
| } else if (local == p->length) { | |
| new_piece->prev = p; | |
| new_piece->next = p->next; | |
| p->next->prev = new_piece; | |
| p->next = new_piece; | |
| } else { | |
| Piece *right = alloc_piece(b); | |
| right->source = p->source; | |
| right->start = p->start + local; | |
| right->length = p->length - local; | |
| p->length = local; | |
| new_piece->prev = p; | |
| new_piece->next = right; | |
| right->prev = new_piece; | |
| right->next = p->next; | |
| p->next->prev = right; | |
| p->next = new_piece; | |
| } | |
| b->total_len += len; | |
| b->line_count += count_newlines(text, len); | |
| b->dirty = true; | |
| push_undo(b, UNDO_INSERT, offset, len, text); | |
| } | |
| void buffer_delete(Buffer *b, size_t offset, size_t len) { | |
| if (len == 0 || offset >= b->total_len) return; | |
| if (offset + len > b->total_len) len = b->total_len - offset; | |
| /* save deleted text for undo */ | |
| char *deleted = (char *)malloc(len); | |
| buffer_read(b, offset, deleted, len); | |
| b->line_count -= count_newlines(deleted, len); | |
| size_t remaining = len; | |
| Piece *p; | |
| size_t local; | |
| find_piece_at(b, offset, &p, &local); | |
| while (remaining > 0 && p != &b->sentinel) { | |
| if (local == 0 && remaining >= p->length) { | |
| /* remove entire piece */ | |
| Piece *next = p->next; | |
| p->prev->next = p->next; | |
| p->next->prev = p->prev; | |
| remaining -= p->length; | |
| p = next; | |
| local = 0; | |
| } else if (local == 0) { | |
| /* trim from start */ | |
| p->start += remaining; | |
| p->length -= remaining; | |
| remaining = 0; | |
| } else if (local + remaining >= p->length) { | |
| /* trim from end */ | |
| size_t removed = p->length - local; | |
| p->length = local; | |
| remaining -= removed; | |
| p = p->next; | |
| local = 0; | |
| } else { | |
| /* split: remove middle */ | |
| Piece *right = alloc_piece(b); | |
| right->source = p->source; | |
| right->start = p->start + local + remaining; | |
| right->length = p->length - local - remaining; | |
| p->length = local; | |
| right->next = p->next; | |
| right->prev = p; | |
| p->next->prev = right; | |
| p->next = right; | |
| remaining = 0; | |
| } | |
| } | |
| b->total_len -= len; | |
| b->dirty = true; | |
| push_undo(b, UNDO_DELETE, offset, len, deleted); | |
| free(deleted); | |
| } | |
| static void raw_insert(Buffer *b, size_t offset, const char *text, size_t len) { | |
| size_t add_start = b->add_len; | |
| append_to_add(b, text, len); | |
| Piece *p; | |
| size_t local; | |
| find_piece_at(b, offset, &p, &local); | |
| Piece *new_piece = alloc_piece(b); | |
| new_piece->source = PIECE_ADD; | |
| new_piece->start = add_start; | |
| new_piece->length = len; | |
| if (p == &b->sentinel || local == 0) { | |
| new_piece->prev = p->prev; | |
| new_piece->next = p; | |
| p->prev->next = new_piece; | |
| p->prev = new_piece; | |
| } else if (local == p->length) { | |
| new_piece->prev = p; | |
| new_piece->next = p->next; | |
| p->next->prev = new_piece; | |
| p->next = new_piece; | |
| } else { | |
| Piece *right = alloc_piece(b); | |
| right->source = p->source; | |
| right->start = p->start + local; | |
| right->length = p->length - local; | |
| p->length = local; | |
| new_piece->prev = p; | |
| new_piece->next = right; | |
| right->prev = new_piece; | |
| right->next = p->next; | |
| p->next->prev = right; | |
| p->next = new_piece; | |
| } | |
| b->total_len += len; | |
| b->line_count += count_newlines(text, len); | |
| } | |
| static void raw_delete(Buffer *b, size_t offset, size_t len) { | |
| if (len == 0) return; | |
| char *tmp = (char *)malloc(len); | |
| buffer_read(b, offset, tmp, len); | |
| b->line_count -= count_newlines(tmp, len); | |
| free(tmp); | |
| size_t remaining = len; | |
| Piece *p; | |
| size_t local; | |
| find_piece_at(b, offset, &p, &local); | |
| while (remaining > 0 && p != &b->sentinel) { | |
| if (local == 0 && remaining >= p->length) { | |
| Piece *next = p->next; | |
| p->prev->next = p->next; | |
| p->next->prev = p->prev; | |
| remaining -= p->length; | |
| p = next; | |
| local = 0; | |
| } else if (local == 0) { | |
| p->start += remaining; | |
| p->length -= remaining; | |
| remaining = 0; | |
| } else if (local + remaining >= p->length) { | |
| size_t removed = p->length - local; | |
| p->length = local; | |
| remaining -= removed; | |
| p = p->next; | |
| local = 0; | |
| } else { | |
| Piece *right = alloc_piece(b); | |
| right->source = p->source; | |
| right->start = p->start + local + remaining; | |
| right->length = p->length - local - remaining; | |
| p->length = local; | |
| right->next = p->next; | |
| right->prev = p; | |
| p->next->prev = right; | |
| p->next = right; | |
| remaining = 0; | |
| } | |
| } | |
| b->total_len -= len; | |
| } | |
| void buffer_undo(Buffer *b) { | |
| if (b->undo_top == 0) return; | |
| b->undo_top--; | |
| UndoEntry *e = &b->undo_stack[b->undo_top]; | |
| b->redo_top++; | |
| if (e->kind == UNDO_INSERT) { | |
| raw_delete(b, e->offset, e->len); | |
| } else { | |
| raw_insert(b, e->offset, e->deleted_text, e->len); | |
| } | |
| b->dirty = true; | |
| } | |
| void buffer_redo(Buffer *b) { | |
| if (b->redo_top == 0) return; | |
| UndoEntry *e = &b->undo_stack[b->undo_top]; | |
| b->undo_top++; | |
| b->redo_top--; | |
| if (e->kind == UNDO_INSERT) { | |
| raw_insert(b, e->offset, e->deleted_text, e->len); | |
| } else { | |
| raw_delete(b, e->offset, e->len); | |
| } | |
| b->dirty = true; | |
| } | |
| size_t buffer_read(const Buffer *b, size_t offset, char *out, size_t max_len) { | |
| size_t copied = 0; | |
| size_t pos = 0; | |
| const Piece *p = b->sentinel.next; | |
| while (p != &b->sentinel && pos + p->length <= offset) { | |
| pos += p->length; | |
| p = p->next; | |
| } | |
| size_t skip = offset - pos; | |
| while (p != &b->sentinel && copied < max_len) { | |
| const char *data = piece_data(b, p) + skip; | |
| size_t avail = p->length - skip; | |
| size_t to_copy = (avail < max_len - copied) ? avail : (max_len - copied); | |
| memcpy(out + copied, data, to_copy); | |
| copied += to_copy; | |
| skip = 0; | |
| p = p->next; | |
| } | |
| return copied; | |
| } | |
| size_t buffer_line_start(const Buffer *b, size_t line) { | |
| if (line == 0) return 0; | |
| size_t current_line = 0; | |
| size_t pos = 0; | |
| const Piece *p = b->sentinel.next; | |
| while (p != &b->sentinel) { | |
| const char *data = piece_data(b, p); | |
| for (size_t i = 0; i < p->length; i++) { | |
| if (data[i] == '\n') { | |
| current_line++; | |
| if (current_line == line) return pos + i + 1; | |
| } | |
| } | |
| pos += p->length; | |
| p = p->next; | |
| } | |
| return b->total_len; | |
| } | |
| size_t buffer_line_length(const Buffer *b, size_t line) { | |
| size_t start = buffer_line_start(b, line); | |
| if (start >= b->total_len) return 0; | |
| size_t pos = start; | |
| const Piece *p = b->sentinel.next; | |
| size_t piece_pos = 0; | |
| while (p != &b->sentinel && piece_pos + p->length <= start) { | |
| piece_pos += p->length; | |
| p = p->next; | |
| } | |
| size_t skip = start - piece_pos; | |
| while (p != &b->sentinel) { | |
| const char *data = piece_data(b, p) + skip; | |
| size_t avail = p->length - skip; | |
| for (size_t i = 0; i < avail; i++) { | |
| if (data[i] == '\n') return pos - start; | |
| pos++; | |
| } | |
| skip = 0; | |
| p = p->next; | |
| } | |
| return pos - start; | |
| } | |