2 * Copyright Neil Brown ©2015-2023 <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
27 * a cache. The owner can get notified of changes which imply that
28 * attributes may have been lost.
30 * When text is removed from a document, the 'chunk' is modified to
31 * reference less text. If the chunk becomes empty, it is removed
32 * from the list, but not freed - as it will be in the undo list.
33 * When text is added to a document a new chunk is created which
34 * points to the next free space in the latest allocation, and text is
35 * added there. If the text is being added to the end of a chunk and
36 * it already points to the end of the latest allocation, then no new
39 * Text is assumed to be UTF-8 encoded. This becomes relevant when adding
40 * a string to the document and it won't all fit in the current allocation.
41 * In that case we ensure the first byte that goes in the next allocation
42 * matches 0xxxxxxx or 11xxxxxx., not 10xxxxxx.
44 * Undo/redo information is stored as a list of edits. Each edit
45 * changes either the start or the end of a 'chunk'. When a chunk becomes
46 * empty it is removed from the chunk list. The 'prev' pointer is preserved
47 * so when an undo makes it non-empty, it knows where to be added back.
49 * A text always has a least one allocation which is created with the text.
50 * If the text is empty, there will not be any chunks though, so all text refs
51 * will point to NULL. The NULL chunk is at the end of the text.
52 * The ->txt pointer of a chunk never changes. It is set when the chunk
53 * is created and then only start and end are changed.
56 #define _GNU_SOURCE /* for asprintf */
68 /* A doc_ref is treated as a pointer to a chunk, and an offset
69 * from the start of 'txt'. So 'o' must be between c->start and
71 * A 'c' of NULL means end of file.
72 * The normalized form requires that 'o' does
73 * not point to the end of the chunk.
75 #define PRIVATE_DOC_REF
82 #define DOC_DATA_TYPE struct text
83 #define DOC_NEXT(d,m,r,b) text_next(d,r,b)
84 #define DOC_PREV(d,m,r,b) text_prev(d,r,b)
88 /* text is allocated is large blocks - possibly a whole
89 * file or some other large unit being added to a document.
90 * For small additions (normal typing) the default allocation
92 * When more is allocated than needed, extra can be added on to
93 * the end - 'free' is the next index with free space.
96 struct text_alloc *prev;
102 #define DEFAULT_SIZE ((int)(4096 - sizeof(struct text_alloc)))
103 #define MAX_SIZE ((int)((1<<20) - sizeof(struct text_alloc)))
105 /* The text document is a list of text_chunk.
106 * The 'txt' pointer is within the text[] of a text_alloc.
107 * 'start' and 'end' narrow that.
108 * Each alloc potentially is divided into multiple
109 * separate chunks which are never merged. The only
110 * chunk that can change size is the last one allocated,
111 * which may grow into the free space.
117 struct list_head lst;
118 struct attrset *attrs;
121 /* An 'edit' consists of one or more text_edit structs linked together.
122 * The 'first' text_edit in a group has 'first' set. So when popping
123 * off the 'undo' list, we pop until we find the 'first' one. When
124 * popping off the 'redo' list, we pop a first, then any following
126 * Each entry identifies a chunk. If 'at_start' is set, the 'len' is
127 * added to the 'start' pointer (subtracted for undo). Otherwise
128 * the len added to the end. If the resulting length is zero, the
129 * chunk is removed from the list.
131 * Each edit can have an altnext.
132 * For the undo list, this is an alternate redo to reflect a branching
134 * For the redo list, this is a second change that happened from the
135 * same starting point. If there is a third change, we insert a no-op
136 * edit so as to get an extra altnext.
137 * In the undo list, altnext is an alternate forward path.
138 * if alt_is_second, then we are currently on the second path, and after
139 * undoing it, will go up the first.
140 * if !alt_is_second, we are currently on the first path, and
141 * don't want to go back up the second (until we undo all the way to the
142 * start and try again).
145 struct text_chunk *target safe;
146 struct text_edit *next, *altnext;
149 bool alt_is_second:1;
150 signed int len:29; // bytes add, -ve for removed.
153 /* A text document is a document with allocations, a list
154 * of chunks, and some undo info.
159 struct text_alloc *alloc safe;
160 struct list_head text;
161 struct text_edit *undo, *redo;
162 /* If prev_edit is Redo then next edit is ->redo or ->undo->altnext
164 * If prev_edit is Undo, then next edit is ->undo->altnext or ->undo
165 * If prev_edit is AltUndo, then next edit is ->undo
167 enum { Redo, Undo, AltUndo } prev_edit;
170 char file_changed; /* '2' means it has changed, but
171 * we are editing anyway
173 char newfile; /* file doesn't exist yet */
174 bool autosave_exists;
177 const char *autosave_name;
178 struct text_edit *saved;
186 #include "core-pane.h"
188 static int text_advance_towards(struct text *t safe, struct doc_ref *ref safe,
189 struct doc_ref *target safe);
190 static int text_retreat_towards(struct text *t safe, struct doc_ref *ref safe,
191 struct doc_ref *target safe);
192 static bool text_ref_same(struct text *t safe, struct doc_ref *r1 safe,
193 struct doc_ref *r2 safe);
194 static bool _text_ref_same(struct text *t safe, struct doc_ref *r1 safe,
195 struct doc_ref *r2 safe);
196 static int text_locate(struct text *t safe, struct doc_ref *r safe,
197 struct doc_ref *dest safe);
198 static void text_check_consistent(struct text *t safe);
199 static void text_normalize(struct text *t safe, struct doc_ref *r safe);
200 static void text_cleanout(struct text *t safe);
201 static void text_add_str(struct text *t safe, struct mark *pm safe,
202 const char *str safe, off_t size,
203 bool *first_edit safe);
204 static void text_check_autosave(struct pane *p safe);
205 static bool check_readonly(const struct cmd_info *ci safe);
207 static MEMPOOL(text);
208 static MEMPOOL(undo);
209 static struct map *text_map;
211 static struct text_alloc *safe
212 text_new_alloc(struct text *t safe, int size)
214 struct text_alloc *new;
217 size += sizeof(struct text_alloc);
218 size = ((size-1) | 255) + 1;
219 new = alloc_buf(size, text);
220 new->prev = t->alloc;
222 new->size = size - sizeof(struct text_alloc);
227 static bool check_file_changed(struct pane *p safe)
229 struct text *t = p->doc_data;
233 /* '1' means it has changed, '2' means "but we don't care" */
234 return t->file_changed == 1;
237 if (stat(t->fname, &st) != 0) {
238 memset(&st, 0, sizeof(st));
242 if (st.st_ino != t->stat.st_ino ||
243 st.st_dev != t->stat.st_dev ||
244 st.st_mtime != t->stat.st_mtime ||
245 st.st_mtim.tv_nsec != t->stat.st_mtim.tv_nsec) {
247 call("doc:notify:doc:status-changed", p);
253 DEF_CMD(text_readonly)
255 struct doc *d = ci->home->data;
256 struct text *t = ci->home->doc_data;
258 if (t->file_changed && !d->readonly && ci->num)
260 /* Use default handling */
264 static const char *safe autosave_name(const char *name safe)
266 char *tempname = malloc(strlen(name) + 3 + 10);
270 strcpy(tempname, name);
271 base = strrchr(name, '/');
276 tbase = tempname + (base - name);
277 sprintf(tbase, "#%s#", base);
281 DEF_CMD(text_load_file)
284 const char *name = ci->str;
286 struct text_alloc *a;
287 struct text_chunk *c = NULL;
289 struct text *t = ci->home->doc_data;
291 if (t->saved != t->undo)
293 if (fd < 0 && (ci->num & 6) && t->fname) {
294 /* re-open existing file name */
296 fd = open(t->autosave_name, O_RDONLY);
298 fd = open(t->fname, O_RDONLY);
305 size = lseek(fd, 0, SEEK_END);
306 lseek(fd, 0, SEEK_SET);
310 if ((ci->num & 1) && t->fname && fd >= 0) {
314 if (stb.st_ino == t->stat.st_ino &&
315 stb.st_dev == t->stat.st_dev &&
316 stb.st_size == t->stat.st_size &&
317 stb.st_mtime == t->stat.st_mtime) {
328 a = text_new_alloc(t, size);
331 while (a->free < size &&
332 (len = read(fd, a->text + a->free, size - a->free)) > 0)
339 list_add(&c->lst, &t->text);
340 hlist_for_each_entry(m, &t->doc.marks, all) {
348 if (fstat(fd, &t->stat) < 0) {
350 memset(&t->stat, 0, sizeof(t->stat));
352 if (name != t->fname) {
354 free((void*)t->fname);
355 t->fname = strdup(name);
356 dname = strrchr(name, '/');
361 call("doc:set-name", ci->home, 0, NULL, dname);
363 if (!t->autosave_name)
364 t->autosave_name = autosave_name(name);
365 if (stat(t->autosave_name, &stb) == 0 &&
366 stb.st_mtime > t->stat.st_mtime)
367 t->autosave_exists = True;
370 /* restored from autoload, so nothing matches saved version */
374 /* Current state is 'saved' */
378 call("doc:notify:doc:status-changed", ci->home);
379 pane_notify("doc:replaced", ci->home);
390 DEF_CMD(text_insert_file)
392 struct text *t = ci->home->doc_data;
393 struct mark *pm = ci->mark, *early;
394 struct text_alloc *a;
399 bool status_changes = (t->undo == t->saved);
401 if (check_readonly(ci))
403 if (!pm || fd < 0 || fd == NO_NUMERIC)
405 size = lseek(fd, 0, SEEK_END);
406 lseek(fd, 0, SEEK_SET);
410 if (a->size - a->free < size)
411 a = text_new_alloc(t, size);
415 early = mark_dup(pm);
419 while (a->free < start + size &&
420 (len = read(fd, a->text + a->free, start + size - a->free)) > 0)
422 text_add_str(t, pm, a->text + start, size, &first);
424 text_check_consistent(t);
425 text_check_autosave(ci->home);
427 call("doc:notify:doc:status-changed", ci->home);
428 pane_notify("doc:replaced", ci->home, 0, early, NULL,
435 static bool do_text_output_file(struct pane *p safe, struct doc_ref *start,
436 struct doc_ref *end, int fd)
438 struct text *t = p->doc_data;
439 struct text_chunk *c;
446 c = list_first_entry_or_null(&t->text, struct text_chunk, lst);
448 list_for_each_entry_from(c, &t->text, lst) {
449 char *s = c->txt + c->start;
450 int ln = c->end - c->start;
451 if (end && end->c == c)
453 if (write(fd, s + offset, ln - offset) != ln - offset)
456 if (end && end->c == c)
464 static bool do_text_write_file(struct pane *p safe, struct doc_ref *start,
466 const char *fname safe)
468 /* Don't worry about links for now
469 * Create a temp file with #basename#~, write to that,
470 * copy mode across, fsync and then rename
472 struct text *t = p->doc_data;
473 char *tempname = malloc(strlen(fname) + 3 + 10);
480 strcpy(tempname, fname);
481 base = strrchr(fname, '/');
486 tbase = tempname + (base - fname);
487 while (cnt < 20 && fd == -1) {
489 sprintf(tbase, "#%s#~%d", base, cnt);
491 sprintf(tbase, "#%s#~", base);
492 fd = open(tempname, O_WRONLY|O_CREAT|O_EXCL, 0666);
493 if (fd < 0 && errno != EEXIST)
500 if (!do_text_output_file(p, start, end, fd))
502 if (stat(fname, &stb) == 0 &&
503 S_ISREG(stb.st_mode))
504 /* Preserve modes, but not setuid */
505 fchmod(fd, stb.st_mode & 0777);
506 if (fname == t->fname && check_file_changed(p)) {
507 /* We are saving to a file which changed since we read it,
508 * so let's move that changed file to a backup
512 for (i = 1 ; i < 1000; i++) {
514 if (asprintf(&new, "%s~%d~", fname, i) < 0)
516 if (link(fname, new) == 0) {
525 if (rename(tempname, fname) < 0)
539 static void autosaves_record(struct pane *p safe, const char *path safe,
543 char *home = getenv("HOME");
544 char *dirname = getenv("EDLIB_AUTOSAVE");
546 bool changed = False;
551 dirname = strconcat(p, home, "/.edlib_autosave");
552 d = opendir(dirname);
556 if (mkdir(dirname, 0770) < 0)
558 d = opendir(dirname);
566 while ((de = readdir(d)) != NULL) {
570 char current[PATH_MAX];
572 if (de->d_name[0] == '.')
574 n = strtoul(de->d_name, &ep, 10);
575 if (!ep || ep == de->d_name || *ep != '\0')
579 len = readlinkat(dirfd(d), de->d_name,
580 current, sizeof(current));
581 if (len <= 0 || len >= (int)sizeof(current))
584 if (strcmp(current, path) == 0) {
586 unlinkat(dirfd(d), de->d_name, 0);
596 snprintf(nbuf, sizeof(nbuf), "%d", num);
597 symlinkat(path, dirfd(d), nbuf);
601 doc = call_ret(pane, "doc:open", p, -1, NULL, dirname);
603 pane_call(doc, "doc:notify:doc:revisit", p);
608 static void do_text_autosave(struct pane *p safe)
610 struct text *t = p->doc_data;
615 check_file_changed(p);
617 if (!t->autosave_name)
618 t->autosave_name = autosave_name(t->fname);
619 if (t->as.changes == 0) {
620 unlink(t->autosave_name);
621 t->autosave_exists = False;
622 autosaves_record(p, t->fname, False);
625 fd = open(t->autosave_name, O_WRONLY|O_CREAT|O_TRUNC, 0666);
629 if (!do_text_output_file(p, NULL, NULL, fd)) {
631 unlink(t->autosave_name);
636 autosaves_record(p, t->fname, True);
639 DEF_CMD(text_autosave_delete)
641 struct pane *home = ci->home;
642 struct text *t = home->data;
643 const char *name = ci->str;
646 if (!t->fname || !name)
649 if (!t->autosave_name)
650 t->autosave_name = autosave_name(t->fname);
652 if (strcmp(name, t->autosave_name) != 0 ||
653 unlink(t->autosave_name) < 0)
655 t->autosave_exists = False;
656 autosaves_record(home, t->fname, False);
661 DEF_CMD(text_autosave_tick)
663 struct pane *home = ci->home;
664 struct text *t = home->data;
666 t->as.timer_started = 0;
669 if (t->as.changes == 0)
670 /* This will delete the file */
671 do_text_autosave(home);
672 if (time(NULL) - t->as.last_change >= 30)
673 do_text_autosave(home);
675 t->as.timer_started = 1;
676 call_comm("event:timer", home, &text_autosave_tick,
677 (t->as.last_change + 30 - time(NULL)) * 1000);
682 static void text_check_autosave(struct pane *p safe)
684 struct text *t = p->doc_data;
686 if (t->undo == t->saved)
690 t->as.last_change = time(NULL);
693 if (t->as.changes > 300 || t->as.changes == 0)
695 else if (!t->as.timer_started) {
696 t->as.timer_started = 1;
697 call_comm("event:timer", p, &text_autosave_tick,
702 DEF_CMD(text_save_file)
704 struct doc *d = ci->home->data;
705 struct text *t = ci->home->doc_data;
708 int change_status = 0;
711 asprintf(&msg, "** No file name known for %s ***", d->name);
714 ret = do_text_write_file(ci->home, NULL, NULL, t->fname);
716 asprintf(&msg, "Successfully wrote %s", t->fname);
722 asprintf(&msg, "*** Failed to write %s ***", t->fname);
724 call("Message", ci->focus, 0, NULL, msg);
727 call("doc:notify:doc:status-changed", ci->home);
728 text_check_autosave(ci->home);
734 DEF_CMD(text_write_file)
737 bool use_marks = ci->mark && ci->mark2;
740 ret = do_text_write_file(ci->home,
741 use_marks ? &ci->mark->ref: NULL,
742 use_marks ? &ci->mark2->ref: NULL,
744 return ret ? 1 : Efail;
746 if (ci->num >= 0 && ci->num != NO_NUMERIC) {
747 ret = do_text_output_file(ci->home,
748 use_marks ? &ci->mark->ref: NULL,
749 use_marks ? &ci->mark2->ref: NULL,
751 return ret ? 1 : Efail;
756 DEF_CMD(text_same_file)
758 struct text *t = ci->home->doc_data;
759 struct stat stb, stb2;
762 if (t->fname == NULL)
764 if (ci->str && strcmp(ci->str, t->fname) == 0)
767 if (fstat(fd, &stb) != 0)
769 } else if (ci->str) {
770 if (stat(ci->str, &stb) != 0)
774 if (t->stat.st_ino != stb.st_ino ||
775 t->stat.st_dev != stb.st_dev)
777 /* Must check file hasn't changed beneath us */
778 if (stat(t->fname, &stb2) != 0)
780 if (stb2.st_ino == stb.st_ino &&
781 stb2.st_dev == stb.st_dev)
786 static void text_add_edit(struct text *t safe, struct text_chunk *target safe,
787 bool *first safe, int at_start, int len)
794 if (t->saved == t->undo)
795 /* Must never merge undo entries across a save point */
799 /* Cannot add an edit before some redo edits, as they
800 * will get confused. We need to record the redo history
801 * here in the undo history, possibly allocating
802 * a nop edit (len == 0)
804 if (t->undo == NULL || t->undo->altnext != NULL) {
806 e->target = target; /* ignored */
809 e->len = 0; /* This is a no-op */
813 t->undo->altnext = t->redo;
814 t->undo->alt_is_second = 0;
817 /* factoring out t->undo here avoids a bug in smatch. */
819 if (e && e->len != 0 && e->len + len != 0 && !*first &&
820 e->target == target && e->at_start == at_start) {
821 /* This new edit can be merged with the previous one */
827 e->at_start = at_start;
832 e->alt_is_second = 0;
837 static void _text_add_str(struct text *t safe, struct doc_ref *pos safe,
838 const char *str safe, off_t len,
839 struct doc_ref *start, bool *first_edit safe)
841 /* Text is added to the end of the referenced chunk, or
842 * in new chunks which are added afterwards. This allows
843 * the caller to reliably updated any pointers to accommodate
845 * The added text has no attributes.
847 * 'pos' is moved to point to the end of the inserted text.
848 * 'start' is set to point to the start which may be the
849 * original 'pos', or may not if a chunk was inserted.
851 /* easy/common case first: pos is at the end of a chunk,
852 * which is the last chunk in the current allocation.
854 struct text_alloc *a = t->alloc;
865 if (pos->c && pos->o == pos->c->end &&
866 pos->c->txt + pos->o == a->text + a->free &&
867 str != a->text + a->free &&
868 (a->size - a->free >= len ||
869 (len2 = utf8_round_len(str, a->size - a->free)) > 0)) {
870 /* Some of this ('len2') can be added to the current chunk */
871 memcpy(a->text+a->free, str, len2);
876 text_add_edit(t, pos->c, first_edit, 0, len2);
881 /* Need a new chunk. Might need to split the current chunk first.
882 * Old chunk must be first to simplify updating of pointers */
883 if (pos->c == NULL || pos->o < pos->c->end) {
884 struct text_chunk *c;
886 if (pos->c == NULL || pos->o == pos->c->start) {
887 /* At the start of a chunk, so create a new one here */
888 c->txt = safe_cast NULL;
889 c->start = c->end = 0;
892 list_add_tail(&c->lst, &pos->c->lst);
894 list_add_tail(&c->lst, &t->text);
896 if (start && start->c == pos->c && start->o == pos->o) {
903 /* Not at the start, so we need to split at pos->o */
904 c->txt = pos->c->txt;
906 c->end = pos->c->end;
907 c->attrs = attr_copy_tail(pos->c->attrs, c->start);
908 pos->c->end = pos->o;
909 attr_trim(&pos->c->attrs, pos->c->end);
910 list_add(&c->lst, &pos->c->lst);
911 text_add_edit(t, c, first_edit, 0, c->end - c->start);
912 /* this implicitly truncates pos->c, so don't need
917 /* Make sure we have an empty chunk */
918 if (pos->c->end > pos->c->start) {
919 struct text_chunk *c;
921 c->start = c->end = 0;
923 list_add(&c->lst, &pos->c->lst);
924 if (start && start->c == pos->c && start->o == pos->o) {
931 /* Make sure we have some space in 'a' */
933 if (a->size - a->free < len &&
934 (len2 = utf8_round_len(str, a->size - a->free)) == 0) {
935 if (orig_len < 128 ||
936 t->alloc->size < DEFAULT_SIZE)
937 a = text_new_alloc(t, DEFAULT_SIZE);
938 else if (len > DEFAULT_SIZE && len > t->alloc->size)
939 a = text_new_alloc(t, ((len +256) | 4095) + 1 - 256);
940 else if (t->alloc->size * 2 < MAX_SIZE)
941 a = text_new_alloc(t, t->alloc->size * 2);
943 a = text_new_alloc(t, MAX_SIZE);
946 len2 = utf8_round_len(str, a->size);
948 pos->c->txt = a->text + a->free;
951 if (str != pos->c->txt)
952 memcpy(pos->c->txt, str, len2);
953 text_add_edit(t, pos->c, first_edit, 0, len2);
960 /* Text insertion, deletion, and undo can modify chunks which various
961 * marks point to - so those marks will need to be updated.
962 * Modification include splitting a chunk, inserting chunks,
963 * or deleting chunks and recombining chunks (for undo).
964 * Also reducing or increasing the range of a chunk.
965 * When a chunk is split, the original becomes the first part.
966 * So any mark pointing past the end of that original must be moved
968 * When a chunk is deleted, any mark pointing to a deleted chunk
969 * must be redirected to the (new) point of deletion.
970 * When a chunk is inserted, marks before the insertion mark must remain
971 * before the inserted chunk, marks after must remain after the insertion
973 * When two chunks are recombined it will appear that the second chunk
974 * was deleted. In this case though, references to the second chunk need
975 * to be repositioned in the first.
976 * When range is reduced, offset must be moved back into range.
977 * When range is increased, and this mark is after change, offset in this mark
978 * need to line up with changed point.
980 * So text_update_prior_after_change() is called on marks before the
981 * mark-of-change in reverse order until the function returns zero.
982 * If it finds a mark pointing to a deleted chunk, that mark changes to
983 * point the same place as the mark-of-change.
984 * If it finds a mark at, or immediately after, the mark-of-change,
985 * that mark is moved to point to the start of insert.
987 * Then text_update_following_after_change() is called on marks after
988 * the mark-of-change in order until that function returns zero.
989 * If a mark points outside the range of a chunk, the other half of the
990 * chunk is found (by walking forward) and the pointer is updated.
991 * If a deleted chunk is found, that mark is redirected to the mark-of-change.
992 * If a location at the start is found, it is move to the end.
995 static int text_update_prior_after_change(struct text *t safe,
996 struct doc_ref *pos safe,
997 struct doc_ref *spos safe,
998 struct doc_ref *epos safe)
1003 /* Was at the end, now must be at the start of the change */
1005 else if (pos->c->start >= pos->c->end)
1006 /* This chunk was deleted */
1008 else if (_text_ref_same(t, pos, epos))
1010 else if (pos->o < pos->c->start)
1011 /* Text deleted from under me */
1012 pos->o = pos->c->start;
1013 else if (pos->o > pos->c->end)
1014 /* Text deleted under me */
1015 pos->o = pos->c->end;
1016 else if (pos->o == pos->c->end)
1017 /* This mark is OK, but previous mark might be
1018 * at start of next chunk, so keep looking
1022 /* no insert or delete here, so all done */
1024 text_normalize(t, pos);
1028 static int text_update_following_after_change(struct text *t safe,
1029 struct doc_ref *pos safe,
1030 struct doc_ref *spos safe,
1031 struct doc_ref *epos safe)
1033 /* A change has happened between spos and epos. pos should be
1036 struct text_chunk *c;
1042 if (pos->c->start >= pos->c->end) {
1043 /* This chunk was deleted */
1045 pos->c->txt == epos->c->txt &&
1046 pos->o >= epos->c->start &&
1047 pos->o <= epos->c->end)
1048 /* chunks were rejoined */
1052 } else if (pos->c == epos->c &&
1054 /* Text inserted, need to push forward. */
1056 else if (pos->o < pos->c->start)
1057 /* must have been deleted... */
1058 pos->o = pos->c->start;
1059 else if (pos->o > pos->c->end) {
1060 /* This was split, or text was deleted off the end */
1064 list_for_each_entry_from(c, &t->text, lst) {
1065 if (c->txt == pos->c->txt &&
1066 c->start <= pos->o &&
1072 if (pos->o > pos->c->end)
1073 /* no split found, so just a delete */
1074 pos->o = pos->c->end;
1075 } else if (_text_ref_same(t, pos, spos))
1077 else if (pos->o == pos->c->start)
1078 /* This mark is OK, but next mark might be
1079 * at end of previous chunk, so keep looking
1083 /* This is beyond the change point and no deletion or split
1084 * happened here, so all done.
1087 text_normalize(t, pos);
1091 static void text_del(struct text *t safe, struct doc_ref *pos safe,
1092 unsigned int len, bool *first_edit safe)
1095 struct text_chunk *c = pos->c;
1097 /* nothing more to delete */
1099 if (pos->o == c->start &&
1100 len >= c->end - c->start) {
1101 /* The whole chunk is deleted, simply disconnect it */
1102 if (c != list_last_entry(&t->text,
1103 struct text_chunk, lst)) {
1104 pos->c = list_next_entry(c, lst);
1105 pos->o = pos->c->start;
1106 } else if (c != list_first_entry(&t->text,
1109 pos->c = list_prev_entry(c, lst);
1110 pos->o = pos->c->end;
1112 /* Deleted final chunk */
1116 __list_del(c->lst.prev, c->lst.next); /* no poison,
1119 attr_free(&c->attrs);
1120 text_add_edit(t, c, first_edit, 0, c->start - c->end);
1121 len -= c->end - c->start;
1122 /* make sure undo knows this is empty at not attached */
1124 } else if (pos->o == c->start) {
1125 /* If the start of the chunk is deleted, just update.
1126 * Note that len must be less that full size, else
1127 * previous branch would have been taken.
1132 s = attr_copy_tail(c->attrs, c->start);
1133 attr_free(&c->attrs);
1135 text_add_edit(t, c, first_edit, 1, len);
1137 } else if (c->end - pos->o <= len) {
1138 /* If the end of the chunk is deleted, just update
1139 * and move forward */
1140 int diff = c->end - pos->o;
1143 attr_trim(&c->attrs, c->end);
1144 text_add_edit(t, c, first_edit, 0, -diff);
1145 if (len && c != list_last_entry(&t->text,
1148 pos->c = list_next_entry(c, lst);
1149 pos->o = pos->c->start;
1153 /* must be deleting out of the middle of the chunk.
1154 * need to create new chunk for the 'after' bit.
1156 struct text_chunk *c2;
1159 c2->start = pos->o + len;
1162 c2->attrs = attr_copy_tail(c->attrs, c2->start);
1163 attr_trim(&c->attrs, c->end);
1164 list_add(&c2->lst, &c->lst);
1165 /* This implicitly trims c, so we only have len
1167 text_add_edit(t, c2, first_edit, 0,
1168 c2->end - c2->start);
1169 text_add_edit(t, c, first_edit, 0, -len);
1175 /* text_undo and text_redo:
1177 * The 'start' and 'end' reported identify the range changed. For a reversed
1178 * insertion they will be the same. If the undo results in the buffer being
1179 * empty, both start and end will point to a NULL chunk.
1180 * When undoing a split, both will be at the point of the split.
1182 static void text_undo(struct text *t safe, struct text_edit *e safe,
1183 struct doc_ref *start safe, struct doc_ref *end safe)
1188 if (e->target->end == e->target->start) {
1189 /* need to re-link */
1190 struct list_head *l = e->target->lst.prev;
1191 if (e->target->lst.next != l->next) abort();
1192 list_add(&e->target->lst, l);
1194 start->c = end->c = e->target;
1195 start->o = e->target->end; // incase was deletion at end
1196 end->o = e->target->start; // incase was deletion at start
1198 e->target->start -= e->len;
1200 /* was deletion, this is insertion */
1201 start->o = e->target->start;
1203 /* was insertion - not really possible */
1204 start->o = end->o = e->target->start;
1206 e->target->end -= e->len;
1208 /* Was insertion, now deleting */
1209 start->o = end->o = e->target->end;
1211 /* Was deletion, now inserting */
1212 end->o = e->target->end;
1214 if (e->target->start == e->target->end) {
1215 /* The undo deletes this chunk, so it must have been inserted,
1216 * either as new text or for a chunk-split.
1217 * If new text, leave start/end pointing just past the chunk.
1218 * if split, leave them at the point of splitting.
1220 if (e->target == list_last_entry(&t->text,
1221 struct text_chunk, lst)) {
1225 end->c = list_next_entry(e->target, lst);
1226 end->o = end->c->start;
1230 __list_del(e->target->lst.prev, e->target->lst.next);
1231 /* If this was created for a split, we need to extend the
1234 if (e->target != list_first_entry(&t->text,
1235 struct text_chunk, lst)) {
1236 struct text_chunk *c = list_prev_entry(e->target, lst);
1237 start->c = end->c = c;
1238 start->o = end->o = c->end;
1239 if (c->txt == e->target->txt &&
1240 c->end == e->target->start &&
1247 static void text_redo(struct text *t safe, struct text_edit *e safe,
1248 struct doc_ref *start safe, struct doc_ref *end safe)
1256 if (e->target->end == e->target->start) {
1257 /* need to re-link */
1258 struct list_head *l = e->target->lst.prev;
1259 if (e->target->lst.next != l->next) abort();
1260 list_add(&e->target->lst, l);
1261 /* If this is a split, need to truncate prior */
1262 if (e->target != list_first_entry(&t->text,
1263 struct text_chunk, lst)) {
1264 struct text_chunk *c = list_prev_entry(e->target, lst);
1265 if (c->txt == e->target->txt &&
1266 c->end > e->target->start) {
1267 c->end = e->target->start;
1272 start->c = end->c = e->target;
1273 end->o = e->target->start; // incase is insertion at start
1274 start->o = e->target->end; // incase inserting at end
1276 e->target->start += e->len;
1278 /* deletion at start */
1279 start->o = end->o = e->target->start;
1281 /* Insertion at start, not currently possible */
1282 start->o = e->target->start;
1284 e->target->end += e->len;
1286 start->o = end->o = e->target->start;
1287 else if (e->len > 0)
1288 /* Insertion at end */
1289 end->o = e->target->end;
1291 /* Deletion at end */
1292 start->o = end->o = e->target->end;
1294 if (e->target->start == e->target->end) {
1295 /* This chunk is deleted, so leave start/end pointing
1297 if (e->target->lst.next == &t->text) {
1301 end->c = list_next_entry(e->target, lst);
1302 end->o = end->c->start;
1306 __list_del(e->target->lst.prev, e->target->lst.next);
1310 static bool check_readonly(const struct cmd_info *ci safe)
1312 struct doc *d = ci->home->data;
1313 struct text *t = ci->home->doc_data;
1315 if (t->undo == t->saved &&
1316 check_file_changed(ci->home) &&
1318 call("doc:notify:doc:status-changed", ci->home);
1323 call("Message", ci->focus, 0, NULL, "Document is read-only");
1327 DEF_CMD(text_reundo)
1329 struct mark *m = ci->mark;
1330 struct doc_ref start, end;
1332 struct text_edit *ed = NULL;
1335 struct text *t = ci->home->doc_data;
1340 if (check_readonly(ci))
1344 /* New undo sequence - do redo first */
1345 t->prev_edit = Redo;
1347 status = (t->undo == t->saved);
1351 struct mark *early = NULL;
1356 if (t->prev_edit <= Redo && t->redo) {
1358 text_redo(t, ed, &start, &end);
1361 ed->alt_is_second = 0;
1362 t->prev_edit = Redo;
1364 last = t->redo == NULL || t->redo->first;
1365 } else if (t->prev_edit <= Undo &&
1367 t->undo->altnext && !t->undo->alt_is_second) {
1368 ed = t->undo->altnext;
1369 text_redo(t, ed, &start, &end);
1370 t->prev_edit = Redo;
1371 t->undo->altnext = t->redo;
1372 t->undo->alt_is_second = 1;
1375 ed->alt_is_second = 0;
1377 last = t->redo == NULL || t->redo->first;
1378 } else if (t->undo) {
1380 text_undo(t, ed, &start, &end);
1382 if (ed->alt_is_second) {
1383 t->prev_edit = AltUndo;
1384 ed->next = ed->altnext;
1385 ed->altnext = t->redo;
1387 t->prev_edit = Undo;
1397 /* That was just a no-op, keep going */
1400 text_normalize(t, &start);
1401 text_normalize(t, &end);
1404 where = text_locate(t, &m->ref, &end);
1406 /* Not nearby, look from the start */
1407 mark_reset(ci->home, m, 0);
1412 t->revising_marks = True;
1416 struct doc_ref tmp = m->ref;
1417 i = text_advance_towards(t, &tmp, &end);
1420 while ((m2 = mark_next(m)) != NULL &&
1421 m2->ref.c == tmp.c &&
1423 mark_to_mark_noref(m, m2);
1429 struct doc_ref tmp = m->ref;
1430 i = text_retreat_towards(t, &tmp, &end);
1433 while ((m2 = mark_prev(m)) != NULL &&
1434 m2->ref.c == tmp.c &&
1436 mark_to_mark_noref(m, m2);
1440 t->revising_marks = False;
1442 if (!_text_ref_same(t, &m->ref, &end))
1445 /* point is now at location of undo */
1448 hlist_for_each_entry_continue_reverse(m2, all)
1449 if (text_update_prior_after_change(t, &m2->ref,
1453 hlist_for_each_entry_continue(m2, all)
1454 if (text_update_following_after_change(t, &m2->ref,
1459 text_normalize(t, &m->ref);
1460 if (text_ref_same(t, &start, &end))
1463 early = mark_dup(m);
1464 mark_step(early, 0);
1465 /* There cannot be any mark between start and end,
1466 * so it is safe to assign 'ref' here.
1470 pane_notify("doc:replaced", ci->home,
1476 text_check_consistent(t);
1478 } while (ed && !last);
1480 text_check_consistent(t);
1482 if (status != (t->undo == t->saved))
1483 call("doc:notify:doc:status-changed", ci->home);
1484 text_check_autosave(ci->home);
1487 t->prev_edit = Redo;
1488 return ed ? 1 : Efalse;
1493 static int common_prefix(char *a, char *b, int l)
1502 /* Compare a string with the text.
1503 * Update the ref passed all matching chars.
1504 * Return length that was matched.
1506 static int text_str_cmp(struct text *t, struct doc_ref *r, char *s)
1508 struct text_chunk *c = r->c;
1515 list_for_each_entry_from(c, &t->text, lst) {
1521 l = common_prefix(c->txt+o, s, l);
1526 if (l == c->end - o)
1537 static void text_normalize(struct text *t safe, struct doc_ref *r safe)
1539 /* Adjust so at not at the end of a chunk - either ->o points
1540 * at a byte, or ->c is NULL.
1542 while (r->c && r->o >= r->c->end) {
1543 if (r->c->lst.next == &t->text) {
1547 r->c = list_next_entry(r->c, lst);
1553 static void text_denormalize(struct text *t safe, struct doc_ref *r safe)
1555 /* Ensure r->o is after some byte, or at start of file. */
1556 if (r->c && r->o > r->c->start)
1560 if (list_empty(&t->text))
1562 r->c = list_entry(t->text.prev, struct text_chunk, lst);
1566 if (r->c->lst.prev == &t->text)
1568 r->c = list_prev_entry(r->c, lst);
1572 static void text_add_str(struct text *t safe, struct mark *pm safe,
1573 const char *str safe, off_t size, bool *first safe)
1575 struct doc_ref start;
1578 text_denormalize(t, &pm->ref);
1579 _text_add_str(t, &pm->ref, str, size, &start, first);
1580 text_normalize(t, &pm->ref);
1581 for (m = mark_prev(pm);
1582 m && text_update_prior_after_change(t, &m->ref,
1586 for (m = mark_next(pm);
1587 m && text_update_following_after_change(t, &m->ref,
1593 static inline wint_t text_next(struct pane *p safe, struct doc_ref *r safe, bool bytes)
1595 struct text *t = p->doc_data;
1599 text_normalize(t, r);
1603 c = r->c->txt + r->o;
1605 ret = get_utf8(&c, r->c->txt + r->c->end);
1607 r->o = c - r->c->txt;
1609 ret = (unsigned char)r->c->txt[r->o++];
1610 text_normalize(t, r);
1614 static inline wint_t text_prev(struct pane *p safe, struct doc_ref *r safe, bool bytes)
1616 struct text *t = p->doc_data;
1620 text_denormalize(t, r);
1621 if (list_empty(&t->text))
1623 if (r->c == NULL || r->o <= r->c->start)
1624 // assert (r->c->lst.prev == &t->text)
1630 r->o = r->c->start +
1631 utf8_round_len(r->c->txt+r->c->start,
1632 r->o - r->c->start - 1);
1633 c = r->c->txt + r->o;
1634 ret = get_utf8(&c, r->c->txt + r->c->end);
1639 ret = (unsigned char)r->c->txt[r->o];
1643 DEF_CMD(text_char_byte)
1645 return do_char_byte(ci);
1647 static bool _text_ref_same(struct text *t safe, struct doc_ref *r1 safe,
1648 struct doc_ref *r2 safe)
1650 if (r1->c == r2->c) {
1651 return r1->o == r2->o;
1653 if (r1->c == NULL /*FIXME redundant*/ && r2->c != NULL) {
1654 if (list_empty(&t->text))
1656 return (r2->o == r2->c->end &&
1657 r2->c->lst.next == &t->text);
1659 if (r2->c == NULL /* FIXME redundant*/ && r1->c != NULL) {
1660 if (list_empty(&t->text))
1662 return (r1->o == r1->c->end &&
1663 r1->c->lst.next == &t->text);
1665 /* FIXME impossible */
1666 if (r1->c == NULL || r2->c == NULL) return False;
1668 if (r1->o == r1->c->end &&
1669 r2->o == r2->c->start &&
1670 list_next_entry(r1->c, lst) == r2->c)
1672 if (r1->o == r1->c->start &&
1673 r2->o == r2->c->end &&
1674 list_prev_entry(r1->c, lst) == r2->c)
1679 static bool text_ref_same(struct text *t safe, struct doc_ref *r1 safe,
1680 struct doc_ref *r2 safe)
1682 bool ret = _text_ref_same(t, r1, r2);
1683 ASSERT(ret == (r1->c == r2->c && r1->o == r2->o));
1687 DEF_LOOKUP_CMD(text_handle, text_map);
1694 p = doc_register(ci->home, &text_handle.c);
1698 t->alloc = safe_cast NULL;
1699 INIT_LIST_HEAD(&t->text);
1700 t->saved = t->undo = t->redo = NULL;
1701 t->prev_edit = Redo;
1703 t->file_changed = 0;
1706 t->as.timer_started = 0;
1707 t->as.last_change = 0;
1708 text_new_alloc(t, 0);
1710 return comm_call(ci->comm2, "callback:doc", p);
1715 if (ci->num2 != S_IFREG)
1716 return Efallthrough;
1717 return text_new_func(ci);
1720 static int count_bytes(struct text *t safe, struct mark *from, struct mark *to)
1722 struct text_chunk *c, *first, *last;
1723 int l = 0, head, tail;
1725 first = list_first_entry_or_null(&t->text, struct text_chunk, lst);
1727 if (from && from->ref.c) {
1728 first = from->ref.c;
1729 head = from->ref.o - first->start;
1733 if (to && to->ref.c) {
1735 tail = last->end - to->ref.o;
1738 list_for_each_entry_from(c, &t->text, lst) {
1739 l += c->end - c->start;
1750 DEF_CMD(text_content)
1752 struct mark *from = ci->mark, *to = ci->mark2;
1754 struct text *t = ci->home->doc_data;
1755 struct text_chunk *c, *first, *last;
1756 int bytes = strcmp(ci->key, "doc:content-bytes") == 0;
1757 int l = 0, head, tail;
1764 first = from->ref.c;
1766 head = from->ref.o - first->start;
1770 /* Calculate size so comm2 can pre-allocate */
1773 tail = last->end - to->ref.o;
1776 list_for_each_entry_from(c, &t->text, lst) {
1777 l += c->end - c->start;
1789 list_for_each_entry_from(c, &t->text, lst) {
1791 const char *s = c->txt + c->start;
1792 int ln = c->end - c->start;
1799 if (m->ref.c != c) {
1800 while ((m2 = mark_next(m)) &&
1801 m2->ref.c == m->ref.c)
1802 mark_to_mark(m, m2);
1804 m->ref.o = c->start;
1814 wc = get_utf8(&s, s+ln);
1819 while ((m2 = mark_next(m)) &&
1820 m2->ref.c == m->ref.c &&
1821 m2->ref.o <= s - c->txt)
1822 mark_to_mark(m, m2);
1823 m->ref.o = s - c->txt;
1824 text_normalize(t, &m->ref);
1827 /* Interpreted can see " unterminated" and know
1828 * than ->num2 is the length of ->str
1830 rv = comm_call(ci->comm2, "consume unterminated",
1832 wc, m, s, ln, NULL, NULL, size, 0);
1834 if (rv <= 0 || rv > ln + 1) {
1838 } else if (rv > 1) {
1839 /* consumed (some of) str */
1851 DEF_CMD(text_debug_mark)
1854 struct text_chunk *c;
1855 struct mark *m = ci->mark;
1857 if (!m || m->owner != ci->home || !ci->comm2)
1861 ret = strdup("M:FREED");
1863 ret = strdup("M:EOF");
1865 unsigned int len = c->end - c->start;
1866 unsigned int o = ci->mark->ref.o;
1868 if (o <= c->start + 4 || len <= 8) {
1871 asprintf(&ret, "M:(%.*s[%d])", len,
1872 c->txt + c->start, m->ref.o);
1874 len = c->end - m->ref.o;
1877 asprintf(&ret, "M:(%.4s..[%d]%.*s)",
1879 m->ref.o, len, c->txt + m->ref.o);
1882 comm_call(ci->comm2, "cb", ci->focus, 0, NULL, ret);
1887 DEF_CMD(text_val_marks)
1889 struct text *t = ci->home->doc_data;
1890 struct text_chunk *c;
1893 if (!ci->mark || !ci->mark2)
1896 if (t->revising_marks)
1899 if (ci->mark->ref.c == ci->mark2->ref.c) {
1900 if (ci->mark->ref.o < ci->mark2->ref.o)
1902 LOG("text_val_marks: same buf, bad offset: %u, %u",
1903 ci->mark->ref.o, ci->mark2->ref.o);
1907 list_for_each_entry(c, &t->text, lst) {
1908 if (ci->mark->ref.c == c)
1910 if (ci->mark2->ref.c == c) {
1913 LOG("text_val_marks: mark2.c found before mark1");
1917 if (ci->mark2->ref.c == NULL) {
1920 LOG("text_val_marks: mark2.c (NULL) found before mark1");
1924 LOG("text_val_marks: Neither mark found in chunk list");
1926 LOG("text_val_marks: mark2 not found in chunk list");
1930 DEF_CMD(text_set_ref)
1932 struct mark *m = ci->mark;
1933 struct text *t = ci->home->doc_data;
1937 mark_to_end(ci->home, m, ci->num != 1);
1938 if (list_empty(&t->text) || ci->num != 1) {
1942 m->ref.c = list_first_entry(&t->text, struct text_chunk, lst);
1943 m->ref.o = m->ref.c->start;
1948 static int text_advance_towards(struct text *t safe,
1949 struct doc_ref *ref safe,
1950 struct doc_ref *target safe)
1952 /* Move 'ref' towards 'target'.
1953 * If at end of chunk, step to next chunk, then
1954 * advance to 'target' or to end of chunk, whichever comes first.
1956 * 0 - reached end of text
1958 * 2 - on a new chunk, keep looking.
1960 if (ref->c && ref->o >= ref->c->end)
1961 text_normalize(t, ref);
1962 if (ref->c == target->c) {
1963 if (ref->o > target->o)
1964 /* will never find it */
1970 /* Reached EOF, haven't found */
1972 ref->o = ref->c->end;
1976 static int text_retreat_towards(struct text *t safe, struct doc_ref *ref safe,
1977 struct doc_ref *target safe)
1979 /* Move 'ref' towards 'target'.
1980 * If at start of chunk, step to previous chunk, then
1981 * retreat to 'target' or to start of chunk, whichever comes first.
1983 * 0 - reached start of text
1985 * 2 - on a new chunk, keep looking.
1988 if (ref->c != target->c && (!ref->c || ref->o <= ref->c->start))
1989 if (text_prev(safe_cast container_of(t, struct pane, doc_data[0]), ref, 1) == WEOF)
1992 if (ref->c == target->c) {
1995 if (target->o > ref->o)
2001 ref->o = ref->c->start;
2005 static int text_locate(struct text *t safe, struct doc_ref *r safe,
2006 struct doc_ref *dest safe)
2008 /* move back/forward a little from 'r' looking for 'dest'.
2009 * return 0 if not found, -1 if dest found before r.
2010 * return 1 if dest found after or at r.
2012 struct text_chunk *next, *prev;
2015 if (dest->c == NULL)
2020 if (dest->c == NULL)
2022 if (r->c == dest->c) {
2028 next = (r->c->lst.next == &t->text) ? NULL : list_next_entry(r->c, lst);
2029 prev = (r->c->lst.prev == &t->text) ? NULL : list_prev_entry(r->c, lst);
2030 if (next == dest->c)
2032 if (prev == dest->c)
2035 next = (next == NULL || next->lst.next == &t->text) ?
2036 NULL : list_next_entry(next, lst);
2037 prev = (prev == NULL || prev->lst.prev == &t->text) ?
2038 NULL : list_prev_entry(prev, lst);
2039 if (next == dest->c)
2041 if (prev == dest->c)
2046 static void check_allocated(struct text *t safe, char *buf safe, int len)
2048 struct text_alloc *ta = t->alloc;
2049 for (ta = t->alloc; ta; ta = ta->prev) {
2050 if (buf >= ta->text && buf+len <= ta->text + ta->free)
2056 static void text_ref_consistent(struct text *t safe, struct doc_ref *r safe,
2059 struct text_chunk *c;
2066 if (r->o >= r->c->end)
2068 if (r->o < r->c->start)
2070 list_for_each_entry(c, &t->text, lst) {
2071 if (r->c == c || *loops <= 0)
2078 static void text_check_consistent(struct text *t safe)
2080 /* make sure text is consistent, and abort if not.
2081 * - each chunk points to allocated space
2082 * - no two chunks overlap
2083 * - no chunks are empty
2084 * - every mark points to a valid chunk with valid offset
2085 * - all marks are in text order
2087 struct text_chunk *c;
2088 struct mark *m, *prev;
2089 struct doc *d = &t->doc;
2092 list_for_each_entry(c, &t->text, lst) {
2093 check_allocated(t, c->txt, c->end);
2094 if (c->start >= c->end)
2097 list_for_each_entry(c, &t->text, lst) {
2098 struct text_chunk *c2;
2099 list_for_each_entry(c2, &t->text, lst) {
2103 if (c->start >= c2->end)
2105 if (c2->start >= c->end)
2111 /* This test is quadratic in the number of marks, so let's
2112 * give up rather then annoy the users.
2115 for (m = mark_first(d); m; m = mark_next(m))
2116 text_ref_consistent(t, &m->ref, &loops);
2119 for (m = mark_first(d); m; m = mark_next(m)) {
2121 struct doc_ref r = prev->ref;/* SMATCH Bug things prev
2124 struct doc_ref r2 = m->ref;
2125 text_normalize(t, &r2);
2126 while ((i = text_advance_towards(t, &r,
2134 doc_check_consistent(d);
2137 static void text_add_attrs(struct attrset **attrs safe,
2138 const char *new safe, int o)
2141 char *cpy = strdup(new);
2155 attr_set_str_key(attrs, k, v, o);
2161 DEF_CMD(text_replace)
2164 struct text *t = ci->home->doc_data;
2165 struct mark *pm = ci->mark2;
2166 struct mark *end = ci->mark;
2167 const char *str = ci->str;
2168 const char *newattrs = ci->str2;
2169 bool first = !ci->num2;
2170 struct mark *early = NULL;
2171 int status_change = 0;
2173 if (check_readonly(ci))
2177 /* Default to insert at end */
2178 pm = point_new(ci->home);
2181 mark_reset(ci->home, pm, 1);
2184 /* First delete, then insert */
2185 if (end && !text_ref_same(t, &pm->ref, &end->ref)) {
2186 struct mark *myend, *m;
2189 if (t->undo == t->saved)
2192 if (pm->seq >= end->seq) {
2193 myend = mark_dup(pm);
2194 mark_to_mark(pm, end);
2196 myend = mark_dup(end);
2197 /* pm is at the start, myend is at the end */
2198 l = count_bytes(t, pm, myend);
2200 text_del(t, &pm->ref, l, &first);
2201 text_normalize(t, &pm->ref);
2203 for (m = mark_prev(pm);
2204 m && text_update_prior_after_change(t, &m->ref,
2205 &pm->ref, &pm->ref);
2208 for (m = mark_next(pm);
2209 m && text_update_following_after_change(t, &m->ref,
2214 text_check_consistent(t);
2216 if (end && end != pm)
2219 early = mark_dup(pm);
2220 /* leave "early" at the start of the insertion, and
2221 * pm moves to the end - they are both currently at
2222 * the same location in the doc.
2224 mark_step(early, 0);
2227 if (t->undo == t->saved)
2230 text_add_str(t, pm, str, -1, &first);
2231 if (newattrs && early->ref.c)
2232 text_add_attrs(&early->ref.c->attrs, newattrs,
2234 text_check_consistent(t);
2237 text_check_autosave(ci->home);
2239 call("doc:notify:doc:status-changed", ci->home);
2240 pane_notify("doc:replaced", ci->home, 0, early, NULL,
2246 return first ? 1 : 2;
2249 static struct attrset *text_attrset(struct pane *p safe, struct mark *m safe,
2252 struct text_chunk *c;
2253 struct text *t = p->doc_data;
2263 /* End of chunk, need to look at next */
2264 if (c->lst.next == &t->text)
2266 c = list_next_entry(c, lst);
2274 DEF_CMD(text_doc_get_attr)
2276 struct mark *m = ci->mark;
2277 const char *attr = ci->str;
2284 a = text_attrset(ci->home, m, &o);
2285 val = attr_get_str(a, attr, o);
2286 if (!val && !ci->num2)
2287 return Efallthrough;
2288 comm_call(ci->comm2, "callback:get_attr", ci->focus, 0, m, val,
2290 if (ci->num2 == 1) {
2291 const char *key = attr;
2292 int len = strlen(attr);
2293 while ((key = attr_get_next_key(a, key, o, &val)) != NULL &&
2294 strncmp(key, attr, len) == 0)
2295 comm_call(ci->comm2, "callback:get_attr", ci->focus,
2302 DEF_CMD(text_get_attr)
2304 struct text *t = ci->home->doc_data;
2305 const char *attr = ci->str;
2311 if ((val = attr_find(ci->home->attrs, attr)) != NULL)
2313 else if (strcmp(attr, "render-default") == 0)
2315 else if (strcmp(attr, "doc-type") == 0)
2317 else if (strcmp(attr, "doc:charset") == 0)
2319 else if (strcmp(attr, "filename") == 0)
2321 else if (strcmp(attr, "doc-file-changed") == 0)
2322 val = t->file_changed ? "yes" : "no";
2323 else if (strcmp(attr, "doc-modified") == 0)
2324 val = (t->saved != t->undo) ? "yes" : "no";
2325 else if (strcmp(attr, "autosave-exists") == 0)
2326 val = t->autosave_exists ? "yes" : "no";
2327 else if (strcmp(attr, "autosave-name") == 0) {
2328 if (!t->autosave_name && t->fname)
2329 t->autosave_name = autosave_name(t->fname);
2330 val = t->autosave_name;
2331 } else if (strcmp(attr, "is_backup") == 0) {
2332 const char *f = t->fname ?: "";
2333 const char *base = strrchr(f, '/');
2341 if (base[0] == '#' && base[l-1] == '#')
2343 else if (base[l-1] == '~' && strchr(base, '~') - base < l-1)
2347 } else if (strcmp(attr, "base-name") == 0) {
2348 char *f = strsave(ci->focus, t->fname ?: "");
2354 base = strrchr(f, '/');
2361 if (base[0] == '#' && base[l-1] == '#') {
2363 strcpy(base, base+1);
2364 } else if (base[l-1] == '~' && strchr(base, '~') - base < l-1) {
2365 while (l > 1 && base[l-2] != '~')
2371 return Efallthrough;
2373 comm_call(ci->comm2, "callback:get_attr", ci->focus, 0, NULL, val);
2377 DEF_CMD(text_set_attr)
2379 const char *attr = ci->str;
2380 const char *val = ci->str2;
2381 struct text_chunk *c, *c2;
2382 struct text *t = ci->home->doc_data;
2388 return Efallthrough;
2390 o = ci->mark->ref.o;
2391 c = ci->mark->ref.c;
2394 return Efallthrough;
2396 /* End of chunk, need to look at next */
2397 if (c->lst.next == &t->text)
2398 return Efallthrough;
2399 c = list_next_entry(c, lst);
2402 pane_notify("doc:replaced-attr", ci->home, 1, ci->mark, NULL,
2404 attr_set_str_key(&c->attrs, attr, val, o);
2405 if (!ci->mark2 || ci->mark2->seq <= ci->mark->seq)
2406 return Efallthrough;
2407 /* Delete all subsequent instances of attr */
2409 o2 = ci->mark2->ref.o;
2410 c2 = ci->mark2->ref.c;
2412 attr_del_all(&c->attrs, attr, o, c->end);
2413 c = list_next_entry(c, lst);
2414 o = c ? c->start : 0;
2417 attr_del_all(&c->attrs, attr, o, o2);
2418 return Efallthrough;
2421 DEF_CMD(text_modified)
2423 struct text *t = ci->home->doc_data;
2427 if (t->saved == t->undo)
2431 } else if (ci->num > 0)
2432 /* Set "is modified" */
2435 /* Clear "is modified" */
2437 text_check_autosave(ci->home);
2438 call("doc:notify:doc:status-changed", ci->home);
2442 DEF_CMD(text_revisited)
2444 struct text *t = ci->home->doc_data;
2447 /* Being buried, not visited */
2448 return Efallthrough;
2450 if (check_file_changed(ci->home) && t->saved == t->undo) {
2451 call("doc:load-file", ci->home, 2, NULL, NULL, -1);
2452 call("Message", ci->focus, 0, NULL, "File Reloaded");
2454 return Efallthrough;
2457 static void text_cleanout(struct text *t safe)
2459 struct text_alloc *ta;
2462 hlist_for_each_entry(m, &t->doc.marks, all) {
2467 while (!list_empty(&t->text)) {
2468 struct text_chunk *c = list_entry(t->text.next,
2469 struct text_chunk, lst);
2471 attr_free(&c->attrs);
2476 struct text_alloc *tmp = ta;
2478 unalloc_buf(tmp, sizeof(*tmp) + tmp->size, text);
2480 t->alloc = safe_cast NULL;
2482 struct text_edit *te = t->undo;
2484 if (te->altnext == NULL) {
2487 } else if (te->next == NULL) {
2488 t->undo = te->altnext;
2491 /* Make the ->altnext link shorted, until it
2494 t->undo = te->altnext;
2495 te->altnext = t->undo->next;
2500 struct text_edit *te = t->redo;
2502 if (te->altnext == NULL) {
2505 } else if (te->next == NULL) {
2506 t->redo = te->altnext;
2509 /* Make the ->altnext link shorted, until it
2512 t->redo = te->altnext;
2513 te->altnext = t->redo->next;
2519 DEF_CMD(text_destroy)
2521 struct text *t = ci->home->doc_data;
2524 free((void*)t->fname);
2526 free((void*)t->autosave_name);
2527 t->autosave_name = NULL;
2528 return Efallthrough;
2533 /* Clear the document, including undo/redo records
2534 * i.e. free all text
2536 struct text *t = ci->home->doc_data;
2540 text_new_alloc(t, 0);
2542 hlist_for_each_entry(m, &t->doc.marks, all) {
2546 pane_notify("doc:replaced", ci->home);
2551 void edlib_init(struct pane *ed safe)
2553 call_comm("global-set-command", ed, &text_new, 0, NULL,
2555 call_comm("global-set-command", ed, &text_new2, 0, NULL,
2558 text_map = key_alloc();
2560 key_add_chain(text_map, doc_default_cmd);
2561 key_add(text_map, "doc:load-file", &text_load_file);
2562 key_add(text_map, "doc:insert-file", &text_insert_file);
2563 key_add(text_map, "doc:same-file", &text_same_file);
2564 key_add(text_map, "doc:content", &text_content);
2565 key_add(text_map, "doc:content-bytes", &text_content);
2566 key_add(text_map, "doc:set-ref", &text_set_ref);
2567 key_add(text_map, "doc:save-file", &text_save_file);
2568 key_add(text_map, "doc:write-file", &text_write_file);
2569 key_add(text_map, "doc:reundo", &text_reundo);
2570 key_add(text_map, "doc:set-attr", &text_set_attr);
2571 key_add(text_map, "doc:get-attr", &text_doc_get_attr);
2572 key_add(text_map, "doc:replace", &text_replace);
2573 key_add(text_map, "doc:char", &text_char_byte);
2574 key_add(text_map, "doc:byte", &text_char_byte);
2575 key_add(text_map, "doc:modified", &text_modified);
2576 key_add(text_map, "doc:set:readonly", &text_readonly);
2577 key_add(text_map, "doc:notify:doc:revisit", &text_revisited);
2578 key_add(text_map, "doc:clear", &text_clear);
2579 key_add(text_map, "doc:autosave-delete", &text_autosave_delete);
2580 key_add(text_map, "doc:debug:mark", &text_debug_mark);
2581 key_add(text_map, "debug:validate-marks", &text_val_marks);
2583 key_add(text_map, "Close", &text_destroy);
2584 key_add(text_map, "get-attr", &text_get_attr);