diff options
Diffstat (limited to 'examples/dte/edit.c')
| -rw-r--r-- | examples/dte/edit.c | 394 |
1 files changed, 0 insertions, 394 deletions
diff --git a/examples/dte/edit.c b/examples/dte/edit.c deleted file mode 100644 index 337a1e9..0000000 --- a/examples/dte/edit.c +++ /dev/null | |||
| @@ -1,394 +0,0 @@ | |||
| 1 | #include <string.h> | ||
| 2 | #include "edit.h" | ||
| 3 | #include "block.h" | ||
| 4 | #include "buffer.h" | ||
| 5 | #include "syntax/highlight.h" | ||
| 6 | #include "util/debug.h" | ||
| 7 | #include "util/list.h" | ||
| 8 | #include "util/xmalloc.h" | ||
| 9 | |||
| 10 | enum { | ||
| 11 | BLOCK_EDIT_SIZE = 512 | ||
| 12 | }; | ||
| 13 | |||
| 14 | static void sanity_check_blocks(const View *view, bool check_newlines) | ||
| 15 | { | ||
| 16 | #if DEBUG >= 1 | ||
| 17 | const Buffer *buffer = view->buffer; | ||
| 18 | BUG_ON(list_empty(&buffer->blocks)); | ||
| 19 | BUG_ON(view->cursor.offset > view->cursor.blk->size); | ||
| 20 | |||
| 21 | const Block *blk = BLOCK(buffer->blocks.next); | ||
| 22 | if (blk->size == 0) { | ||
| 23 | // The only time a zero-sized block is valid is when it's the | ||
| 24 | // first and only block | ||
| 25 | BUG_ON(buffer->blocks.next->next != &buffer->blocks); | ||
| 26 | BUG_ON(view->cursor.blk != blk); | ||
| 27 | return; | ||
| 28 | } | ||
| 29 | |||
| 30 | bool cursor_seen = false; | ||
| 31 | block_for_each(blk, &buffer->blocks) { | ||
| 32 | const size_t size = blk->size; | ||
| 33 | BUG_ON(size == 0); | ||
| 34 | BUG_ON(size > blk->alloc); | ||
| 35 | if (blk == view->cursor.blk) { | ||
| 36 | cursor_seen = true; | ||
| 37 | } | ||
| 38 | if (check_newlines) { | ||
| 39 | BUG_ON(blk->data[size - 1] != '\n'); | ||
| 40 | } | ||
| 41 | if (DEBUG > 2) { | ||
| 42 | BUG_ON(count_nl(blk->data, size) != blk->nl); | ||
| 43 | } | ||
| 44 | } | ||
| 45 | BUG_ON(!cursor_seen); | ||
| 46 | #else | ||
| 47 | // Silence "unused parameter" warnings | ||
| 48 | (void)view; | ||
| 49 | (void)check_newlines; | ||
| 50 | #endif | ||
| 51 | } | ||
| 52 | |||
| 53 | static size_t copy_count_nl(char *dst, const char *src, size_t len) | ||
| 54 | { | ||
| 55 | size_t nl = 0; | ||
| 56 | for (size_t i = 0; i < len; i++) { | ||
| 57 | dst[i] = src[i]; | ||
| 58 | if (src[i] == '\n') { | ||
| 59 | nl++; | ||
| 60 | } | ||
| 61 | } | ||
| 62 | return nl; | ||
| 63 | } | ||
| 64 | |||
| 65 | static size_t insert_to_current(BlockIter *cursor, const char *buf, size_t len) | ||
| 66 | { | ||
| 67 | Block *blk = cursor->blk; | ||
| 68 | size_t offset = cursor->offset; | ||
| 69 | size_t size = blk->size + len; | ||
| 70 | |||
| 71 | if (size > blk->alloc) { | ||
| 72 | blk->alloc = round_size_to_next_multiple(size, BLOCK_ALLOC_MULTIPLE); | ||
| 73 | xrenew(blk->data, blk->alloc); | ||
| 74 | } | ||
| 75 | memmove(blk->data + offset + len, blk->data + offset, blk->size - offset); | ||
| 76 | size_t nl = copy_count_nl(blk->data + offset, buf, len); | ||
| 77 | blk->nl += nl; | ||
| 78 | blk->size = size; | ||
| 79 | return nl; | ||
| 80 | } | ||
| 81 | |||
| 82 | /* | ||
| 83 | * Combine current block and new data into smaller blocks: | ||
| 84 | * - Block _must_ contain whole lines | ||
| 85 | * - Block _must_ contain at least one line | ||
| 86 | * - Preferred maximum size of block is BLOCK_EDIT_SIZE | ||
| 87 | * - Size of any block can be larger than BLOCK_EDIT_SIZE | ||
| 88 | * only if there's a very long line | ||
| 89 | */ | ||
| 90 | static size_t split_and_insert(BlockIter *cursor, const char *buf, size_t len) | ||
| 91 | { | ||
| 92 | Block *blk = cursor->blk; | ||
| 93 | ListHead *prev_node = blk->node.prev; | ||
| 94 | const char *buf1 = blk->data; | ||
| 95 | const char *buf2 = buf; | ||
| 96 | const char *buf3 = blk->data + cursor->offset; | ||
| 97 | size_t size1 = cursor->offset; | ||
| 98 | size_t size2 = len; | ||
| 99 | size_t size3 = blk->size - size1; | ||
| 100 | size_t total = size1 + size2 + size3; | ||
| 101 | size_t start = 0; // Beginning of new block | ||
| 102 | size_t size = 0; // Size of new block | ||
| 103 | size_t pos = 0; // Current position | ||
| 104 | size_t nl_added = 0; | ||
| 105 | |||
| 106 | while (start < total) { | ||
| 107 | // Size of new block if next line would be added | ||
| 108 | size_t new_size = 0; | ||
| 109 | size_t copied = 0; | ||
| 110 | |||
| 111 | if (pos < size1) { | ||
| 112 | const char *nl = memchr(buf1 + pos, '\n', size1 - pos); | ||
| 113 | if (nl) { | ||
| 114 | new_size = nl - buf1 + 1 - start; | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | if (!new_size && pos < size1 + size2) { | ||
| 119 | size_t offset = 0; | ||
| 120 | if (pos > size1) { | ||
| 121 | offset = pos - size1; | ||
| 122 | } | ||
| 123 | |||
| 124 | const char *nl = memchr(buf2 + offset, '\n', size2 - offset); | ||
| 125 | if (nl) { | ||
| 126 | new_size = size1 + nl - buf2 + 1 - start; | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | if (!new_size && pos < total) { | ||
| 131 | size_t offset = 0; | ||
| 132 | if (pos > size1 + size2) { | ||
| 133 | offset = pos - size1 - size2; | ||
| 134 | } | ||
| 135 | |||
| 136 | const char *nl = memchr(buf3 + offset, '\n', size3 - offset); | ||
| 137 | if (nl) { | ||
| 138 | new_size = size1 + size2 + nl - buf3 + 1 - start; | ||
| 139 | } else { | ||
| 140 | new_size = total - start; | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | if (new_size <= BLOCK_EDIT_SIZE) { | ||
| 145 | // Fits | ||
| 146 | size = new_size; | ||
| 147 | pos = start + new_size; | ||
| 148 | if (pos < total) { | ||
| 149 | continue; | ||
| 150 | } | ||
| 151 | } else { | ||
| 152 | // Does not fit | ||
| 153 | if (!size) { | ||
| 154 | // One block containing one very long line | ||
| 155 | size = new_size; | ||
| 156 | pos = start + new_size; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | BUG_ON(!size); | ||
| 161 | Block *new = block_new(size); | ||
| 162 | if (start < size1) { | ||
| 163 | size_t avail = size1 - start; | ||
| 164 | size_t count = MIN(size, avail); | ||
| 165 | new->nl += copy_count_nl(new->data, buf1 + start, count); | ||
| 166 | copied += count; | ||
| 167 | start += count; | ||
| 168 | } | ||
| 169 | if (start >= size1 && start < size1 + size2) { | ||
| 170 | size_t offset = start - size1; | ||
| 171 | size_t avail = size2 - offset; | ||
| 172 | size_t count = MIN(size - copied, avail); | ||
| 173 | new->nl += copy_count_nl(new->data + copied, buf2 + offset, count); | ||
| 174 | copied += count; | ||
| 175 | start += count; | ||
| 176 | } | ||
| 177 | if (start >= size1 + size2) { | ||
| 178 | size_t offset = start - size1 - size2; | ||
| 179 | size_t avail = size3 - offset; | ||
| 180 | size_t count = size - copied; | ||
| 181 | BUG_ON(count > avail); | ||
| 182 | new->nl += copy_count_nl(new->data + copied, buf3 + offset, count); | ||
| 183 | copied += count; | ||
| 184 | start += count; | ||
| 185 | } | ||
| 186 | |||
| 187 | new->size = size; | ||
| 188 | BUG_ON(copied != size); | ||
| 189 | list_add_before(&new->node, &blk->node); | ||
| 190 | |||
| 191 | nl_added += new->nl; | ||
| 192 | size = 0; | ||
| 193 | } | ||
| 194 | |||
| 195 | cursor->blk = BLOCK(prev_node->next); | ||
| 196 | while (cursor->offset > cursor->blk->size) { | ||
| 197 | cursor->offset -= cursor->blk->size; | ||
| 198 | cursor->blk = BLOCK(cursor->blk->node.next); | ||
| 199 | } | ||
| 200 | |||
| 201 | nl_added -= blk->nl; | ||
| 202 | block_free(blk); | ||
| 203 | return nl_added; | ||
| 204 | } | ||
| 205 | |||
| 206 | static size_t insert_bytes(BlockIter *cursor, const char *buf, size_t len) | ||
| 207 | { | ||
| 208 | // Blocks must contain whole lines. | ||
| 209 | // Last char of buf might not be newline. | ||
| 210 | block_iter_normalize(cursor); | ||
| 211 | |||
| 212 | Block *blk = cursor->blk; | ||
| 213 | size_t new_size = blk->size + len; | ||
| 214 | if (new_size <= blk->alloc || new_size <= BLOCK_EDIT_SIZE) { | ||
| 215 | return insert_to_current(cursor, buf, len); | ||
| 216 | } | ||
| 217 | |||
| 218 | if (blk->nl <= 1 && !memchr(buf, '\n', len)) { | ||
| 219 | // Can't split this possibly very long line. | ||
| 220 | // insert_to_current() is much faster than split_and_insert(). | ||
| 221 | return insert_to_current(cursor, buf, len); | ||
| 222 | } | ||
| 223 | return split_and_insert(cursor, buf, len); | ||
| 224 | } | ||
| 225 | |||
| 226 | void do_insert(View *view, const char *buf, size_t len) | ||
| 227 | { | ||
| 228 | Buffer *buffer = view->buffer; | ||
| 229 | size_t nl = insert_bytes(&view->cursor, buf, len); | ||
| 230 | buffer->nl += nl; | ||
| 231 | sanity_check_blocks(view, true); | ||
| 232 | |||
| 233 | view_update_cursor_y(view); | ||
| 234 | buffer_mark_lines_changed(buffer, view->cy, nl ? LONG_MAX : view->cy); | ||
| 235 | if (buffer->syn) { | ||
| 236 | hl_insert(buffer, view->cy, nl); | ||
| 237 | } | ||
| 238 | } | ||
| 239 | |||
| 240 | static bool only_block(const Buffer *buffer, const Block *blk) | ||
| 241 | { | ||
| 242 | return blk->node.prev == &buffer->blocks && blk->node.next == &buffer->blocks; | ||
| 243 | } | ||
| 244 | |||
| 245 | char *do_delete(View *view, size_t len, bool sanity_check_newlines) | ||
| 246 | { | ||
| 247 | ListHead *saved_prev_node = NULL; | ||
| 248 | Block *blk = view->cursor.blk; | ||
| 249 | size_t offset = view->cursor.offset; | ||
| 250 | size_t pos = 0; | ||
| 251 | size_t deleted_nl = 0; | ||
| 252 | |||
| 253 | if (!len) { | ||
| 254 | return NULL; | ||
| 255 | } | ||
| 256 | |||
| 257 | if (!offset) { | ||
| 258 | // The block where cursor is can become empty and thereby may be deleted | ||
| 259 | saved_prev_node = blk->node.prev; | ||
| 260 | } | ||
| 261 | |||
| 262 | Buffer *buffer = view->buffer; | ||
| 263 | char *deleted = xmalloc(len); | ||
| 264 | while (pos < len) { | ||
| 265 | ListHead *next = blk->node.next; | ||
| 266 | size_t avail = blk->size - offset; | ||
| 267 | size_t count = MIN(len - pos, avail); | ||
| 268 | size_t nl = copy_count_nl(deleted + pos, blk->data + offset, count); | ||
| 269 | if (count < avail) { | ||
| 270 | memmove ( | ||
| 271 | blk->data + offset, | ||
| 272 | blk->data + offset + count, | ||
| 273 | avail - count | ||
| 274 | ); | ||
| 275 | } | ||
| 276 | |||
| 277 | deleted_nl += nl; | ||
| 278 | buffer->nl -= nl; | ||
| 279 | blk->nl -= nl; | ||
| 280 | blk->size -= count; | ||
| 281 | if (!blk->size && !only_block(buffer, blk)) { | ||
| 282 | block_free(blk); | ||
| 283 | } | ||
| 284 | |||
| 285 | offset = 0; | ||
| 286 | pos += count; | ||
| 287 | blk = BLOCK(next); | ||
| 288 | |||
| 289 | BUG_ON(pos < len && next == &buffer->blocks); | ||
| 290 | } | ||
| 291 | |||
| 292 | if (saved_prev_node) { | ||
| 293 | // Cursor was at beginning of a block that was possibly deleted | ||
| 294 | if (saved_prev_node->next == &buffer->blocks) { | ||
| 295 | view->cursor.blk = BLOCK(saved_prev_node); | ||
| 296 | view->cursor.offset = view->cursor.blk->size; | ||
| 297 | } else { | ||
| 298 | view->cursor.blk = BLOCK(saved_prev_node->next); | ||
| 299 | } | ||
| 300 | } | ||
| 301 | |||
| 302 | blk = view->cursor.blk; | ||
| 303 | if ( | ||
| 304 | blk->size | ||
| 305 | && blk->data[blk->size - 1] != '\n' | ||
| 306 | && blk->node.next != &buffer->blocks | ||
| 307 | ) { | ||
| 308 | Block *next = BLOCK(blk->node.next); | ||
| 309 | size_t size = blk->size + next->size; | ||
| 310 | |||
| 311 | if (size > blk->alloc) { | ||
| 312 | blk->alloc = round_size_to_next_multiple(size, BLOCK_ALLOC_MULTIPLE); | ||
| 313 | xrenew(blk->data, blk->alloc); | ||
| 314 | } | ||
| 315 | memcpy(blk->data + blk->size, next->data, next->size); | ||
| 316 | blk->size = size; | ||
| 317 | blk->nl += next->nl; | ||
| 318 | block_free(next); | ||
| 319 | } | ||
| 320 | |||
| 321 | sanity_check_blocks(view, sanity_check_newlines); | ||
| 322 | |||
| 323 | view_update_cursor_y(view); | ||
| 324 | buffer_mark_lines_changed(buffer, view->cy, deleted_nl ? LONG_MAX : view->cy); | ||
| 325 | if (buffer->syn) { | ||
| 326 | hl_delete(buffer, view->cy, deleted_nl); | ||
| 327 | } | ||
| 328 | return deleted; | ||
| 329 | } | ||
| 330 | |||
| 331 | char *do_replace(View *view, size_t del, const char *buf, size_t ins) | ||
| 332 | { | ||
| 333 | block_iter_normalize(&view->cursor); | ||
| 334 | Block *blk = view->cursor.blk; | ||
| 335 | size_t offset = view->cursor.offset; | ||
| 336 | |||
| 337 | size_t avail = blk->size - offset; | ||
| 338 | if (del >= avail) { | ||
| 339 | goto slow; | ||
| 340 | } | ||
| 341 | |||
| 342 | size_t new_size = blk->size + ins - del; | ||
| 343 | if (new_size > BLOCK_EDIT_SIZE) { | ||
| 344 | // Should split | ||
| 345 | if (blk->nl > 1 || memchr(buf, '\n', ins)) { | ||
| 346 | // Most likely can be split | ||
| 347 | goto slow; | ||
| 348 | } | ||
| 349 | } | ||
| 350 | |||
| 351 | if (new_size > blk->alloc) { | ||
| 352 | blk->alloc = round_size_to_next_multiple(new_size, BLOCK_ALLOC_MULTIPLE); | ||
| 353 | xrenew(blk->data, blk->alloc); | ||
| 354 | } | ||
| 355 | |||
| 356 | // Modification is limited to one block | ||
| 357 | Buffer *buffer = view->buffer; | ||
| 358 | char *ptr = blk->data + offset; | ||
| 359 | char *deleted = xmalloc(del); | ||
| 360 | size_t del_nl = copy_count_nl(deleted, ptr, del); | ||
| 361 | blk->nl -= del_nl; | ||
| 362 | buffer->nl -= del_nl; | ||
| 363 | |||
| 364 | if (del != ins) { | ||
| 365 | memmove(ptr + ins, ptr + del, avail - del); | ||
| 366 | } | ||
| 367 | |||
| 368 | size_t ins_nl = copy_count_nl(ptr, buf, ins); | ||
| 369 | blk->nl += ins_nl; | ||
| 370 | buffer->nl += ins_nl; | ||
| 371 | blk->size = new_size; | ||
| 372 | sanity_check_blocks(view, true); | ||
| 373 | view_update_cursor_y(view); | ||
| 374 | |||
| 375 | // If the number of inserted and removed bytes are the same, some | ||
| 376 | // line(s) changed but the lines after them didn't move up or down | ||
| 377 | long max = (del_nl == ins_nl) ? view->cy + del_nl : LONG_MAX; | ||
| 378 | buffer_mark_lines_changed(buffer, view->cy, max); | ||
| 379 | |||
| 380 | if (buffer->syn) { | ||
| 381 | hl_delete(buffer, view->cy, del_nl); | ||
| 382 | hl_insert(buffer, view->cy, ins_nl); | ||
| 383 | } | ||
| 384 | |||
| 385 | return deleted; | ||
| 386 | |||
| 387 | slow: | ||
| 388 | // The "sanity_check_newlines" argument of do_delete() is false here | ||
| 389 | // because it may be removing a terminating newline that do_insert() | ||
| 390 | // is going to insert again at a different position: | ||
| 391 | deleted = do_delete(view, del, false); | ||
| 392 | do_insert(view, buf, ins); | ||
| 393 | return deleted; | ||
| 394 | } | ||
