2 * Copyright Neil Brown ©2015 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * Generic text document.
7 * This allows for a file to be read in, and edited by creating
8 * a linked list of chunks of text - the original isn't changed.
9 * This means that pointers outside of the edit region are mostly
12 * Indefinite undo is kept as a record of changes to the list of chunks.
13 * New text is added to the end of a list of allocations.
17 * The text of a document is stored in a collection of allocations
18 * which are immutable once created. They are attached to the 'document'
19 * and freed only when the document is discard.
20 * The current state of the document is represented by a linked list of
21 * 'chunks' which each point to part of some allocation.
23 * Each chunk can have 'attributes' which add arbitrary annotations to
24 * ranges of text. Unlike the text itself, these are not immutable. Only
25 * the 'current' attributes are stored. It is assumed that following
26 * 'undo', the appropriate attributes can be re-computed. i.e. they are
29 * When text is removed from a document, the 'chunk' is modified to
30 * reference less text. If the chunk becomes empty, it is discarded.
31 * When text is added to a document a new chunk is created which
32 * points to the next free space in the latest allocation, and text is
33 * added there. If the text is being added to the end of a chunk and
34 * it already points to the end of the latest allocation, then no new
37 * Text is assumed to be UTF-8 encoded. This becomes relevant when adding
38 * a string to the document, and it wont all fit in the current allocation.
39 * In that case we ensure the first byte that goes in the next allocation
40 * matches 0xxxxxxx or 11xxxxxx., not 10xxxxxx.
42 * Undo/redo information is stored as a list of edits. Each edit
43 * changes either the start of the end of a 'chunk'. When a chunk becomes
44 * empty it is removed from the chunk list. The 'prev' pointer is preserved
45 * so when an undo make it non-empty, it knows where to be added back.
47 * A text always has a least one allocation which is created with the text.
48 * If the text is empty, there will not be any chunks though, so all text refs
49 * will point to NULL. The NULL chunk is at the end of the text.
50 * The ->txt pointer of chunk never changes. It is set when the chunk is created
51 * and then only start and end are changed.
55 #define _GNU_SOURCE /* for asprintf */
65 /* A doc_ref is treated as a pointer to a chunk, and an offset
66 * from the start of 'txt'. So 'o' must be between c->start and
69 #define PRIVATE_DOC_REF
79 /* text is allocated is large blocks - possibly a whole
80 * file or some other large unit being added to a document.
81 * For small additions (normal typing) the default allocation
83 * When more is allocated than needed, extra can be added on to
84 * the end - 'free' is the next index with free space.
87 struct text_alloc *prev;
93 #define DEFAULT_SIZE (4096 - sizeof(struct text_alloc))
95 /* The text document is a list of text_chunk.
96 * The 'txt' pointer is the text[] of a text_alloc.
97 * 'start' and 'end' narrow that.
103 struct list_head lst;
104 struct attrset *attrs;
107 /* An 'edit' consists of one or more text_edit structs linked together.
108 * The 'first' text_edit in a group has 'first' set. So when popping
109 * off the 'undo' list, we pop until we find the 'first' one. When
110 * popping of the 'redo' list, we pop a first, then any following
112 * Each entry identifies a chunk. If 'at_start' is set, the 'len' is
113 * added to the 'start' pointer (subtracted for undo). Otherwise
114 * the len added to the end. If the resulting length is zero, the
115 * chunk is removed from the list.
118 struct text_chunk *target;
119 struct text_edit *next;
122 int len:30; // bytes add, -ve for removed.
125 /* A text document is a document with allocations, a list
126 * of chunks, and some undo info.
131 struct text_alloc *alloc;
132 struct list_head text;
133 struct text_edit *undo, *redo;
139 static int text_advance_towards(struct text *t, struct doc_ref *ref, struct doc_ref *target);
140 static int text_retreat_towards(struct text *t, struct doc_ref *ref, struct doc_ref *target);
141 static int text_ref_same(struct text *t, struct doc_ref *r1, struct doc_ref *r2);
142 static int text_locate(struct text *t, struct doc_ref *r, struct doc_ref *dest);
143 static void text_check_consistent(struct text *t);
145 static struct map *text_map;
147 * A text will mostly hold utf-8 so we try to align chunk breaks with
148 * Unicode points. This particularly affects adding new strings to
150 * There is no guarantee that a byte string is UTF-8 though, so
151 * We only adjust the length if we can find a start-of-code-point in
152 * the last 4 bytes. (longest UTF-8 encoding of 21bit unicode is 4 bytes).
153 * A start of codepoint starts with 0b0 or 0b11, not 0b10.
155 static int text_round_len(char *text, int len)
157 /* The string at 'text' is *longer* than 'len', or
158 * at least text[len] is defined - it can be nul. If
159 * [len] isn't the start of a new codepoint, and there
160 * is a start marker in the previous 4 bytes,
161 * move back to there.
164 while (i <= len && i <=4)
165 if ((text[len-i] & 0xC0) == 0x80)
166 /* next byte is inside a UTF-8 code point, so
167 * this isn't a good spot to end. Try further
175 static struct text_alloc *
176 text_new_alloc(struct text *t, int size)
178 struct text_alloc *new;
181 new = malloc(size + sizeof(struct text_alloc));
184 new->prev = t->alloc;
191 DEF_CMD(text_load_file)
195 char *name = ci->str;
196 off_t size = lseek(fd, 0, SEEK_END);
197 struct text_alloc *a;
198 struct text_chunk *c;
206 t = container_of(dd->doc, struct text, doc);
210 lseek(fd, 0, SEEK_SET);
212 c = malloc(sizeof(*c));
213 a = text_new_alloc(t, size);
216 while (a->free < size &&
217 (len = read(fd, a->text + a->free, size - a->free)) > 0)
224 list_add(&c->lst, &t->text);
231 t->fname = strdup(name);
232 dname = strrchr(name, '/');
237 doc_set_name(dd->doc, dname);
245 static int do_text_write_file(struct text *t, char *fname)
247 /* Don't worry about links for now
248 * Create a temp file with #basename#~, write to that,
249 * fsync and then rename
251 char *tempname = malloc(strlen(fname) + 3 + 10);
255 struct text_chunk *c;
257 strcpy(tempname, fname);
258 base = strrchr(fname, '/');
263 tbase = tempname + (base - fname);
264 while (cnt < 20 && fd == -1) {
266 sprintf(tbase, "#%s#~%d", base, cnt);
268 sprintf(tbase, "#%s#~", base);
269 fd = open(tempname, O_WRONLY|O_CREAT|O_EXCL, 0666);
270 if (fd < 0 && errno != EEXIST)
277 list_for_each_entry(c, &t->text, lst) {
278 char *s = c->txt + c->start;
279 int ln = c->end - c->start;
280 if (write(fd, s, ln) != ln)
285 if (rename(tempname, fname) < 0)
298 DEF_CMD(text_save_file)
300 struct doc_data *dd = ci->home->data;
301 struct text *t = container_of(dd->doc, struct text, doc);
306 asprintf(&msg, "** No file name known for %s ***", dd->doc->name);
309 ret = do_text_write_file(t, t->fname);
311 asprintf(&msg, "Successfully wrote %s", t->fname);
313 asprintf(&msg, "*** Faild to write %s ***", t->fname);
315 call5("Message", ci->home, 0, NULL, msg, 0);
322 DEF_CMD(text_same_file)
324 struct doc_data *dd = ci->home->data;
325 struct stat *stb = ci->misc;
326 struct text *t = container_of(dd->doc, struct text, doc);
328 if (t->fname == NULL)
330 if (t->stat.st_ino == stb->st_ino &&
331 t->stat.st_dev == stb->st_dev)
336 static void text_add_edit(struct text *t, struct text_chunk *target,
337 bool *first, int at_start, int len)
343 e = malloc(sizeof(*e));
346 e->at_start = at_start;
353 static void text_add_str(struct text *t, struct doc_ref *pos, char *str,
354 struct doc_ref *start, bool *first_edit)
356 /* Text is added to the end of the referenced chunk, or
357 * in new chunks which are added afterwards. This allows
358 * the caller to reliably updated any pointers to accommodate
360 * The added text has no attributes.
362 * 'pos' is moved to point to the end of the inserted text.
363 * 'start' is set to point to the start which may be the
364 * original 'pos', or may not if a chunk was inserted.
366 /* easy/common case first: pos is at the end of a chunk,
367 * which is the last chunk in the current allocation.
369 struct text_alloc *a = t->alloc;
370 int len = strlen(str);
377 if (pos->c && pos->o == pos->c->end &&
378 pos->c->txt + pos->o == a->text + a->free &&
379 (a->size - a->free >= len ||
380 (len2 = text_round_len(str, a->size - a->free)) > 0)) {
381 /* Some of this ('len') can be added to the current chunk */
383 memcpy(a->text+a->free, str, len);
388 text_add_edit(t, pos->c, first_edit, 0, len);
393 /* Need a new chunk. Might need to split the current chunk first.
394 * Old chunk must be first to simplify updating of pointers */
395 if (pos->c == NULL || pos->o < pos->c->end) {
396 struct text_chunk *c = malloc(sizeof(*c));
397 if (pos->c == NULL || pos->o == pos->c->start) {
398 /* At the start of a chunk, so create a new one here */
400 c->start = c->end = 0;
403 list_add_tail(&c->lst, &pos->c->lst);
405 list_add_tail(&c->lst, &t->text);
407 if (start && start->c == pos->c && start->o == pos->o) {
414 c->txt = pos->c->txt;
416 c->end = pos->c->end;
417 c->attrs = attr_copy_tail(pos->c->attrs, c->start);
418 attr_trim(&pos->c->attrs, c->start);
419 pos->c->end = pos->o;
420 list_add(&c->lst, &pos->c->lst);
421 text_add_edit(t, c, first_edit, 0, c->end - c->start);
422 /* this implicitly truncates pos->c, so don't need to record that. */
425 while ((len = strlen(str)) > 0) {
426 /* Make sure we have an empty chunk */
427 if (pos->c->end > pos->c->start) {
428 struct text_chunk *c = malloc(sizeof(*c));
429 c->start = c->end = 0;
431 list_add(&c->lst, &pos->c->lst);
432 if (start && start->c == pos->c && start->o == pos->o) {
439 /* Make sure we have some space in 'a' */
440 if (a->size - a->free < len &&
441 (len = text_round_len(str, a->size - a->free)) == 0) {
442 a = text_new_alloc(t, 0);
445 len = text_round_len(str, a->size);
447 pos->c->txt = a->text + a->free;
450 memcpy(a->text + a->free, str, len);
451 text_add_edit(t, pos->c, first_edit, 0, len);
457 /* Text insertion, deletion, and undo can modify chunks which various
458 * marks point to - so those marks will need to be updated.
459 * Modification include splitting a chunk, inserting chunks,
460 * or deleting chunks and recombining chunks (for undo).
461 * Also reducing or increasing the range of a chunk.
462 * When a chunk is split, the original becomes the first part.
463 * So any mark pointing past the end of that original must be moved
465 * When a chunk is deleted, any mark pointing to a deleted chunk
466 * must be redirected to the (new) point of deletion.
467 * When a chunk is inserted, marks before the insertion mark must remain
468 * before the inserted chunk, marks after must remain after the insertion
470 * When two chunks are recombined it will appear that the second chunk
471 * was deleted. In this case though, references to the second chunk need
472 * to be repositioned in the first.
473 * When range is reduced, offset must be moved back into range.
474 * When range is increased, and this mark is after change, offset in this mark
475 * need to line up with changed point.
477 * So text_update_prior_after_change() is called on marks before the
478 * mark-of-change in reverse order until the function returns zero.
479 * If it finds a mark pointing to a deleted chunk, that mark changes to
480 * point the same place as the mark-of-change.
481 * If it finds a mark at, or immediately after, the mark-of-change,
482 * that mark is moved to point to the start of insert.
484 * Then text_update_following_after_change() is called on marks after
485 * the mark-of-change in order until that function returns zero.
486 * If a mark points outside the range of a chunk, the other half of the
487 * chunk is found (by walking forward) and the pointer is updated.
488 * If a deleted chunk is found, that mark is redirected to the mark-of-change.
489 * If a location at the start is found, it is move to the end.
492 static int text_update_prior_after_change(struct text *t, struct doc_ref *pos,
493 struct doc_ref *spos, struct doc_ref *epos)
496 if (pos->c == NULL) {
497 /* Was at the end, now must be at the start of the change */
502 if (pos->c->start >= pos->c->end) {
503 /* This chunk was deleted */
507 if (text_ref_same(t, pos, epos)) {
511 if (pos->o < pos->c->start) {
512 /* Text deleted from under me */
513 pos->o = pos->c->start;
516 if (pos->o > pos->c->end) {
517 /* Text deleted under me */
518 pos->o = pos->c->end;
521 /* no insert or delete here, so all done */
525 static int text_update_following_after_change(struct text *t, struct doc_ref *pos,
526 struct doc_ref *spos, struct doc_ref *epos)
528 /* A change has happened between spos and epos. pos should be at or after
531 struct text_chunk *c;
536 if (pos->c->start >= pos->c->end) {
537 /* This chunk was deleted */
539 pos->c->txt == epos->c->txt &&
540 pos->o >= epos->c->start &&
541 pos->o <= epos->c->end)
542 /* chunks were rejoined */
548 if (pos->c == epos->c &&
550 /* Text inserted, need to push forward. */
554 if (pos->o < pos->c->start) {
555 /* must have been deleted... */
556 pos->o = pos->c->start;
559 if (pos->o > pos->c->end) {
560 /* This was split, or text was deleted off the end */
564 list_for_each_entry_from(c, &t->text, lst) {
565 if (c->txt == pos->c->txt &&
566 c->start <= pos->o &&
572 if (pos->o > pos->c->end)
573 /* no split found, so just a delete */
574 pos->o = pos->c->end;
577 if (text_ref_same(t, pos, spos)) {
582 if (text_ref_same(t, pos, epos))
585 /* This is beyond the change point and no deletion or split
586 * happened here, so all done.
591 static void text_del(struct text *t, struct doc_ref *pos, int len, bool *first_edit)
594 struct text_chunk *c = pos->c;
596 /* nothing more to delete */
598 if (pos->o == pos->c->start &&
599 len >= pos->c->end - pos->c->start) {
600 /* The whole chunk is deleted, simply disconnect it */
601 if (c->lst.next != &t->text) {
602 pos->c = list_next_entry(c, lst);
603 pos->o = pos->c->start;
604 } else if (c->lst.prev != &t->text) {
605 pos->c = list_prev_entry(c, lst);
606 pos->o = pos->c->end;
608 /* Deleted final chunk */
612 __list_del(c->lst.prev, c->lst.next); /* no poison, retain place in list */
613 attr_free(&c->attrs);
614 text_add_edit(t, c, first_edit, 0, c->start - c->end);
615 len -= c->end - c->start;
617 } else if (pos->o == pos->c->start) {
618 /* If the start of the chunk is deleted, just update */
622 s = attr_copy_tail(c->attrs, c->start);
623 attr_free(&c->attrs);
625 text_add_edit(t, c, first_edit, 1, len);
627 } else if (c->end - pos->o <= len) {
628 /* If the end of the chunk is deleted, just update
629 * and move forward */
630 int diff = c->end - pos->o;
633 attr_trim(&c->attrs, c->end);
634 text_add_edit(t, c, first_edit, 0, -diff);
635 if (len && c->lst.next != &t->text) {
636 pos->c = list_next_entry(c, lst);
637 pos->o = pos->c->start;
641 /* must be deleting out of the middle of the chunk.
642 * need to create new chunk for the 'after' bit.
644 struct text_chunk *c2 = malloc(sizeof(*c2));
646 c2->start = pos->o + len;
649 c2->attrs = attr_copy_tail(c->attrs, c2->start);
650 attr_trim(&c->attrs, c->end);
651 list_add(&c2->lst, &c->lst);
652 text_add_edit(t, c2, first_edit, 0, c2->end - c2->start);
653 text_add_edit(t, c, first_edit, 0, -len);
659 /* text_undo and text_redo return:
660 * 0 - there are no more changes to do
661 * 1 - A change has been done, there are no more parts to it.
662 * 2 - A change has been partially undone - call again to undo more.
664 * The 'start' and 'end' reported identify the range changed. For a reversed insertion
665 * they will be the same. If the undo results in the buffer being empty,
666 * both start and end will point to a NULL chunk.
667 * When undoing a split, both will be at the point of the split.
669 static int text_undo(struct text *t, struct doc_ref *start, struct doc_ref *end)
671 struct text_edit *e = t->undo;
676 if (e->target->end == e->target->start) {
677 /* need to re-link */
678 struct list_head *l = e->target->lst.prev;
679 list_add(&e->target->lst, l);
681 start->c = end->c = e->target;
682 start->o = e->target->end; // incase was deletion at end
683 end->o = e->target->start; // incase was deletion at start
685 e->target->start -= e->len;
687 /* was deletion, this is insertion */
688 start->o = e->target->start;
690 /* was insertion - not really possible */
691 start->o = end->o = e->target->start;
693 e->target->end -= e->len;
695 /* Was insertion, now deleting */
696 start->o = end->o = e->target->end;
698 /* Was deletion, now inserting */
699 end->o = e->target->end;
704 if (e->target->start == e->target->end) {
705 /* The undo deletes this chunk, so it must have been inserted,
706 * either as new text or for a chunk-split.
707 * If new text, leave start/end pointing just past the chunk.
708 * if split, leave them at the point of splitting.
710 if (e->target->lst.next == &t->text) {
714 end->c = list_next_entry(e->target, lst);
715 end->o = end->c->start;
719 __list_del(e->target->lst.prev, e->target->lst.next);
720 /* If this was created for a split, we need to extend the other half */
721 if (e->target->lst.prev != &t->text) {
722 struct text_chunk *c = list_prev_entry(e->target, lst);
723 start->c = end->c = c;
724 start->o = end->o = c->end;
725 if (c->txt == e->target->txt &&
726 c->end == e->target->start &&
737 static int text_redo(struct text *t, struct doc_ref *start, struct doc_ref *end)
739 struct text_edit *e = t->redo;
745 if (e->target->end == e->target->start) {
746 /* need to re-link */
747 struct list_head *l = e->target->lst.prev;
748 list_add(&e->target->lst, l);
749 /* If this is a split, need to truncate prior */
750 if (e->target->lst.prev != &t->text) {
751 struct text_chunk *c = list_prev_entry(e->target, lst);
752 if (c->txt == e->target->txt &&
753 c->end > e->target->start) {
754 c->end = e->target->start;
759 start->c = end->c = e->target;
760 end->o = e->target->start; // incase is insertion at start
761 start->o = e->target->end; // incase inserting at end
763 e->target->start += e->len;
765 /* deletion at start */
766 start->o = end->o = e->target->start;
768 /* Insertion at start, not currently possible */
769 start->o = e->target->start;
771 e->target->end += e->len;
773 /* Insertion at end */
774 end->o = e->target->end;
776 start->o = end->o = e->target->start;
778 /* Deletion at end */
779 start->o = end->o = e->target->end;
784 if (e->target->start == e->target->end) {
785 /* This chunk is deleted, so leave start/end pointing beyond it */
786 if (e->target->lst.next == &t->text) {
790 end->c = list_next_entry(e->target, lst);
791 end->o = end->c->start;
795 __list_del(e->target->lst.prev, e->target->lst.next);
797 if (t->redo && t->redo->first)
805 struct doc_data *dd = ci->home->data;
806 struct mark *m = dd->point;
807 bool redo = ci->numeric != 0;
808 struct doc_ref start, end;
811 struct text *t = container_of(dd->doc, struct text, doc);
813 while (did_do != 1) {
815 struct mark *early = NULL;
820 did_do = text_redo(t, &start, &end);
822 did_do = text_undo(t, &start, &end);
827 /* Not nearby, look from the start */
828 mark_reset(&t->doc, m);
832 where = text_locate(t, &m->ref, &end);
838 i = text_advance_towards(t, &m->ref, &end);
841 while ((m2 = doc_next_mark_all(m)) != NULL &&
842 m2->ref.c == m->ref.c &&
843 m2->ref.o <= m->ref.o)
844 mark_forward_over(m, m2);
848 i = text_retreat_towards(t, &m->ref, &end);
851 while ((m2 = doc_prev_mark_all(m)) != NULL &&
852 m2->ref.c == m->ref.c &&
853 m2->ref.o >= m->ref.o)
854 mark_backward_over(m, m2);
858 if (!text_ref_same(t, &m->ref, &end))
861 /* point is now at location of undo */
864 hlist_for_each_entry_continue_reverse(m2, all)
865 if (text_update_prior_after_change(t, &m2->ref,
869 hlist_for_each_entry_continue(m2, all)
870 if (text_update_following_after_change(t, &m2->ref,
874 early = doc_prev_mark_all(m);
875 if (early && !text_ref_same(t, &early->ref, &start))
878 doc_notify_change(&t->doc, dd->point, early);
880 text_check_consistent(t);
882 text_check_consistent(t);
888 static int common_prefix(char *a, char *b, int l)
897 /* Compare a string with the text.
898 * Update the ref passed all matching chars.
899 * Return length that was matched.
901 static int text_str_cmp(struct text *t, struct doc_ref *r, char *s)
903 struct text_chunk *c = r->c;
910 list_for_each_entry_from(c, &t->text, lst) {
916 l = common_prefix(c->txt+o, s, l);
932 static wint_t text_next(struct text *t, struct doc_ref *r)
941 if (r->o >= r->c->end) {
942 if (r->c->lst.next == &t->text)
944 r->c = list_next_entry(r->c, lst);
948 err = mbrtowc(&ret, r->c->txt + r->o, r->c->end - r->o, &ps);
950 ASSERT(text_round_len(r->c->txt, r->o+err-1) == r->o);
954 ret = (unsigned char)r->c->txt[r->o++];
958 static wint_t text_prev(struct text *t, struct doc_ref *r)
965 if (list_empty(&t->text))
967 r->c = list_entry(t->text.prev, struct text_chunk, lst);
971 if (r->o <= r->c->start) {
972 if (r->c->lst.prev == &t->text)
974 r->c = list_prev_entry(r->c, lst);
978 text_round_len(r->c->txt+r->c->start, r->o - r->c->start - 1);
979 err = mbrtowc(&ret, r->c->txt + r->o, r->c->end - r->o, &ps);
983 ret = (unsigned char)r->c->txt[r->o];
989 struct doc_data *dd = ci->home->data;
990 struct mark *m = ci->mark;
991 bool forward = ci->numeric;
992 bool move = ci->extra;
994 struct text *t = container_of(dd->doc, struct text, doc);
1000 ret = text_next(t, &r);
1002 ret = text_prev(t, &r);
1003 if (ret != WEOF && move)
1004 m->ref = *(struct doc_ref*)&r;
1005 /* return value must be +ve, so use high bits to ensure this. */
1006 return (ret & 0xFFFFF) | 0x100000;
1009 static int text_ref_same(struct text *t, struct doc_ref *r1, struct doc_ref *r2)
1011 if (r1->c == r2->c) {
1016 /* if references are in the middle of a UTF-8 encoded
1017 * char, accept as same if it is same char
1019 if (r1->o == r1->c->end ||
1020 r2->o == r2->c->end)
1022 return text_round_len(r1->c->txt, r1->o) ==
1023 text_round_len(r1->c->txt, r2->o);
1025 if (r1->c == NULL) {
1026 if (list_empty(&t->text))
1028 return (r2->o == r2->c->end &&
1029 r2->c->lst.next == &t->text);
1031 if (r2->c == NULL) {
1032 if (list_empty(&t->text))
1034 return (r1->o == r1->c->end &&
1035 r1->c->lst.next == &t->text);
1038 if (r1->o == r1->c->end &&
1039 r2->o == r2->c->start &&
1040 list_next_entry(r1->c, lst) == r2->c)
1042 if (r1->o == r1->c->start &&
1043 r2->o == r2->c->end &&
1044 list_prev_entry(r1->c, lst) == r2->c)
1049 DEF_CMD(text_mark_same)
1051 struct doc_data *dd = ci->home->data;
1052 struct text *t = container_of(dd->doc, struct text, doc);
1054 return text_ref_same(t, &ci->mark->ref, &ci->mark2->ref) ? 1 : 2;
1059 struct text *t = malloc(sizeof(*t));
1060 struct editor *ed = pane2ed(ci->focus);
1064 INIT_LIST_HEAD(&t->text);
1065 t->undo = t->redo = NULL;
1067 t->doc.map = text_map;
1069 text_new_alloc(t, 0);
1070 p = doc_attach(ed->root.focus, &t->doc);
1072 return comm_call(ci->comm2, "callback:doc", p, 0, NULL, NULL, 0);
1076 static int count_bytes(struct text *t, struct mark *from, struct mark *to)
1078 struct text_chunk *c, *first, *last;
1079 int l = 0, head, tail;
1081 first = list_first_entry_or_null(&t->text, struct text_chunk, lst);
1083 if (from && from->ref.c) {
1084 first = from->ref.c;
1085 head = from->ref.o - first->start;
1089 if (to && to->ref.c) {
1091 tail = last->end - to->ref.o;
1094 list_for_each_entry_from(c, &t->text, lst) {
1095 l += c->end - c->start;
1106 DEF_CMD(text_get_str)
1108 struct doc_data *dd = ci->home->data;
1109 struct mark *from = NULL, *to = NULL;
1110 struct text *t = container_of(dd->doc, struct text, doc);
1111 struct text_chunk *c, *first, *last;
1113 int l = 0, head, tail;
1115 if (ci->mark && ci->mark2) {
1116 if (mark_ordered(ci->mark2, ci->mark)) {
1125 first = list_first_entry_or_null(&t->text, struct text_chunk, lst);
1127 if (from && from->ref.c) {
1128 first = from->ref.c;
1129 head = from->ref.o - first->start;
1133 if (to && to->ref.c) {
1135 tail = last->end - to->ref.o;
1138 list_for_each_entry_from(c, &t->text, lst) {
1139 l += c->end - c->start;
1150 list_for_each_entry_from(c, &t->text, lst) {
1151 char *s = c->txt + c->start;
1152 int ln = c->end - c->start;
1159 memcpy(ret+l, s, ln);
1165 comm_call(ci->comm2, "callback:get-str", ci->focus, 0, NULL, ret, 0);
1170 DEF_CMD(text_set_ref)
1172 struct doc_data *dd = ci->home->data;
1173 struct mark *m = ci->mark;
1174 struct text *t = container_of(dd->doc, struct text, doc);
1176 if (list_empty(&t->text) || ci->numeric != 1) {
1180 m->ref.c = list_first_entry(&t->text, struct text_chunk, lst);
1181 m->ref.o = m->ref.c->start;
1187 static int text_advance_towards(struct text *t, struct doc_ref *ref, struct doc_ref *target)
1189 /* Move 'ref' towards 'target'.
1190 * If at end of chunk, step to next chunk, then
1191 * advance to 'target' or to end of chunk, whichever comes first.
1193 * 0 - reached end of text
1195 * 2 - on a new chunk, keep looking.
1197 if (ref->c == target->c) {
1198 if (ref->o > target->o)
1199 /* will never find it */
1204 if (ref->c == NULL) {
1205 if (target->c->lst.next == &t->text &&
1206 target->o == target->c->end)
1210 if (ref->o >= ref->c->end) {
1211 if (ref->c->lst.next == &t->text) {
1212 if (target->c == NULL) {
1219 ref->c = list_next_entry(ref->c, lst);
1220 ref->o = ref->c->start;
1222 if (ref->c == target->c) {
1223 if (ref->o > target->o)
1224 /* will never find it */
1229 ref->o = ref->c->end;
1233 static int text_retreat_towards(struct text *t, struct doc_ref *ref, struct doc_ref *target)
1235 /* Move 'ref' towards 'target'.
1236 * If at end of chunk, step to next chunk, then
1237 * advance to 'target' or to end of chunk, whichever comes first.
1239 * 0 - reached start of text
1241 * 2 - on a new chunk, keep looking.
1243 if (ref->c == NULL) {
1244 if (list_empty(&t->text))
1246 ref->c = list_entry(t->text.prev, struct text_chunk, lst);
1247 ref->o = ref->c->end;
1249 if (ref->o <= ref->c->start) {
1250 if (ref->c->lst.prev == &t->text)
1252 ref->c = list_prev_entry(ref->c, lst);
1253 ref->o = ref->c->end;
1255 if (ref->c == target->c) {
1259 ref->o = ref->c->start;
1263 static int text_locate(struct text *t, struct doc_ref *r, struct doc_ref *dest)
1265 /* move back/forward a little from 'r' looking for 'dest'.
1266 * return 0 if not found, -1 if dest found before r.
1267 * return 1 if dest found after or at r.
1269 struct text_chunk *next, *prev;
1272 if (dest->c == NULL)
1277 if (dest->c == NULL)
1279 if (r->c == dest->c) {
1285 next = (r->c->lst.next == &t->text) ? NULL : list_next_entry(r->c, lst);
1286 prev = (r->c->lst.prev == &t->text) ? NULL : list_prev_entry(r->c, lst);
1287 if (next == dest->c)
1289 if (prev == dest->c)
1292 next = (next == NULL || next->lst.next == &t->text) ? NULL : list_next_entry(next, lst);
1293 prev = (prev == NULL || prev->lst.prev == &t->text) ? NULL : list_prev_entry(prev, lst);
1294 if (next == dest->c)
1296 if (prev == dest->c)
1301 static void check_allocated(struct text *t, char *buf, int len)
1303 struct text_alloc *ta = t->alloc;
1304 for (ta = t->alloc; ta; ta = ta->prev) {
1305 if (buf >= ta->text && buf+len <= ta->text + ta->free)
1311 static void text_ref_consistent(struct text *t, struct doc_ref *r)
1313 struct text_chunk *c;
1320 if (r->o > r->c->end)
1322 if (r->o < r->c->start)
1324 list_for_each_entry(c, &t->text, lst)
1330 static void text_check_consistent(struct text *t)
1332 /* make sure text is consistent, and abort if not.
1333 * - each chunk points to allocated space
1334 * - no two chunks overlap
1335 * - no chunks are empty
1336 * - every mark points to a valid chunk with valid offset
1337 * - all marks are in text order
1339 struct text_chunk *c;
1340 struct mark *m, *prev;
1341 struct doc *d = &t->doc;
1343 list_for_each_entry(c, &t->text, lst) {
1344 check_allocated(t, c->txt, c->end);
1345 if (c->start >= c->end)
1350 list_for_each_entry(c, &t->text, lst) {
1351 struct text_chunk *c2;
1352 list_for_each_entry(c2, &t->text, lst) {
1356 if (c->start >= c2->end)
1358 if (c2->start >= c->end)
1364 for (m = doc_first_mark_all(d); m; m = doc_next_mark_all(m))
1365 text_ref_consistent(t, &m->ref);
1368 for (m = doc_first_mark_all(d); m; m = doc_next_mark_all(m)) {
1370 struct doc_ref r = prev->ref;
1373 while ((i = text_advance_towards(t, &r, &m->ref)) != 1) {
1380 doc_check_consistent(d);
1383 DEF_CMD(text_replace)
1385 struct doc_data *dd = ci->home->data;
1386 struct text *t = container_of(dd->doc, struct text, doc);
1387 struct mark *pm = dd->point;
1388 struct mark *end = ci->mark;
1389 char *str = ci->str;
1390 bool first = ci->extra;
1391 struct mark *early = NULL;
1393 /* First delete, then insert */
1394 if (end && !text_ref_same(t, &pm->ref, &end->ref)) {
1395 struct mark *myend, *m;
1398 if (!mark_ordered(pm, end)) {
1399 myend = mark_dup(pm, 1);
1400 mark_to_mark(pm, end);
1402 myend = mark_dup(end, 1);
1403 l = count_bytes(t, pm, myend);
1405 text_del(t, &pm->ref, l, &first);
1407 for (m = doc_prev_mark_all(pm);
1408 m && text_update_prior_after_change(t, &m->ref, &pm->ref, &pm->ref);
1409 m = doc_prev_mark_all(m))
1411 for (m = doc_next_mark_all(pm);
1412 m && text_update_following_after_change(t, &m->ref, &pm->ref, &pm->ref);
1413 m = doc_next_mark_all(m))
1415 text_check_consistent(t);
1417 early = doc_prev_mark_all(pm);
1418 if (early && !mark_same(&t->doc, early, pm))
1422 struct doc_ref start;
1425 text_add_str(t, &pm->ref, str, &start, &first);
1426 for (m = doc_prev_mark_all(pm);
1427 m && text_update_prior_after_change(t, &m->ref, &start, &pm->ref);
1428 m = doc_prev_mark_all(m))
1430 for (m = doc_next_mark_all(pm);
1431 m && text_update_following_after_change(t, &m->ref, &start, &pm->ref);
1432 m = doc_next_mark_all(m))
1434 text_check_consistent(t);
1437 doc_notify_change(&t->doc, pm, early);
1438 return first ? 1 : 2;
1442 static char *__text_get_attr(struct doc *d, struct mark *m,
1443 bool forward, char *attr)
1445 struct text_chunk *c;
1446 struct text *t = container_of(d, struct text, doc);
1450 char *a = attr_get_str(d->attrs, attr, -1);
1453 if (strcmp(attr, "default-renderer") == 0) {
1454 // if (t->fname && strcmp(t->fname + strlen(t->fname)-3, ".md") == 0)
1455 // return "present";
1458 if (strcmp(attr, "filename") == 0)
1471 /* End of chunk, need to look at next */
1472 if (c->lst.next == &t->text)
1474 c = list_next_entry(c, lst);
1479 if (list_empty(&t->text))
1481 c = list_entry(t->text.prev, struct text_chunk, lst);
1485 if (c->lst.prev == &t->text)
1487 c = list_entry(c->lst.prev, struct text_chunk, lst);
1492 return attr_get_str(c->attrs, attr, o);
1495 DEF_CMD(text_get_attr)
1497 struct doc_data *dd = ci->home->data;
1498 struct mark *m = ci->mark;
1499 bool forward = ci->numeric != 0;
1500 char *attr = ci->str;
1501 char *val = __text_get_attr(dd->doc, m, forward, attr);
1503 comm_call(ci->comm2, "callback:get_attr", ci->focus, 0, NULL, val, 0);
1507 DEF_CMD(text_set_attr)
1509 char *attr = ci->str;
1510 char *val = ci->str2;
1511 struct text_chunk *c = ci->mark->ref.c;
1512 struct doc_data *dd = ci->home->data;
1513 struct text *t = container_of(dd->doc, struct text, doc);
1514 int o = ci->mark->ref.o;
1520 /* End of chunk, need to look at next */
1521 if (c->lst.next == &t->text)
1523 c = list_next_entry(c, lst);
1526 doc_notify_change(&t->doc, ci->mark, NULL);
1527 return attr_set_str(&c->attrs, attr, val, o);
1530 DEF_CMD(text_destroy)
1532 struct doc_data *dd = ci->home->data;
1533 struct text *t = container_of(dd->doc, struct text, doc);
1535 while (!list_empty(&t->text)) {
1536 struct text_chunk *c = list_entry(t->text.next, struct text_chunk, lst);
1538 attr_free(&c->attrs);
1542 struct text_alloc *ta = t->alloc;
1543 t->alloc = ta->prev;
1547 struct text_edit *te = t->undo;
1552 struct text_edit *te = t->redo;
1560 #define LARGE_LINE 4096
1562 DEF_CMD(render_line_prev)
1564 /* In the process of rendering a line we need to find the
1566 * Search backwards until a newline or start-of-file is found,
1567 * or until a LARGE_LINE boundary has been passed and a further
1568 * LARGE_LINE/2 bytes examined with no newline. In that case,
1569 * report the boundary.
1570 * If RPT_NUM == 1, step back at least one character so we get
1571 * the previous line and not the line we are on.
1572 * If we hit start-of-file without finding newline, return -1;
1574 struct mark *m = ci->mark;
1575 struct doc_data *dd = ci->home->data;
1576 struct doc *d = dd->doc;
1577 struct mark *boundary = NULL;
1579 int rpt = RPT_NUM(ci);
1583 while ((ch = mark_prev(d, m)) != WEOF &&
1584 (ch != '\n' || rpt > 0) &&
1585 (!boundary || since_boundary < LARGE_LINE/2)) {
1588 since_boundary += 1;
1589 else if (m->ref.o < offset &&
1590 m->ref.o >= LARGE_LINE &&
1591 (m->ref.o-1) / LARGE_LINE != (offset-1) / LARGE_LINE) {
1592 /* here is a boundary */
1593 boundary = mark_dup(m, 1);
1597 if (ch != WEOF && ch != '\n') {
1598 /* need to use the boundary */
1601 mark_to_mark(m, boundary);
1602 mark_free(boundary);
1606 mark_free(boundary);
1607 if (ch == WEOF && rpt)
1610 /* Found a '\n', so step back over it for start-of-line. */
1615 DEF_CMD(render_line)
1617 /* Render the line from 'mark' to the first '\n' or until
1619 * Convert '<' to '<<' and if a char has the 'highlight' attribute,
1620 * include that between '<>'.
1623 struct doc_data *dd = ci->home->data;
1624 struct doc *d = dd->doc;
1625 struct mark *m = ci->mark;
1626 struct mark *pm = ci->mark2; /* The location to render as focus */
1627 int o = ci->numeric;
1637 char *attr = __text_get_attr(d, m, 1, "highlight");
1638 int offset = m->ref.o;
1639 if (o >= 0 && b.len >= o)
1641 if (pm && mark_same(d, m, pm))
1643 ch = mark_next(d, m);
1650 if (chars > LARGE_LINE/2 &&
1651 m->ref.o > offset &&
1652 (m->ref.o-1)/LARGE_LINE != (offset-1)/LARGE_LINE)
1655 buf_append(&b, '<');
1656 buf_concat(&b, attr);
1657 buf_append(&b, '>');
1660 if (o >= 0 && b.len+1 >= o) {
1664 buf_append(&b, '<');
1666 if (ch < ' ' && ch != '\t' && ch != '\n') {
1667 buf_concat(&b, "<fg:red>^");
1668 buf_append(&b, '@' + ch);
1669 buf_concat(&b, "</>");
1670 } else if (ch == 0x7f) {
1671 buf_concat(&b, "<fg:red>^?</>");
1675 buf_concat(&b, "</>");
1678 ret = comm_call(ci->comm2, "callback:render", ci->focus, 0, NULL,
1684 void edlib_init(struct editor *ed)
1686 key_add(ed->commands, "doc-text", &text_new);
1688 text_map = key_alloc();
1689 key_add(text_map, "render-line-prev", &render_line_prev);
1690 key_add(text_map, "render-line", &render_line);
1692 key_add(text_map, "doc:load-file", &text_load_file);
1693 key_add(text_map, "doc:same-file", &text_same_file);
1694 key_add(text_map, "doc:get-str", &text_get_str);
1695 key_add(text_map, "doc:destroy", &text_destroy);
1696 key_add(text_map, "doc:set-ref", &text_set_ref);
1697 key_add(text_map, "doc:save-file", &text_save_file);
1698 key_add(text_map, "doc:reundo", &text_reundo);
1699 key_add(text_map, "doc:set-attr", &text_set_attr);
1700 key_add(text_map, "doc:get-attr", &text_get_attr);
1701 key_add(text_map, "doc:replace", &text_replace);
1702 key_add(text_map, "doc:mark-same", &text_mark_same);
1703 key_add(text_map, "doc:step", &text_step);