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