buffer.c (33596B)
1 /* FIXME: maybe use separate unicode grapheme library so all functions 2 that need grapheme boundaries can be included here instead of in the views */ 3 4 #include <stdio.h> 5 #include <errno.h> 6 #include <string.h> 7 #include <limits.h> 8 #include <stdlib.h> 9 #include <unistd.h> 10 11 #include <X11/Xlib.h> 12 #include <X11/Xutil.h> 13 #include <X11/Xatom.h> 14 #include <pango/pangoxft.h> 15 #include <X11/extensions/Xdbe.h> 16 17 #include "util.h" 18 #include "undo.h" 19 #include "cache.h" 20 #include "memory.h" 21 #include "common.h" 22 #include "txtbuf.h" 23 #include "window.h" 24 #include "buffer.h" 25 #include "assert.h" 26 #include "pango-compat.h" 27 28 /* 29 * Important notes: 30 * - Lines must be null-terminated for some things to work (e.g. text search), 31 * but the '\0' is not included in 'len'. 32 * - When the line is not normalized, the '\0' is not always included! This 33 * should maybe be changed for consistency, but for now, the resizing just 34 * always makes sure to add one so there's enough space when the text is 35 * normalized. This is a bit ugly, but oh well. 36 */ 37 38 /* 39 * Note: "line gap buffer" refers to the gap buffer containing 40 * all lines, not to the gap buffer of the text in one line. 41 */ 42 43 /* 44 * Move the gap of the line gap buffer to 'index'. 45 */ 46 static void move_line_gap(ledit_buffer *buffer, size_t index); 47 48 /* 49 * Resize the line so it can hold at least 'min_size' bytes and move the gap 50 * to byte position 'index'. 51 */ 52 static void resize_and_move_text_gap(ledit_line *line, size_t min_size, size_t index); 53 54 /* 55 * Resize the line gap buffer so it can hold at least 'min_size' lines and 56 * move the gap to line at position 'index'. 57 */ 58 static void resize_and_move_line_gap(ledit_buffer *buffer, size_t min_size, size_t index); 59 60 /* 61 * Similar to strchr, but the length of the text is given separately 62 */ 63 static char *strchr_len(char *text, char c, size_t len); 64 65 /* 66 * Initialize a line with default values for its struct members. 67 */ 68 static void init_line(ledit_buffer *buffer, ledit_line *line); 69 70 /* 71 * Insert 'src_len' bytes from 'src_line' starting at byte position 'src_index' 72 * into line 'dst_line' at byte position 'dst_index'. 73 * 'dst_line' must not be the same as 'src_line'. 74 * If 'text_ret' is not NULL, the copied text is additionally copied into 'text_ret'. 75 * This function does not update the views or normalize the lines, so it should 76 * only be used for efficiency purposes when performing multiple operations. 77 */ 78 static void buffer_insert_text_from_line_base( 79 ledit_buffer *buffer, 80 size_t dst_line, size_t dst_index, 81 size_t src_line, size_t src_index, size_t src_len, 82 txtbuf *text_ret 83 ); 84 85 /* 86 * Insert text 'text' with length 'len' at line 'line_index' and byte position 'index'. 87 * The text must not contain newlines. 88 * This function does not update the views or normalize the lines, so it should 89 * only be used for efficiency purposes when performing multiple operations. 90 */ 91 static void buffer_insert_text_base( 92 ledit_buffer *buffer, 93 size_t line_index, size_t index, 94 char *text, size_t len 95 ); 96 97 /* Insert text 'text' with length 'len' at line 'line_index' and byte position 'index. 98 * The text may contain newlines. 99 * If end_line_ret is not NULL, the line index at the end of the insertion is 100 * written into it. 101 * If end_byte_ret is not NULL, the byte position at the end of the insertion is 102 * written into it. 103 * This function does not update the views or normalize the lines, so it should 104 * only be used for efficiency purposes when performing multiple operations. 105 */ 106 static void buffer_insert_text_with_newlines_base( 107 ledit_buffer *buffer, 108 size_t line_index, size_t index, 109 char *text, size_t len, 110 size_t *end_line_ret, size_t *end_char_ret 111 ); 112 113 /* 114 * Same as buffer_insert_text_with_newlines_base, but the views are updated afterwards. 115 */ 116 static void buffer_insert_text_with_newlines( 117 ledit_buffer *buffer, 118 size_t line_index, size_t index, 119 char *text, size_t len, 120 size_t *end_line_ret, size_t *end_char_ret 121 ); 122 123 /* 124 * Append line after line at 'line_index'. 125 * if 'break_text' is not 0, the text on line 'line_index' starting at 126 * byte index 'text_index' is moved to the newly appended line. 127 * The views are notified that a line has been appended, but not told to update 128 * their line heights and offsets. 129 */ 130 static void buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text); 131 132 /* 133 * Delete lines between 'index1' and 'index2' (inclusive). 134 * The views are notified of the deletion but not told to 135 * update their line heights and offsets. 136 */ 137 static void buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2); 138 139 /* 140 * Delete the section of line 'line' starting at byte 'start' with length 'length' 141 * and notify the views. 142 * Note that this does not tell the views to recalculate their line heights and offsets. 143 */ 144 static void buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length); 145 146 /* 147 * Copy text range into given buffer. 148 * - dst is null-terminated 149 * - dst must be large enough to contain the text and NUL (only use this together with buffer_textlen) 150 * - the range must be sorted already 151 */ 152 static void buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2); 153 154 static void marklist_destroy(ledit_buffer_marklist *marklist); 155 static ledit_buffer_marklist *marklist_create(void); 156 157 static void 158 marklist_destroy(ledit_buffer_marklist *marklist) { 159 for (size_t i = 0; i < marklist->len; i++) { 160 free(marklist->marks[i].text); 161 } 162 free(marklist->marks); 163 free(marklist); 164 } 165 166 void 167 buffer_insert_mark(ledit_buffer *buffer, char *mark, size_t len, size_t line, size_t byte) { 168 ledit_buffer_marklist *marklist = buffer->marklist; 169 for (size_t i = 0; i < marklist->len; i++) { 170 if (!strncmp(mark, marklist->marks[i].text, len)) { 171 marklist->marks[i].line = line; 172 marklist->marks[i].byte = byte; 173 return; 174 } 175 } 176 if (marklist->len == marklist->alloc) { 177 size_t new_alloc = ideal_array_size(marklist->alloc, add_sz(marklist->len, 1)); 178 marklist->marks = ledit_reallocarray( 179 marklist->marks, new_alloc, sizeof(ledit_buffer_mark) 180 ); 181 marklist->alloc = new_alloc; 182 } 183 ledit_buffer_mark *m = &marklist->marks[marklist->len]; 184 m->text = ledit_strndup(mark, len); 185 m->line = line; 186 m->byte = byte; 187 marklist->len++; 188 } 189 190 /* FIXME: check that byte is actually at grapheme boundary 191 (difficult because that can't currently be done by the buffer) */ 192 int 193 buffer_get_mark(ledit_buffer *buffer, char *mark, size_t len, size_t *line_ret, size_t *byte_ret) { 194 ledit_buffer_marklist *marklist = buffer->marklist; 195 int ret = 1; 196 for (size_t i = 0; i < marklist->len; i++) { 197 if (!strncmp(mark, marklist->marks[i].text, len)) { 198 ledit_line *ll; 199 ledit_buffer_mark *m = &marklist->marks[i]; 200 if (m->line >= buffer->lines_num) { 201 *line_ret = buffer->lines_num - 1; 202 ll = buffer_get_line(buffer, *line_ret); 203 *byte_ret = ll->len; 204 } else { 205 *line_ret = m->line; 206 ll = buffer_get_line(buffer, m->line); 207 if (m->byte >= ll->len) 208 *byte_ret = ll->len; 209 else 210 *byte_ret = m->byte; 211 } 212 ret = 0; 213 break; 214 } 215 } 216 return ret; 217 } 218 219 static ledit_buffer_marklist * 220 marklist_create(void) { 221 ledit_buffer_marklist *marklist = ledit_malloc(sizeof(ledit_buffer_marklist)); 222 marklist->len = marklist->alloc = 0; 223 marklist->marks = NULL; 224 return marklist; 225 } 226 227 ledit_buffer * 228 buffer_create(ledit_common *common, ledit_clipboard *clipboard) { 229 ledit_buffer *buffer = ledit_malloc(sizeof(ledit_buffer)); 230 buffer->common = common; 231 buffer->clipboard = clipboard; 232 buffer->undo = undo_stack_create(); 233 buffer->marklist = marklist_create(); 234 235 buffer->filename = NULL; 236 memset(&buffer->file_mtime, 0, sizeof(buffer->file_mtime)); 237 buffer->lines = NULL; 238 buffer->lines_num = 0; 239 buffer->lines_cap = 0; 240 buffer->lines_gap = 0; 241 buffer->views = NULL; 242 buffer->views_num = 0; 243 buffer->hard_line_based = 1; 244 buffer->modified = 0; 245 246 /* add one empty line to buffer */ 247 resize_and_move_line_gap(buffer, 1, 0); 248 buffer->lines_num++; 249 buffer->lines_gap++; 250 ledit_line *ll = buffer_get_line(buffer, 0); 251 init_line(buffer, ll); 252 253 return buffer; 254 } 255 256 void 257 buffer_lock_all_views_except(ledit_buffer *buffer, ledit_view *view, char *lock_text) { 258 for (size_t i = 0; i < buffer->views_num; i++) { 259 if (buffer->views[i] != view) { 260 view_lock(buffer->views[i], lock_text); 261 } 262 } 263 } 264 265 void 266 buffer_unlock_all_views(ledit_buffer *buffer) { 267 for (size_t i = 0; i < buffer->views_num; i++) { 268 view_unlock(buffer->views[i]); 269 } 270 } 271 272 void 273 buffer_set_hard_line_based(ledit_buffer *buffer, int hl) { 274 buffer->hard_line_based = hl; 275 } 276 277 /* FIXME: lock view if others are locked! */ 278 void 279 buffer_add_view(ledit_buffer *buffer, ledit_mode mode, size_t line, size_t pos, long scroll_offset) { 280 size_t new_num = add_sz(buffer->views_num, 1); 281 buffer->views = ledit_reallocarray(buffer->views, new_num, sizeof(ledit_view *)); 282 buffer->views[buffer->views_num] = view_create(buffer, mode, line, pos); 283 view_scroll(buffer->views[buffer->views_num], scroll_offset); 284 buffer->views_num = new_num; 285 } 286 287 /* FIXME: error checking */ 288 void 289 buffer_remove_view(ledit_buffer *buffer, ledit_view *view) { 290 size_t i = 0; 291 int found = 0; 292 for (; i < buffer->views_num; i++) { 293 if (buffer->views[i] == view) { 294 found = 1; 295 break; 296 } 297 } 298 if (found) { 299 view_destroy(buffer->views[i]); 300 memmove( 301 buffer->views + i, 302 buffer->views + i + 1, 303 (buffer->views_num - i - 1) * sizeof(ledit_view *) 304 ); 305 --buffer->views_num; 306 /* FIXME: use generic "vector" library to avoid problems like this */ 307 if (buffer->views_num == 0) { 308 free(buffer->views); 309 buffer->views = NULL; 310 } else { 311 buffer->views = ledit_reallocarray(buffer->views, buffer->views_num, sizeof(ledit_view *)); 312 } 313 } 314 } 315 316 void 317 buffer_recalc_all_views_from_line(ledit_buffer *buffer, size_t line) { 318 for (size_t i = 0; i < buffer->views_num; i++) { 319 view_recalc_from_line(buffer->views[i], line); 320 } 321 } 322 323 /* WARNING: errstr must be copied as soon as possible! */ 324 int 325 buffer_load_file(ledit_buffer *buffer, char *filename, size_t line, char **errstr) { 326 long len; 327 int off = 0; 328 ledit_line *ll; 329 char *file_contents; 330 FILE *file; 331 332 /* FIXME: https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file */ 333 file = fopen(filename, "r"); 334 if (!file) goto error; 335 if (fseek(file, 0, SEEK_END)) goto errorclose; 336 len = ftell(file); 337 if (len < 0) goto errorclose; 338 if (fseek(file, 0, SEEK_SET)) goto errorclose; 339 size_t lenz = add_sz((size_t)len, 2); 340 341 ll = buffer_get_line(buffer, line); 342 /* FIXME: insert in chunks instead of allocating huge buffer */ 343 file_contents = ledit_malloc(lenz); 344 /* mimic nvi (or at least the openbsd version) - if the line 345 is empty, insert directly, otherwise insert after the line */ 346 if (ll->len > 0) { 347 off = 1; 348 file_contents[0] = '\n'; 349 } 350 clearerr(file); 351 fread(file_contents + off, 1, (size_t)len, file); 352 if (ferror(file)) goto errorclose; 353 file_contents[len + off] = '\0'; 354 /* don't generate extra newline at end */ 355 if (len + off > 0 && file_contents[len + off - 1] == '\n') { 356 file_contents[len + off - 1] = '\0'; 357 len--; 358 } 359 if (fclose(file)) goto error; 360 361 buffer_insert_text_with_newlines( 362 buffer, line, ll->len, file_contents, len + off, NULL, NULL 363 ); 364 free(file_contents); 365 buffer->modified = 0; 366 return 0; 367 error: 368 if (errstr) 369 *errstr = strerror(errno); 370 return 1; 371 errorclose: 372 if (errstr) 373 *errstr = strerror(errno); 374 fclose(file); 375 return 1; 376 } 377 378 int 379 buffer_write_to_file(ledit_buffer *buffer, FILE *file, char **errstr) { 380 ledit_line *ll; 381 clearerr(file); 382 for (size_t i = 0; i < buffer->lines_num; i++) { 383 ll = buffer_get_line(buffer, i); 384 buffer_normalize_line(ll); 385 if (fprintf(file, "%s\n", ll->text) < 0) goto errorclose; 386 } 387 if (fclose(file)) goto error; 388 buffer->modified = 0; 389 return 0; 390 error: 391 if (errstr) 392 *errstr = strerror(errno); 393 return 1; 394 errorclose: 395 if (errstr) 396 *errstr = strerror(errno); 397 fclose(file); 398 return 1; 399 } 400 401 int 402 buffer_write_to_fd(ledit_buffer *buffer, int fd, char **errstr) { 403 FILE *file = fdopen(fd, "w"); 404 if (!file) goto error; 405 return buffer_write_to_file(buffer, file, errstr); 406 error: 407 if (errstr) 408 *errstr = strerror(errno); 409 /* catching errors on the close wouldn't 410 really make much sense anymore */ 411 close(fd); 412 return 1; 413 } 414 415 /* FIXME: allow to write only certain lines */ 416 int 417 buffer_write_to_filename(ledit_buffer *buffer, char *filename, char **errstr) { 418 FILE *file = fopen(filename, "w"); 419 if (!file) goto error; 420 return buffer_write_to_file(buffer, file, errstr); 421 error: 422 if (errstr) 423 *errstr = strerror(errno); 424 return 1; 425 } 426 427 void 428 buffer_destroy(ledit_buffer *buffer) { 429 ledit_line *l; 430 for (size_t i = 0; i < buffer->lines_num; i++) { 431 l = buffer_get_line(buffer, i); 432 free(l->text); 433 } 434 undo_stack_destroy(buffer->undo); 435 free(buffer->lines); 436 if (buffer->filename) 437 free(buffer->filename); 438 marklist_destroy(buffer->marklist); 439 for (size_t i = 0; i < buffer->views_num; i++) { 440 view_destroy(buffer->views[i]); 441 } 442 free(buffer->views); 443 free(buffer); 444 } 445 446 void 447 buffer_normalize_line(ledit_line *line) { 448 if (line->gap < line->len) { 449 memmove( 450 line->text + line->gap, 451 line->text + line->gap + line->cap - line->len, 452 line->len - line->gap 453 ); 454 line->gap = line->len; 455 } 456 /* this should never happen because the functions always 457 make sure to allocate one more for the '\0' */ 458 ledit_assert(line->len < line->cap); 459 line->text[line->len] = '\0'; 460 } 461 462 /* FIXME: To simplify this a bit, maybe just copy text to txtbuf first and 463 then insert it in one go instead of having this complex logic */ 464 /* FIXME: check if there can be bugs when a newline is inserted in some way 465 other than pasting or pressing enter */ 466 467 static void 468 buffer_insert_text_from_line_base( 469 ledit_buffer *buffer, 470 size_t dst_line, size_t dst_index, 471 size_t src_line, size_t src_index, size_t src_len, 472 txtbuf *text_ret) { 473 ledit_assert(dst_line != src_line); 474 ledit_line *ll = buffer_get_line(buffer, src_line); 475 ledit_assert(add_sz(src_index, src_len) <= ll->len); 476 if (text_ret != NULL) { 477 txtbuf_resize(text_ret, src_len); 478 text_ret->len = src_len; 479 } 480 if (src_index >= ll->gap) { 481 /* all text to insert is after gap */ 482 buffer_insert_text_base( 483 buffer, dst_line, dst_index, 484 ll->text + src_index + ll->cap - ll->len, src_len 485 ); 486 if (text_ret != NULL) { 487 memcpy( 488 text_ret->text, 489 ll->text + src_index + ll->cap - ll->len, 490 src_len 491 ); 492 } 493 } else if (ll->gap - src_index >= src_len) { 494 /* all text to insert is before gap */ 495 buffer_insert_text_base( 496 buffer, dst_line, dst_index, 497 ll->text + src_index, src_len 498 ); 499 if (text_ret != NULL) { 500 memcpy( 501 text_ret->text, 502 ll->text + src_index, 503 src_len 504 ); 505 } 506 } else { 507 /* insert part of text before gap */ 508 buffer_insert_text_base( 509 buffer, dst_line, dst_index, 510 ll->text + src_index, ll->gap - src_index 511 ); 512 /* insert part of text after gap */ 513 buffer_insert_text_base( 514 buffer, dst_line, dst_index + ll->gap - src_index, 515 ll->text + ll->gap + ll->cap - ll->len, 516 src_len - ll->gap + src_index 517 ); 518 if (text_ret != NULL) { 519 memcpy( 520 text_ret->text, 521 ll->text + src_index, 522 ll->gap - src_index 523 ); 524 memcpy( 525 text_ret + ll->gap - src_index, 526 ll->text + ll->gap + ll->cap - ll->len, 527 src_len - ll->gap + src_index 528 ); 529 } 530 531 } 532 for (size_t i = 0; i < buffer->views_num; i++) { 533 view_notify_insert_text(buffer->views[i], dst_line, dst_index, src_len); 534 } 535 } 536 537 static void 538 move_line_gap(ledit_buffer *buffer, size_t index) { 539 move_gap( 540 buffer->lines, sizeof(ledit_line), index, 541 buffer->lines_gap, buffer->lines_cap, buffer->lines_num, 542 &buffer->lines_gap 543 ); 544 } 545 546 /* FIXME: add "final" versions of the functions that include the 547 normalization, i.e. if they have to move the gap anyways, they 548 just move it to the end */ 549 550 static void 551 resize_and_move_text_gap(ledit_line *line, size_t min_size, size_t index) { 552 /* yes, I know sizeof(char) == 1 anyways */ 553 line->text = resize_and_move_gap( 554 line->text, sizeof(char), 555 line->gap, line->cap, line->len, 556 min_size, index, 557 &line->gap, &line->cap 558 ); 559 } 560 561 static void 562 resize_and_move_line_gap(ledit_buffer *buffer, size_t min_size, size_t index) { 563 buffer->lines = resize_and_move_gap( 564 buffer->lines, sizeof(ledit_line), 565 buffer->lines_gap, buffer->lines_cap, buffer->lines_num, 566 min_size, index, 567 &buffer->lines_gap, &buffer->lines_cap 568 ); 569 } 570 571 static void 572 buffer_insert_text_base(ledit_buffer *buffer, size_t line_index, size_t index, char *text, size_t len) { 573 ledit_line *line = buffer_get_line(buffer, line_index); 574 ledit_assert(index <= line->len); 575 /* \0 is not included in line->len */ 576 resize_and_move_text_gap(line, add_sz3(line->len, len, 1), index); 577 /* the gap is now located at 'index' and at least large enough to hold the new text */ 578 memmove(line->text + index, text, len); 579 line->gap += len; 580 line->len += len; 581 for (size_t i = 0; i < buffer->views_num; i++) { 582 view_notify_insert_text(buffer->views[i], line_index, index, len); 583 } 584 } 585 586 /* FIXME: this isn't optimized like the standard version, but whatever */ 587 static char * 588 strchr_len(char *text, char c, size_t len) { 589 for (size_t i = 0; i < len; i++) { 590 if (text[i] == c) 591 return text + i; 592 } 593 return NULL; 594 } 595 596 /* FIXME: make these functions that call recalc* also be final as described above */ 597 static void 598 buffer_insert_text_with_newlines( 599 ledit_buffer *buffer, 600 size_t line_index, size_t index, 601 char *text, size_t len, 602 size_t *end_line_ret, size_t *end_byte_ret) { 603 size_t end; 604 buffer_insert_text_with_newlines_base( 605 buffer, line_index, index, text, len, 606 &end, end_byte_ret 607 ); 608 if (end_line_ret) 609 *end_line_ret = end; 610 if (line_index == end) 611 buffer_recalc_line(buffer, line_index); 612 else 613 buffer_recalc_from_line(buffer, line_index); 614 } 615 616 /* FIXME: also look for \r */ 617 static void 618 buffer_insert_text_with_newlines_base( 619 ledit_buffer *buffer, 620 size_t line_index, size_t index, 621 char *text, size_t len, 622 size_t *end_line_ret, size_t *end_byte_ret) { 623 size_t rem_len = len; 624 char *cur, *last = text; 625 size_t cur_line = line_index; 626 size_t cur_index = index; 627 /* FIXME: strchr_len isn't really needed when the lines are normalized anyways */ 628 while ((cur = strchr_len(last, '\n', rem_len)) != NULL) { 629 /* FIXME: this is probably inefficient, but I don't have time to 630 think about it right now */ 631 buffer_append_line_base(buffer, cur_line, cur_index, 1); 632 buffer_insert_text_base(buffer, cur_line, cur_index, last, cur - last); 633 cur_index = 0; 634 cur_line++; 635 rem_len -= cur - last + 1; 636 last = cur + 1; 637 } 638 /* FIXME: check how legal this casting between pointers and size_t's is */ 639 buffer_insert_text_base(buffer, cur_line, cur_index, last, text + len - last); 640 if (end_line_ret) 641 *end_line_ret = cur_line; 642 if (end_byte_ret) 643 *end_byte_ret = cur_index + text + len - last; 644 } 645 646 static void 647 init_line(ledit_buffer *buffer, ledit_line *line) { 648 line->parent_buffer = buffer; 649 line->gap = 0; 650 line->cap = 2; /* arbitrary */ 651 line->text = ledit_malloc(line->cap); 652 line->text[0] = '\0'; 653 line->len = 0; 654 } 655 656 static void 657 buffer_append_line_base(ledit_buffer *buffer, size_t line_index, size_t text_index, int break_text) { 658 size_t new_len = add_sz(buffer->lines_num, 1); 659 size_t insert_index = add_sz(line_index, 1); 660 resize_and_move_line_gap(buffer, new_len, insert_index); 661 buffer->lines_num++; 662 buffer->lines_gap++; 663 ledit_line *new_l = buffer_get_line(buffer, line_index + 1); 664 init_line(buffer, new_l); 665 for (size_t i = 0; i < buffer->views_num; i++) { 666 view_notify_append_line(buffer->views[i], line_index); 667 } 668 if (break_text) { 669 ledit_line *l = buffer_get_line(buffer, line_index); 670 buffer_insert_text_from_line_base( 671 buffer, line_index + 1, 0, 672 line_index, text_index, l->len - text_index, NULL 673 ); 674 buffer_delete_line_section_base( 675 buffer, line_index, 676 text_index, l->len - text_index 677 ); 678 } 679 } 680 681 /* IMPORTANT: buffer_recalc_from_line needs to be called sometime after this! */ 682 static void 683 buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, size_t index2) { 684 ledit_line *l; 685 ledit_assert(index2 >= index1); 686 /* it isn't allowed to delete all lines */ 687 ledit_assert(index2 - index1 != buffer->lines_num); 688 for (size_t i = index1; i <= index2; i++) { 689 l = buffer_get_line(buffer, i); 690 free(l->text); 691 } 692 move_line_gap(buffer, index1); 693 buffer->lines_num -= index2 - index1 + 1; 694 /* possibly decrease size of array - this needs to be after 695 actually deleting the lines so the length is already less */ 696 size_t min_size = ideal_array_size(buffer->lines_cap, buffer->lines_num); 697 if (min_size != buffer->lines_cap) 698 resize_and_move_line_gap(buffer, buffer->lines_num, buffer->lines_gap); 699 for (size_t i = 0; i < buffer->views_num; i++) { 700 view_notify_delete_lines(buffer->views[i], index1, index2); 701 } 702 } 703 704 ledit_line * 705 buffer_get_line(ledit_buffer *buffer, size_t index) { 706 ledit_assert(index < buffer->lines_num); 707 return index < buffer->lines_gap ? 708 &buffer->lines[index] : 709 &buffer->lines[index + buffer->lines_cap - buffer->lines_num]; 710 } 711 712 void 713 buffer_recalc_line(ledit_buffer *buffer, size_t line) { 714 for (size_t i = 0; i < buffer->views_num; i++) { 715 view_recalc_line(buffer->views[i], line); 716 } 717 } 718 719 void 720 buffer_recalc_from_line(ledit_buffer *buffer, size_t line) { 721 for (size_t i = 0; i < buffer->views_num; i++) { 722 view_recalc_from_line(buffer->views[i], line); 723 } 724 } 725 726 void 727 buffer_recalc_all_lines(ledit_buffer *buffer) { 728 for (size_t i = 0; i < buffer->views_num; i++) { 729 view_recalc_all_lines(buffer->views[i]); 730 } 731 } 732 733 size_t 734 buffer_textlen(ledit_buffer *buffer, size_t line1, size_t byte1, size_t line2, size_t byte2) { 735 ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); 736 size_t len = 0; 737 ledit_line *ll = buffer_get_line(buffer, line1); 738 ledit_line *ll2 = buffer_get_line(buffer, line2); 739 ledit_assert(byte1 <= ll->len); 740 ledit_assert(byte2 <= ll2->len); 741 if (line1 == line2) { 742 len = byte2 - byte1; 743 } else { 744 /* + 1 for newline */ 745 len = add_sz3(ll->len - byte1, byte2, 1); 746 for (size_t i = line1 + 1; i < line2; i++) { 747 ll = buffer_get_line(buffer, i); 748 /* ll->len + 1 should be valid anyways 749 because there *should* always be 750 space for '\0' at the end, i.e. ll->cap 751 should be at least ll->len + 1 */ 752 /* FIXME: also, this overflow checking is 753 probably completely useless (it definitely 754 is really ugly) */ 755 len += add_sz(ll->len, 1); 756 } 757 } 758 return len; 759 } 760 761 /* FIXME: Only copy new text in selection when expanding it */ 762 /* FIXME: Work directly with gap buffer instead of normalizing first 763 -> I guess it doesn't matter right now because copying text is 764 only done when it is re-rendered (and thus normalized because 765 of pango's requirements). If a more efficient rendering 766 backend is added, it would be good to optimize this, though. */ 767 static void 768 buffer_copy_text(ledit_buffer *buffer, char *dst, int line1, int byte1, int line2, int byte2) { 769 ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); 770 ledit_line *ll1 = buffer_get_line(buffer, line1); 771 ledit_line *ll2 = buffer_get_line(buffer, line2); 772 buffer_normalize_line(ll1); 773 if (line1 == line2) { 774 memcpy(dst, ll1->text + byte1, byte2 - byte1); 775 dst[byte2 - byte1] = '\0'; 776 } else { 777 size_t cur_pos = 0; 778 memcpy(dst, ll1->text + byte1, ll1->len - byte1); 779 cur_pos += ll1->len - byte1; 780 dst[cur_pos] = '\n'; 781 cur_pos++; 782 for (int i = line1 + 1; i < line2; i++) { 783 ledit_line *ll = buffer_get_line(buffer, i); 784 buffer_normalize_line(ll); 785 memcpy(dst + cur_pos, ll->text, ll->len); 786 cur_pos += ll->len; 787 dst[cur_pos] = '\n'; 788 cur_pos++; 789 } 790 buffer_normalize_line(ll2); 791 memcpy(dst + cur_pos, ll2->text, byte2); 792 cur_pos += byte2; 793 dst[cur_pos] = '\0'; 794 } 795 } 796 797 void 798 buffer_copy_text_to_txtbuf( 799 ledit_buffer *buffer, 800 txtbuf *buf, 801 size_t line1, size_t byte1, 802 size_t line2, size_t byte2) { 803 ledit_assert(line1 < line2 || (line1 == line2 && byte1 <= byte2)); 804 size_t len = buffer_textlen(buffer, line1, byte1, line2, byte2); 805 txtbuf_resize(buf, len); 806 buffer_copy_text(buffer, buf->text, line1, byte1, line2, byte2); 807 buf->len = len; 808 } 809 810 /* get char with logical index i from line */ 811 #define LINE_CHAR(line, i) ((i) < (line)->gap ? (line)->text[i] : (line)->text[i + (line)->cap - (line)->len]) 812 813 size_t 814 line_prev_utf8(ledit_line *line, size_t index) { 815 if (index == 0) 816 return 0; 817 size_t i = index - 1; 818 /* find valid utf8 char - this probably needs to be improved */ 819 while (i > 0 && ((LINE_CHAR(line, i) & 0xC0) == 0x80)) 820 i--; 821 return i; 822 } 823 824 size_t 825 line_next_utf8(ledit_line *line, size_t index) { 826 if (index >= line->len) 827 return line->len; 828 size_t i = index + 1; 829 while (i < line->len && ((LINE_CHAR(line, i) & 0xC0) == 0x80)) 830 i++; 831 return i; 832 } 833 834 /* Warning: this is very inefficient! */ 835 /* FIXME: Any way to optimize this? */ 836 size_t 837 line_byte_to_char(ledit_line *line, size_t byte) { 838 size_t c = 0; 839 size_t i = 0; 840 size_t b = byte > line->len ? line->len : byte; /* maybe not necessary */ 841 while (i < b) { 842 c++; 843 i = line_next_utf8(line, i); 844 } 845 return c; 846 } 847 848 /* FIXME: It might make more sense to add a flag to view_{next,prev}_char_pos 849 since this is essentially the same, just without the check for is_cursor_position */ 850 void 851 buffer_next_char_pos( 852 ledit_buffer *buffer, 853 size_t line, size_t byte, 854 int num, int multiline, 855 size_t *line_ret, size_t *byte_ret) { 856 ledit_line *ll = buffer_get_line(buffer, line); 857 size_t c = line_byte_to_char(ll, byte); 858 size_t cur_byte = byte; 859 for (int i = 0; i < num; i++) { 860 if (cur_byte >= ll->len) { 861 if (multiline && line < buffer->lines_num - 1) { 862 line++; 863 ll = buffer_get_line(buffer, line); 864 c = 0; 865 cur_byte = 0; 866 i++; 867 continue; 868 } else { 869 break; 870 } 871 } 872 cur_byte = line_next_utf8(ll, cur_byte); 873 c++; 874 } 875 if (line_ret) 876 *line_ret = line; 877 if (byte_ret) 878 *byte_ret = cur_byte <= ll->len ? cur_byte : ll->len; 879 } 880 881 void 882 buffer_prev_char_pos( 883 ledit_buffer *buffer, 884 size_t line, size_t byte, 885 int num, int multiline, 886 size_t *line_ret, size_t *byte_ret) { 887 ledit_line *ll = buffer_get_line(buffer, line); 888 size_t c = line_byte_to_char(ll, byte); 889 size_t cur_byte = byte; 890 for (int i = 0; i < num; i++) { 891 if (cur_byte == 0) { 892 if (multiline && line > 0) { 893 line--; 894 ll = buffer_get_line(buffer, line); 895 c = line_byte_to_char(ll, ll->len); 896 cur_byte = ll->len; 897 i++; 898 continue; 899 } else { 900 break; 901 } 902 } 903 cur_byte = line_prev_utf8(ll, cur_byte); 904 c--; 905 } 906 if (line_ret) 907 *line_ret = line; 908 if (byte_ret) 909 *byte_ret = cur_byte; 910 } 911 912 /* The check for length == 0 in buffer_delete_line_section_base and the check for 913 empty range in delete_range_base shouldn't be needed, but I added them just in case. */ 914 static void 915 buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_t start, size_t length) { 916 if (length == 0) 917 return; 918 ledit_line *l = buffer_get_line(buffer, line); 919 /* FIXME: somehow make sure this doesn't get optimized out? */ 920 (void)add_sz(start, length); /* just check that no overflow */ 921 ledit_assert(start + length <= l->len); 922 if (start <= l->gap && start + length >= l->gap) { 923 /* range includes gap */ 924 l->gap = start; 925 } else if (start < l->gap && start + length <= l->gap) { 926 /* entire range is before gap */ 927 memmove( 928 l->text + l->cap - l->len + start + length, 929 l->text + start + length, 930 l->gap - start - length 931 ); 932 l->gap = start; 933 } else { 934 /* entire range is after gap */ 935 /* move piece between end of gap and 936 start of range to beginning of gap */ 937 memmove( 938 l->text + l->gap, 939 l->text + l->gap + l->cap - l->len, 940 start - l->gap 941 ); 942 l->gap += start - l->gap; 943 } 944 l->len -= length; 945 /* possibly decrease size of line */ 946 size_t cap = ideal_array_size(l->cap, add_sz(l->len, 1)); 947 if (cap != l->cap) 948 resize_and_move_text_gap(l, l->len + 1, l->gap); 949 for (size_t i = 0; i < buffer->views_num; i++) { 950 view_notify_delete_text(buffer->views[i], line, start, length); 951 } 952 } 953 954 static void 955 delete_range_base( 956 ledit_buffer *buffer, 957 size_t line_index1, size_t byte_index1, 958 size_t line_index2, size_t byte_index2, 959 txtbuf *text_ret) { 960 if (line_index1 == line_index2 && byte_index1 == byte_index2) 961 return; 962 sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2); 963 if (line_index1 == line_index2) { 964 if (text_ret) { 965 buffer_copy_text_to_txtbuf( 966 buffer, text_ret, 967 line_index1, byte_index1, 968 line_index2, byte_index2 969 ); 970 } 971 buffer_delete_line_section_base( 972 buffer, line_index1, byte_index1, byte_index2 - byte_index1 973 ); 974 } else { 975 if (text_ret) { 976 buffer_copy_text_to_txtbuf( 977 buffer, text_ret, 978 line_index1, byte_index1, 979 line_index2, byte_index2 980 ); 981 } 982 ledit_line *line1 = buffer_get_line(buffer, line_index1); 983 ledit_line *line2 = buffer_get_line(buffer, line_index2); 984 ledit_assert(byte_index1 <= line1->len); 985 ledit_assert(byte_index2 <= line2->len); 986 buffer_delete_line_section_base( 987 buffer, line_index1, byte_index1, line1->len - byte_index1 988 ); 989 buffer_insert_text_from_line_base( 990 buffer, 991 line_index1, byte_index1, 992 line_index2, byte_index2, 993 line2->len - byte_index2, NULL 994 ); 995 buffer_delete_line_entries_base( 996 buffer, line_index1 + 1, line_index2 997 ); 998 } 999 } 1000 1001 static void 1002 undo_insert_helper(void *data, size_t line, size_t byte, char *text, size_t text_len) { 1003 ledit_buffer *buffer = (ledit_buffer *)data; 1004 buffer_insert_text_with_newlines_base(buffer, line, byte, text, text_len, NULL, NULL); 1005 } 1006 1007 static void 1008 undo_delete_helper(void *data, size_t line1, size_t byte1, size_t line2, size_t byte2) { 1009 ledit_buffer *buffer = (ledit_buffer *)data; 1010 delete_range_base(buffer, line1, byte1, line2, byte2, NULL); 1011 } 1012 1013 undo_status 1014 buffer_undo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_line, size_t *cur_byte) { 1015 size_t min_line; 1016 undo_status s = ledit_undo( 1017 buffer->undo, mode, buffer, &undo_insert_helper, 1018 &undo_delete_helper, cur_line, cur_byte, &min_line 1019 ); 1020 if (min_line < buffer->lines_num) { 1021 buffer_recalc_all_views_from_line( 1022 buffer, min_line > 0 ? min_line - 1 : min_line 1023 ); 1024 } 1025 buffer->modified = 1; 1026 return s; 1027 } 1028 1029 undo_status 1030 buffer_redo(ledit_buffer *buffer, ledit_mode mode, size_t *cur_line, size_t *cur_byte) { 1031 size_t min_line; 1032 undo_status s = ledit_redo( 1033 buffer->undo, mode, buffer, &undo_insert_helper, 1034 &undo_delete_helper, cur_line, cur_byte, &min_line 1035 ); 1036 if (min_line < buffer->lines_num) { 1037 buffer_recalc_all_views_from_line( 1038 buffer, min_line > 0 ? min_line - 1 : min_line 1039 ); 1040 } 1041 buffer->modified = 1; 1042 return s; 1043 } 1044 1045 void 1046 buffer_delete_with_undo_base( 1047 ledit_buffer *buffer, ledit_range cur_range, 1048 int start_undo_group, ledit_mode mode, /* for undo */ 1049 size_t line_index1, size_t byte_index1, 1050 size_t line_index2, size_t byte_index2, 1051 txtbuf *text_ret) { 1052 /* FIXME: global txtbuf to avoid allocating each time */ 1053 /* actually, could just use stack variable here */ 1054 txtbuf *buf = text_ret != NULL ? text_ret : txtbuf_new(); 1055 sort_range(&line_index1, &byte_index1, &line_index2, &byte_index2); 1056 delete_range_base( 1057 buffer, line_index1, byte_index1, line_index2, byte_index2, buf 1058 ); 1059 ledit_range del_range = { 1060 .line1 = line_index1, .byte1 = byte_index1, 1061 .line2 = line_index2, .byte2 = byte_index2 1062 }; 1063 undo_push_delete( 1064 buffer->undo, buf, del_range, cur_range, start_undo_group, mode 1065 ); 1066 if (text_ret == NULL) 1067 txtbuf_destroy(buf); 1068 buffer->modified = 1; 1069 } 1070 1071 void 1072 buffer_delete_with_undo( 1073 ledit_buffer *buffer, ledit_range cur_range, 1074 int start_undo_group, ledit_mode mode, /* for undo */ 1075 size_t line_index1, size_t byte_index1, 1076 size_t line_index2, size_t byte_index2, 1077 txtbuf *text_ret) { 1078 buffer_delete_with_undo_base( 1079 buffer, cur_range, 1080 start_undo_group, mode, 1081 line_index1, byte_index1, 1082 line_index2, byte_index2, 1083 text_ret 1084 ); 1085 size_t min = line_index1 < line_index2 ? line_index1 : line_index2; 1086 buffer_recalc_all_views_from_line(buffer, min); 1087 } 1088 1089 void 1090 buffer_insert_with_undo_base( 1091 ledit_buffer *buffer, 1092 ledit_range cur_range, int set_range_end, 1093 int start_undo_group, ledit_mode mode, 1094 size_t line, size_t byte, 1095 char *text, size_t len, 1096 size_t *line_ret, size_t *byte_ret) { 1097 txtbuf ins_buf = {.text = text, .len = len, .cap = len}; 1098 ledit_range ins_range; 1099 ins_range.line1 = line; 1100 ins_range.byte1 = byte; 1101 size_t new_line, new_byte; 1102 buffer_insert_text_with_newlines_base( 1103 buffer, line, byte, text, len, 1104 &new_line, &new_byte 1105 ); 1106 if (set_range_end) { 1107 cur_range.line2 = new_line; 1108 cur_range.byte2 = new_byte; 1109 } 1110 ins_range.line2 = new_line; 1111 ins_range.byte2 = new_byte; 1112 undo_push_insert( 1113 buffer->undo, &ins_buf, ins_range, cur_range, start_undo_group, mode 1114 ); 1115 if (line_ret != NULL) 1116 *line_ret = new_line; 1117 if (byte_ret != NULL) 1118 *byte_ret = new_byte; 1119 buffer->modified = 1; 1120 } 1121 1122 void 1123 buffer_insert_with_undo( 1124 ledit_buffer *buffer, 1125 ledit_range cur_range, int set_range_end, 1126 int start_undo_group, ledit_mode mode, 1127 size_t line, size_t byte, 1128 char *text, size_t len, 1129 size_t *line_ret, size_t *byte_ret) { 1130 buffer_insert_with_undo_base( 1131 buffer, 1132 cur_range, set_range_end, 1133 start_undo_group, mode, 1134 line, byte, text, len, 1135 line_ret, byte_ret 1136 ); 1137 buffer_recalc_all_views_from_line(buffer, line); 1138 }