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 } |