summaryrefslogtreecommitdiff
path: root/buffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'buffer.c')
-rw-r--r--buffer.c116
1 files changed, 116 insertions, 0 deletions
diff --git a/buffer.c b/buffer.c
new file mode 100644
index 0000000..78fcc1a
--- /dev/null
+++ b/buffer.c
@@ -0,0 +1,116 @@
+// Text buffers - These store the contents of a file.
+// To make inserting characters faster, the file contents are split up into
+// blocks of a few thousand bytes. Block boundaries are not necessarily
+// on character boundaries, so one block might have part of a UTF-8 character
+// with the next block having the rest.
+#include "util.c"
+
+#define TEXT_BLOCK_MAX_SIZE 400
+// Once two adjacent blocks are at most this big, combine them into
+// one bigger text block.
+#define TEXT_BLOCK_COMBINE_SIZE (3*(TEXT_BLOCK_MAX_SIZE / 4))
+// A good size for text blocks to be. When a file is loaded, there
+// shouldn't be just full blocks, because they will all immediately split.
+// Instead, we use blocks of this size.
+#define TEXT_BLOCK_DEFAULT_SIZE (3*(TEXT_BLOCK_MAX_SIZE / 4))
+typedef struct {
+ u16 len;
+ char *contents;
+} TextBlock;
+
+typedef struct {
+ u32 nblocks; // number of text blocks
+ TextBlock *blocks;
+} TextBuffer;
+
+// Returns a new block added at index `where`,
+// or NULL if there's not enough memory.
+static TextBlock *text_buffer_add_block(TextBuffer *buffer, u32 where) {
+ // make sure we can actually allocate enough memory for the contents of the block
+ // before doing anything
+ char *block_contents = calloc(1, TEXT_BLOCK_MAX_SIZE);
+ if (!block_contents) {
+ return NULL;
+ }
+
+ u32 nblocks_before = buffer->nblocks;
+ u32 nblocks_after = buffer->nblocks + 1;
+ if (util_is_power_of_2(nblocks_after)) {
+ // whenever the number of blocks is a power of 2, we need to grow the blocks array.
+ buffer->blocks = realloc(buffer->blocks, 2 * nblocks_after * sizeof *buffer->blocks);
+ if (!buffer->blocks) {
+ // out of memory
+ free(block_contents);
+ return NULL;
+ }
+ }
+ if (where != nblocks_before) { // if we aren't just inserting the block at the end,
+ // make space for the new block
+ memmove(buffer->blocks + (where + 1), buffer->blocks + where, nblocks_before - where);
+ }
+
+ TextBlock *block = buffer->blocks + where;
+ util_zero_memory(block, sizeof *buffer->blocks); // zero everything
+ block->contents = block_contents;
+
+
+ buffer->nblocks = nblocks_after;
+ return block;
+}
+
+Status text_buffer_load_file(TextBuffer *buffer, FILE *fp) {
+ char block_contents[TEXT_BLOCK_DEFAULT_SIZE];
+ size_t bytes_read;
+
+ util_zero_memory(buffer, sizeof *buffer);
+
+ // read file one block at a time
+ do {
+ bytes_read = fread(block_contents, 1, sizeof block_contents, fp);
+ if (bytes_read > 0) {
+ TextBlock *block = text_buffer_add_block(buffer, buffer->nblocks);
+ if (!block) {
+ return false;
+ }
+ memcpy(block->contents, block_contents, bytes_read);
+ block->len = (u16)bytes_read;
+ }
+ } while (bytes_read == sizeof block_contents);
+ if (ferror(fp))
+ return false;
+ return true;
+}
+
+static void text_buffer_print_internal(TextBuffer *buffer, bool debug) {
+ printf("\033[2J\033[;H");
+ TextBlock *blocks = buffer->blocks;
+ u32 nblocks = buffer->nblocks;
+
+ for (u32 i = 0; i < nblocks; ++i) {
+ TextBlock *block = &blocks[i];
+ fwrite(block->contents, 1, block->len, stdout);
+ if (debug)
+ printf("\n\n------\n\n"); // NOTE: could be bad with UTF-8
+ }
+ fflush(stdout);
+}
+
+// print the contents of a buffer to stdout
+void text_buffer_print(TextBuffer *buffer) {
+ text_buffer_print_internal(buffer, false);
+}
+
+// print the contents of a buffer to stdout, along with debugging information
+void text_buffer_print_debug(TextBuffer *buffer) {
+ text_buffer_print_internal(buffer, true);
+}
+
+// Does not free the pointer `buffer` (buffer might not have even been allocated with malloc)
+void text_buffer_free(TextBuffer *buffer) {
+ TextBlock *blocks = buffer->blocks;
+ u32 nblocks = buffer->nblocks;
+ for (u32 i = 0; i < nblocks; ++i) {
+ free(blocks[i].contents);
+ }
+ free(blocks);
+}