buffer.c - ledit - Text editor (WIP)
HTML git clone git://lumidify.org/ledit.git (fast, but not encrypted)
HTML git clone https://lumidify.org/ledit.git (encrypted, but very slow)
HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ledit.git (over tor)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
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 }