Introduction
Introduction Statistics Contact Development Disclaimer Help
buffer.c - ledit - Text editor (WIP)
git clone git://lumidify.org/ledit.git (fast, but not encrypted)
git clone https://lumidify.org/ledit.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/…
Log
Files
Refs
README
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 …
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 se…
31 * but the '\0' is not included in 'len'.
32 * - When the line is not normalized, the '\0' is not always included! T…
33 * should maybe be changed for consistency, but for now, the resizing …
34 * always makes sure to add one so there's enough space when the text …
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…
50 * to byte position 'index'.
51 */
52 static void resize_and_move_text_gap(ledit_line *line, size_t min_size, …
53
54 /*
55 * Resize the line gap buffer so it can hold at least 'min_size' lines a…
56 * move the gap to line at position 'index'.
57 */
58 static void resize_and_move_line_gap(ledit_buffer *buffer, size_t min_si…
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…
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 int…
70 * This function does not update the views or normalize the lines, so it…
71 * only be used for efficiency purposes when performing multiple operati…
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 po…
82 * The text must not contain newlines.
83 * This function does not update the views or normalize the lines, so it…
84 * only be used for efficiency purposes when performing multiple operati…
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 po…
93 * The text may contain newlines.
94 * If end_line_ret is not NULL, the line index at the end of the inserti…
95 * written into it.
96 * If end_byte_ret is not NULL, the byte position at the end of the inse…
97 * written into it.
98 * This function does not update the views or normalize the lines, so it…
99 * only be used for efficiency purposes when performing multiple operati…
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 upda…
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…
123 * their line heights and offsets.
124 */
125 static void buffer_append_line_base(ledit_buffer *buffer, size_t line_in…
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…
133
134 /*
135 * Delete the section of line 'line' starting at byte 'start' with lengt…
136 * and notify the views.
137 * Note that this does not tell the views to recalculate their line heig…
138 */
139 static void buffer_delete_line_section_base(ledit_buffer *buffer, size_t…
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…
145 * - the range must be sorted already
146 */
147 static void buffer_copy_text(ledit_buffer *buffer, char *dst, int line1,…
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 …
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…
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 *li…
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_buff…
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, cha…
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…
275 size_t new_num = add_sz(buffer->views_num, 1);
276 buffer->views = ledit_reallocarray(buffer->views, new_num, sizeo…
277 buffer->views[buffer->views_num] = view_create(buffer, mode, lin…
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…
302 if (buffer->views_num == 0) {
303 free(buffer->views);
304 buffer->views = NULL;
305 } else {
306 buffer->views = ledit_reallocarray(buffer->views…
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…
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.…
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 **er…
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 a…
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 …
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_…
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 i…
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 …
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 t…
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, in…
587 }
588 }
589
590 /* FIXME: make these functions that call recalc* also be final as descri…
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_in…
622 buffer_insert_text_base(buffer, cur_line, cur_in…
623 cur_index = 0;
624 cur_line++;
625 /* handle \n\r or \r\n */
626 /* the two chars are compared to make sure that …
627 still handled as separate newlines */
628 if (cur_pos + 1 < len && text[cur_pos] != text[c…
629 (text[cur_pos + 1] == '\r' || text[cur_pos +…
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, tex…
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 …
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 …
679 static void
680 buffer_delete_line_entries_base(ledit_buffer *buffer, size_t index1, siz…
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->li…
694 if (min_size != buffer->lines_cap)
695 resize_and_move_line_gap(buffer, buffer->lines_num, buff…
696 for (size_t i = 0; i < buffer->views_num; i++) {
697 view_notify_delete_lines(buffer->views[i], index1, index…
698 }
699 }
700
701 ledit_line *
702 buffer_get_line_impl(ledit_buffer *buffer, size_t index, const char *fil…
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_…
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 …
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, …
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)…
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 nec…
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}_cha…
846 since this is essentially the same, just without the check for is_cur…
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 …
910 empty range in delete_range_base shouldn't be needed, but I added the…
911 static void
912 buffer_delete_line_section_base(ledit_buffer *buffer, size_t line, size_…
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, l…
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_index…
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…
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_…
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, siz…
1000 ledit_buffer *buffer = (ledit_buffer *)data;
1001 buffer_insert_text_with_newlines_base(buffer, line, byte, text, …
1002 }
1003
1004 static void
1005 undo_delete_helper(void *data, size_t line1, size_t byte1, size_t line2,…
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, siz…
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, siz…
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_index…
1053 delete_range_base(
1054 buffer, line_index1, byte_index1, line_index2, byte_index2, …
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, m…
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_inde…
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_gro…
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 }
You are viewing proxied material from lumidify.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.