use EXIT_FAILURE for exit status
[vis.git] / text.c
blob46696e059ff252122921a71dac77976748c63233
1 #include <unistd.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <time.h>
6 #include <fcntl.h>
7 #include <errno.h>
8 #include <wchar.h>
9 #include <stdint.h>
10 #include <libgen.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <sys/mman.h>
14 #if CONFIG_ACL
15 #include <sys/acl.h>
16 #endif
17 #if CONFIG_SELINUX
18 #include <selinux/selinux.h>
19 #endif
21 #include "text.h"
22 #include "text-util.h"
23 #include "text-motions.h"
24 #include "util.h"
26 /* Allocate buffers holding the actual file content in junks of size: */
27 #define BUFFER_SIZE (1 << 20)
28 /* Files smaller than this value are copied on load, larger ones are mmap(2)-ed
29 * directely. Hence the former can be truncated, while doing so on the latter
30 * results in havoc. */
31 #define BUFFER_MMAP_SIZE (1 << 23)
33 /* Buffer holding the file content, either readonly mmap(2)-ed from the original
34 * file or heap allocated to store the modifications.
36 typedef struct Buffer Buffer;
37 struct Buffer {
38 size_t size; /* maximal capacity */
39 size_t len; /* current used length / insertion position */
40 char *data; /* actual data */
41 enum { /* type of allocation */
42 MMAP_ORIG, /* mmap(2)-ed from an external file */
43 MMAP, /* mmap(2)-ed from a temporary file only known to this process */
44 MALLOC, /* heap allocated buffer using malloc(3) */
45 } type;
46 Buffer *next; /* next junk */
49 /* A piece holds a reference (but doesn't itself store) a certain amount of data.
50 * All active pieces chained together form the whole content of the document.
51 * At the beginning there exists only one piece, spanning the whole document.
52 * Upon insertion/deletion new pieces will be created to represent the changes.
53 * Generally pieces are never destroyed, but kept around to peform undo/redo
54 * operations.
56 struct Piece {
57 Text *text; /* text to which this piece belongs */
58 Piece *prev, *next; /* pointers to the logical predecessor/successor */
59 Piece *global_prev; /* double linked list in order of allocation, */
60 Piece *global_next; /* used to free individual pieces */
61 const char *data; /* pointer into a Buffer holding the data */
62 size_t len; /* the length in number of bytes of the data */
65 /* used to transform a global position (byte offset starting from the beginning
66 * of the text) into an offset relative to a piece.
68 typedef struct {
69 Piece *piece; /* piece holding the location */
70 size_t off; /* offset into the piece in bytes */
71 } Location;
73 /* A Span holds a certain range of pieces. Changes to the document are always
74 * performed by swapping out an existing span with a new one.
76 typedef struct {
77 Piece *start, *end; /* start/end of the span */
78 size_t len; /* the sum of the lengths of the pieces which form this span */
79 } Span;
81 /* A Change keeps all needed information to redo/undo an insertion/deletion. */
82 typedef struct Change Change;
83 struct Change {
84 Span old; /* all pieces which are being modified/swapped out by the change */
85 Span new; /* all pieces which are introduced/swapped in by the change */
86 size_t pos; /* absolute position at which the change occured */
87 Change *next; /* next change which is part of the same revision */
88 Change *prev; /* previous change which is part of the same revision */
91 /* A Revision is a list of Changes which are used to undo/redo all modifications
92 * since the last snapshot operation. Revisions are stored in a directed graph structure.
94 typedef struct Revision Revision;
95 struct Revision {
96 Change *change; /* the most recent change */
97 Revision *next; /* the next (child) revision in the undo tree */
98 Revision *prev; /* the previous (parent) revision in the undo tree */
99 Revision *earlier; /* the previous Revision, chronologically */
100 Revision *later; /* the next Revision, chronologically */
101 time_t time; /* when the first change of this revision was performed */
102 size_t seq; /* a unique, strictly increasing identifier */
105 typedef struct {
106 size_t pos; /* position in bytes from start of file */
107 size_t lineno; /* line number in file i.e. number of '\n' in [0, pos) */
108 } LineCache;
110 /* The main struct holding all information of a given file */
111 struct Text {
112 Buffer *buf; /* original file content at the time of load operation */
113 Buffer *buffers; /* all buffers which have been allocated to hold insertion data */
114 Piece *pieces; /* all pieces which have been allocated, used to free them */
115 Piece *cache; /* most recently modified piece */
116 Piece begin, end; /* sentinel nodes which always exists but don't hold any data */
117 Revision *history; /* undo tree */
118 Revision *current_revision; /* revision holding all file changes until a snapshot is performed */
119 Revision *last_revision; /* the last revision added to the tree, chronologically */
120 Revision *saved_revision; /* the last revision at the time of the save operation */
121 size_t size; /* current file content size in bytes */
122 struct stat info; /* stat as probed at load time */
123 LineCache lines; /* mapping between absolute pos in bytes and logical line breaks */
124 enum TextNewLine newlines; /* which type of new lines does the file use */
127 struct TextSave { /* used to hold context between text_save_{begin,commit} calls */
128 Text *txt; /* text to operate on */
129 char *filename; /* filename to save to as given to text_save_begin */
130 char *tmpname; /* temporary name used for atomic rename(2) */
131 int fd; /* file descriptor to write data to using text_save_write */
132 enum {
133 TEXT_SAVE_UNKNOWN,
134 TEXT_SAVE_ATOMIC, /* create a new file, write content, atomically rename(2) over old file */
135 TEXT_SAVE_INPLACE, /* truncate file, overwrite content (any error will result in data loss) */
136 } type;
139 /* buffer management */
140 static Buffer *buffer_alloc(Text *txt, size_t size);
141 static Buffer *buffer_read(Text *txt, size_t size, int fd);
142 static Buffer *buffer_mmap(Text *txt, size_t size, int fd, off_t offset);
143 static void buffer_free(Buffer *buf);
144 static bool buffer_capacity(Buffer *buf, size_t len);
145 static const char *buffer_append(Buffer *buf, const char *data, size_t len);
146 static bool buffer_insert(Buffer *buf, size_t pos, const char *data, size_t len);
147 static bool buffer_delete(Buffer *buf, size_t pos, size_t len);
148 static const char *buffer_store(Text *txt, const char *data, size_t len);
149 /* cache layer */
150 static void cache_piece(Text *txt, Piece *p);
151 static bool cache_contains(Text *txt, Piece *p);
152 static bool cache_insert(Text *txt, Piece *p, size_t off, const char *data, size_t len);
153 static bool cache_delete(Text *txt, Piece *p, size_t off, size_t len);
154 /* piece management */
155 static Piece *piece_alloc(Text *txt);
156 static void piece_free(Piece *p);
157 static void piece_init(Piece *p, Piece *prev, Piece *next, const char *data, size_t len);
158 static Location piece_get_intern(Text *txt, size_t pos);
159 static Location piece_get_extern(Text *txt, size_t pos);
160 /* span management */
161 static void span_init(Span *span, Piece *start, Piece *end);
162 static void span_swap(Text *txt, Span *old, Span *new);
163 /* change management */
164 static Change *change_alloc(Text *txt, size_t pos);
165 static void change_free(Change *c);
166 /* revision management */
167 static Revision *revision_alloc(Text *txt);
168 static void revision_free(Revision *rev);
169 /* logical line counting cache */
170 static void lineno_cache_invalidate(LineCache *cache);
171 static size_t lines_skip_forward(Text *txt, size_t pos, size_t lines, size_t *lines_skiped);
172 static size_t lines_count(Text *txt, size_t pos, size_t len);
174 static ssize_t write_all(int fd, const char *buf, size_t count) {
175 size_t rem = count;
176 while (rem > 0) {
177 ssize_t written = write(fd, buf, rem);
178 if (written < 0) {
179 if (errno == EAGAIN || errno == EINTR)
180 continue;
181 return -1;
182 } else if (written == 0) {
183 break;
185 rem -= written;
186 buf += written;
188 return count - rem;
191 /* allocate a new buffer of MAX(size, BUFFER_SIZE) bytes */
192 static Buffer *buffer_alloc(Text *txt, size_t size) {
193 Buffer *buf = calloc(1, sizeof(Buffer));
194 if (!buf)
195 return NULL;
196 if (BUFFER_SIZE > size)
197 size = BUFFER_SIZE;
198 if (!(buf->data = malloc(size))) {
199 free(buf);
200 return NULL;
202 buf->type = MALLOC;
203 buf->size = size;
204 buf->next = txt->buffers;
205 txt->buffers = buf;
206 return buf;
209 static Buffer *buffer_read(Text *txt, size_t size, int fd) {
210 Buffer *buf = buffer_alloc(txt, size);
211 if (!buf)
212 return NULL;
213 while (size > 0) {
214 char data[4096];
215 ssize_t len = read(fd, data, MIN(sizeof(data), size));
216 if (len == -1) {
217 txt->buffers = buf->next;
218 buffer_free(buf);
219 return NULL;
220 } else if (len == 0) {
221 break;
222 } else {
223 buffer_append(buf, data, len);
224 size -= len;
227 return buf;
230 static Buffer *buffer_mmap(Text *txt, size_t size, int fd, off_t offset) {
231 Buffer *buf = calloc(1, sizeof(Buffer));
232 if (!buf)
233 return NULL;
234 if (size) {
235 buf->data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset);
236 if (buf->data == MAP_FAILED) {
237 free(buf);
238 return NULL;
241 buf->type = MMAP_ORIG;
242 buf->size = size;
243 buf->len = size;
244 buf->next = txt->buffers;
245 txt->buffers = buf;
246 return buf;
249 static void buffer_free(Buffer *buf) {
250 if (!buf)
251 return;
252 if (buf->type == MALLOC)
253 free(buf->data);
254 else if ((buf->type == MMAP_ORIG || buf->type == MMAP) && buf->data)
255 munmap(buf->data, buf->size);
256 free(buf);
259 /* check whether buffer has enough free space to store len bytes */
260 static bool buffer_capacity(Buffer *buf, size_t len) {
261 return buf->size - buf->len >= len;
264 /* append data to buffer, assumes there is enough space available */
265 static const char *buffer_append(Buffer *buf, const char *data, size_t len) {
266 char *dest = memcpy(buf->data + buf->len, data, len);
267 buf->len += len;
268 return dest;
271 /* stores the given data in a buffer, allocates a new one if necessary. returns
272 * a pointer to the storage location or NULL if allocation failed. */
273 static const char *buffer_store(Text *txt, const char *data, size_t len) {
274 Buffer *buf = txt->buffers;
275 if ((!buf || !buffer_capacity(buf, len)) && !(buf = buffer_alloc(txt, len)))
276 return NULL;
277 return buffer_append(buf, data, len);
280 /* insert data into buffer at an arbitrary position, this should only be used with
281 * data of the most recently created piece. */
282 static bool buffer_insert(Buffer *buf, size_t pos, const char *data, size_t len) {
283 if (pos > buf->len || !buffer_capacity(buf, len))
284 return false;
285 if (buf->len == pos)
286 return buffer_append(buf, data, len);
287 char *insert = buf->data + pos;
288 memmove(insert + len, insert, buf->len - pos);
289 memcpy(insert, data, len);
290 buf->len += len;
291 return true;
294 /* delete data from a buffer at an arbitrary position, this should only be used with
295 * data of the most recently created piece. */
296 static bool buffer_delete(Buffer *buf, size_t pos, size_t len) {
297 if (pos + len > buf->len)
298 return false;
299 if (buf->len == pos) {
300 buf->len -= len;
301 return true;
303 char *delete = buf->data + pos;
304 memmove(delete, delete + len, buf->len - pos - len);
305 buf->len -= len;
306 return true;
309 /* cache the given piece if it is the most recently changed one */
310 static void cache_piece(Text *txt, Piece *p) {
311 Buffer *buf = txt->buffers;
312 if (!buf || p->data < buf->data || p->data + p->len != buf->data + buf->len)
313 return;
314 txt->cache = p;
317 /* check whether the given piece was the most recently modified one */
318 static bool cache_contains(Text *txt, Piece *p) {
319 Buffer *buf = txt->buffers;
320 Revision *rev = txt->current_revision;
321 if (!buf || !txt->cache || txt->cache != p || !rev || !rev->change)
322 return false;
324 Piece *start = rev->change->new.start;
325 Piece *end = rev->change->new.end;
326 bool found = false;
327 for (Piece *cur = start; !found; cur = cur->next) {
328 if (cur == p)
329 found = true;
330 if (cur == end)
331 break;
334 return found && p->data + p->len == buf->data + buf->len;
337 /* try to insert a junk of data at a given piece offset. the insertion is only
338 * performed if the piece is the most recenetly changed one. the legnth of the
339 * piece, the span containing it and the whole text is adjusted accordingly */
340 static bool cache_insert(Text *txt, Piece *p, size_t off, const char *data, size_t len) {
341 if (!cache_contains(txt, p))
342 return false;
343 Buffer *buf = txt->buffers;
344 size_t bufpos = p->data + off - buf->data;
345 if (!buffer_insert(buf, bufpos, data, len))
346 return false;
347 p->len += len;
348 txt->current_revision->change->new.len += len;
349 txt->size += len;
350 return true;
353 /* try to delete a junk of data at a given piece offset. the deletion is only
354 * performed if the piece is the most recenetly changed one and the whole
355 * affected range lies within it. the legnth of the piece, the span containing it
356 * and the whole text is adjusted accordingly */
357 static bool cache_delete(Text *txt, Piece *p, size_t off, size_t len) {
358 if (!cache_contains(txt, p))
359 return false;
360 Buffer *buf = txt->buffers;
361 size_t bufpos = p->data + off - buf->data;
362 if (off + len > p->len || !buffer_delete(buf, bufpos, len))
363 return false;
364 p->len -= len;
365 txt->current_revision->change->new.len -= len;
366 txt->size -= len;
367 return true;
370 /* initialize a span and calculate its length */
371 static void span_init(Span *span, Piece *start, Piece *end) {
372 size_t len = 0;
373 span->start = start;
374 span->end = end;
375 for (Piece *p = start; p; p = p->next) {
376 len += p->len;
377 if (p == end)
378 break;
380 span->len = len;
383 /* swap out an old span and replace it with a new one.
385 * - if old is an empty span do not remove anything, just insert the new one
386 * - if new is an empty span do not insert anything, just remove the old one
388 * adjusts the document size accordingly.
390 static void span_swap(Text *txt, Span *old, Span *new) {
391 if (old->len == 0 && new->len == 0) {
392 return;
393 } else if (old->len == 0) {
394 /* insert new span */
395 new->start->prev->next = new->start;
396 new->end->next->prev = new->end;
397 } else if (new->len == 0) {
398 /* delete old span */
399 old->start->prev->next = old->end->next;
400 old->end->next->prev = old->start->prev;
401 } else {
402 /* replace old with new */
403 old->start->prev->next = new->start;
404 old->end->next->prev = new->end;
406 txt->size -= old->len;
407 txt->size += new->len;
410 /* Allocate a new revision and place it in the revision graph.
411 * All further changes will be associated with this revision. */
412 static Revision *revision_alloc(Text *txt) {
413 Revision *rev = calloc(1, sizeof(Revision));
414 if (!rev)
415 return NULL;
416 rev->time = time(NULL);
417 txt->current_revision = rev;
419 /* set sequence number */
420 if (!txt->last_revision)
421 rev->seq = 0;
422 else
423 rev->seq = txt->last_revision->seq + 1;
425 /* set earlier, later pointers */
426 if (txt->last_revision)
427 txt->last_revision->later = rev;
428 rev->earlier = txt->last_revision;
430 if (!txt->history) {
431 txt->history = rev;
432 return rev;
435 /* set prev, next pointers */
436 rev->prev = txt->history;
437 txt->history->next = rev;
438 txt->history = rev;
439 return rev;
442 static void revision_free(Revision *rev) {
443 if (!rev)
444 return;
445 for (Change *next, *c = rev->change; c; c = next) {
446 next = c->next;
447 change_free(c);
449 free(rev);
452 static Piece *piece_alloc(Text *txt) {
453 Piece *p = calloc(1, sizeof(Piece));
454 if (!p)
455 return NULL;
456 p->text = txt;
457 p->global_next = txt->pieces;
458 if (txt->pieces)
459 txt->pieces->global_prev = p;
460 txt->pieces = p;
461 return p;
464 static void piece_free(Piece *p) {
465 if (!p)
466 return;
467 if (p->global_prev)
468 p->global_prev->global_next = p->global_next;
469 if (p->global_next)
470 p->global_next->global_prev = p->global_prev;
471 if (p->text->pieces == p)
472 p->text->pieces = p->global_next;
473 if (p->text->cache == p)
474 p->text->cache = NULL;
475 free(p);
478 static void piece_init(Piece *p, Piece *prev, Piece *next, const char *data, size_t len) {
479 p->prev = prev;
480 p->next = next;
481 p->data = data;
482 p->len = len;
485 /* returns the piece holding the text at byte offset pos. if pos happens to
486 * be at a piece boundry i.e. the first byte of a piece then the previous piece
487 * to the left is returned with an offset of piece->len. this is convenient for
488 * modifications to the piece chain where both pieces (the returned one and the
489 * one following it) are needed, but unsuitable as a public interface.
491 * in particular if pos is zero, the begin sentinel piece is returned.
493 static Location piece_get_intern(Text *txt, size_t pos) {
494 size_t cur = 0;
495 for (Piece *p = &txt->begin; p->next; p = p->next) {
496 if (cur <= pos && pos <= cur + p->len)
497 return (Location){ .piece = p, .off = pos - cur };
498 cur += p->len;
501 return (Location){ 0 };
504 /* similiar to piece_get_intern but usable as a public API. returns the piece
505 * holding the text at byte offset pos. never returns a sentinel piece.
506 * it pos is the end of file (== text_size()) and the file is not empty then
507 * the last piece holding data is returned.
509 static Location piece_get_extern(Text *txt, size_t pos) {
510 size_t cur = 0;
512 if (pos > 0 && pos == txt->size) {
513 Piece *p = txt->begin.next;
514 while (p->next->next)
515 p = p->next;
516 return (Location){ .piece = p, .off = p->len };
519 for (Piece *p = txt->begin.next; p->next; p = p->next) {
520 if (cur <= pos && pos < cur + p->len)
521 return (Location){ .piece = p, .off = pos - cur };
522 cur += p->len;
525 return (Location){ 0 };
528 /* allocate a new change, associate it with current revision or a newly
529 * allocated one if none exists. */
530 static Change *change_alloc(Text *txt, size_t pos) {
531 Revision *rev = txt->current_revision;
532 if (!rev) {
533 rev = revision_alloc(txt);
534 if (!rev)
535 return NULL;
537 Change *c = calloc(1, sizeof(Change));
538 if (!c)
539 return NULL;
540 c->pos = pos;
541 c->next = rev->change;
542 if (rev->change)
543 rev->change->prev = c;
544 rev->change = c;
545 return c;
548 static void change_free(Change *c) {
549 if (!c)
550 return;
551 /* only free the new part of the span, the old one is still in use */
552 piece_free(c->new.start);
553 if (c->new.start != c->new.end)
554 piece_free(c->new.end);
555 free(c);
558 /* When inserting new data there are 2 cases to consider.
560 * - in the first the insertion point falls into the middle of an exisiting
561 * piece which is replaced by three new pieces:
563 * /-+ --> +---------------+ --> +-\
564 * | | | existing text | | |
565 * \-+ <-- +---------------+ <-- +-/
567 * Insertion point for "demo "
569 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
570 * | | | existing| |demo | |text | | |
571 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
573 * - the second case deals with an insertion point at a piece boundry:
575 * /-+ --> +---------------+ --> +-\
576 * | | | existing text | | |
577 * \-+ <-- +---------------+ <-- +-/
579 * Insertion point for "short"
581 * /-+ --> +-----+ --> +---------------+ --> +-\
582 * | | |short| | existing text | | |
583 * \-+ <-- +-----+ <-- +---------------+ <-- +-/
585 bool text_insert(Text *txt, size_t pos, const char *data, size_t len) {
586 if (len == 0)
587 return true;
588 if (pos > txt->size)
589 return false;
590 if (pos < txt->lines.pos)
591 lineno_cache_invalidate(&txt->lines);
593 Location loc = piece_get_intern(txt, pos);
594 Piece *p = loc.piece;
595 if (!p)
596 return false;
597 size_t off = loc.off;
598 if (cache_insert(txt, p, off, data, len))
599 return true;
601 Change *c = change_alloc(txt, pos);
602 if (!c)
603 return false;
605 if (!(data = buffer_store(txt, data, len)))
606 return false;
608 Piece *new = NULL;
610 if (off == p->len) {
611 /* insert between two existing pieces, hence there is nothing to
612 * remove, just add a new piece holding the extra text */
613 if (!(new = piece_alloc(txt)))
614 return false;
615 piece_init(new, p, p->next, data, len);
616 span_init(&c->new, new, new);
617 span_init(&c->old, NULL, NULL);
618 } else {
619 /* insert into middle of an existing piece, therfore split the old
620 * piece. that is we have 3 new pieces one containing the content
621 * before the insertion point then one holding the newly inserted
622 * text and one holding the content after the insertion point.
624 Piece *before = piece_alloc(txt);
625 new = piece_alloc(txt);
626 Piece *after = piece_alloc(txt);
627 if (!before || !new || !after)
628 return false;
629 piece_init(before, p->prev, new, p->data, off);
630 piece_init(new, before, after, data, len);
631 piece_init(after, new, p->next, p->data + off, p->len - off);
633 span_init(&c->new, before, after);
634 span_init(&c->old, p, p);
637 cache_piece(txt, new);
638 span_swap(txt, &c->old, &c->new);
639 return true;
642 bool text_appendf(Text *txt, const char *format, ...) {
643 va_list ap;
644 va_start(ap, format);
645 bool ret = text_vprintf(txt, text_size(txt), format, ap);
646 va_end(ap);
647 return ret;
650 bool text_printf(Text *txt, size_t pos, const char *format, ...) {
651 va_list ap;
652 va_start(ap, format);
653 bool ret = text_vprintf(txt, pos, format, ap);
654 va_end(ap);
655 return ret;
658 bool text_vprintf(Text *txt, size_t pos, const char *format, va_list ap) {
659 va_list ap_save;
660 va_copy(ap_save, ap);
661 int len = vsnprintf(NULL, 0, format, ap);
662 if (len == -1)
663 return false;
664 char *buf = malloc(len+1);
665 bool ret = buf && (vsnprintf(buf, len+1, format, ap_save) == len) && text_insert(txt, pos, buf, len);
666 free(buf);
667 va_end(ap_save);
668 return ret;
671 size_t text_insert_newline(Text *txt, size_t pos) {
672 const char *data = text_newline_char(txt);
673 size_t len = strlen(data);
674 return text_insert(txt, pos, data, len) ? len : 0;
677 static size_t revision_undo(Text *txt, Revision *rev) {
678 size_t pos = EPOS;
679 for (Change *c = rev->change; c; c = c->next) {
680 span_swap(txt, &c->new, &c->old);
681 pos = c->pos;
683 return pos;
686 static size_t revision_redo(Text *txt, Revision *rev) {
687 size_t pos = EPOS;
688 Change *c = rev->change;
689 while (c->next)
690 c = c->next;
691 for ( ; c; c = c->prev) {
692 span_swap(txt, &c->old, &c->new);
693 pos = c->pos;
694 if (c->new.len > c->old.len)
695 pos += c->new.len - c->old.len;
697 return pos;
700 size_t text_undo(Text *txt) {
701 size_t pos = EPOS;
702 /* taking rev snapshot makes sure that txt->current_revision is reset */
703 text_snapshot(txt);
704 Revision *rev = txt->history->prev;
705 if (!rev)
706 return pos;
707 pos = revision_undo(txt, txt->history);
708 txt->history = rev;
709 lineno_cache_invalidate(&txt->lines);
710 return pos;
713 size_t text_redo(Text *txt) {
714 size_t pos = EPOS;
715 /* taking a snapshot makes sure that txt->current_revision is reset */
716 text_snapshot(txt);
717 Revision *rev = txt->history->next;
718 if (!rev)
719 return pos;
720 pos = revision_redo(txt, rev);
721 txt->history = rev;
722 lineno_cache_invalidate(&txt->lines);
723 return pos;
726 static bool history_change_branch(Revision *rev) {
727 bool changed = false;
728 while (rev->prev) {
729 if (rev->prev->next != rev) {
730 rev->prev->next = rev;
731 changed = true;
733 rev = rev->prev;
735 return changed;
738 static size_t history_traverse_to(Text *txt, Revision *rev) {
739 size_t pos = EPOS;
740 if (!rev)
741 return pos;
742 bool changed = history_change_branch(rev);
743 if (!changed) {
744 if (rev->seq == txt->history->seq) {
745 return txt->lines.pos;
746 } else if (rev->seq > txt->history->seq) {
747 while (txt->history != rev)
748 pos = text_redo(txt);
749 return pos;
750 } else if (rev->seq < txt->history->seq) {
751 while (txt->history != rev)
752 pos = text_undo(txt);
753 return pos;
755 } else {
756 while (txt->history->prev && txt->history->prev->next == txt->history)
757 text_undo(txt);
758 pos = text_undo(txt);
759 while (txt->history != rev)
760 pos = text_redo(txt);
761 return pos;
763 return pos;
766 size_t text_earlier(Text *txt, int count) {
767 Revision *rev = txt->history;
768 while (count-- > 0 && rev->earlier)
769 rev = rev->earlier;
770 return history_traverse_to(txt, rev);
773 size_t text_later(Text *txt, int count) {
774 Revision *rev = txt->history;
775 while (count-- > 0 && rev->later)
776 rev = rev->later;
777 return history_traverse_to(txt, rev);
780 size_t text_restore(Text *txt, time_t time) {
781 Revision *rev = txt->history;
782 while (time < rev->time && rev->earlier)
783 rev = rev->earlier;
784 while (time > rev->time && rev->later)
785 rev = rev->later;
786 time_t diff = labs(rev->time - time);
787 if (rev->earlier && rev->earlier != txt->history && labs(rev->earlier->time - time) < diff)
788 rev = rev->earlier;
789 if (rev->later && rev->later != txt->history && labs(rev->later->time - time) < diff)
790 rev = rev->later;
791 return history_traverse_to(txt, rev);
794 time_t text_state(Text *txt) {
795 return txt->history->time;
798 static bool preserve_acl(int src, int dest) {
799 #if CONFIG_ACL
800 acl_t acl = acl_get_fd(src);
801 if (!acl)
802 return errno == ENOTSUP ? true : false;
803 if (acl_set_fd(dest, acl) == -1) {
804 acl_free(acl);
805 return false;
807 acl_free(acl);
808 #endif /* CONFIG_ACL */
809 return true;
812 static bool preserve_selinux_context(int src, int dest) {
813 #if CONFIG_SELINUX
814 char *context = NULL;
815 if (!is_selinux_enabled())
816 return true;
817 if (fgetfilecon(src, &context) == -1)
818 return errno == ENOTSUP ? true : false;
819 if (fsetfilecon(dest, context) == -1) {
820 freecon(context);
821 return false;
823 freecon(context);
824 #endif /* CONFIG_SELINUX */
825 return true;
828 /* Create a new file named `filename~` and try to preserve all important
829 * meta data. After the file content has been written to this temporary
830 * file, text_save_commit_atomic will atomically move it to its final
831 * (possibly already existing) destination using rename(2).
833 * This approach does not work if:
835 * - the file is a symbolic link
836 * - the file is a hard link
837 * - file ownership can not be preserved
838 * - file group can not be preserved
839 * - directory permissions do not allow creation of a new file
840 * - POSXI ACL can not be preserved (if enabled)
841 * - SELinux security context can not be preserved (if enabled)
843 static bool text_save_begin_atomic(TextSave *ctx) {
844 int oldfd, saved_errno;
845 if ((oldfd = open(ctx->filename, O_RDONLY)) == -1 && errno != ENOENT)
846 goto err;
847 struct stat oldmeta = { 0 };
848 if (oldfd != -1 && lstat(ctx->filename, &oldmeta) == -1)
849 goto err;
850 if (oldfd != -1) {
851 if (S_ISLNK(oldmeta.st_mode)) /* symbolic link */
852 goto err;
853 if (oldmeta.st_nlink > 1) /* hard link */
854 goto err;
857 size_t namelen = strlen(ctx->filename) + 1 /* ~ */ + 1 /* \0 */;
858 if (!(ctx->tmpname = calloc(1, namelen)))
859 goto err;
860 snprintf(ctx->tmpname, namelen, "%s~", ctx->filename);
862 if ((ctx->fd = open(ctx->tmpname, O_CREAT|O_WRONLY|O_TRUNC, oldfd == -1 ? S_IRUSR|S_IWUSR : oldmeta.st_mode)) == -1)
863 goto err;
864 if (oldfd != -1) {
865 if (!preserve_acl(oldfd, ctx->fd) || !preserve_selinux_context(oldfd, ctx->fd))
866 goto err;
867 /* change owner if necessary */
868 if (oldmeta.st_uid != getuid() && fchown(ctx->fd, oldmeta.st_uid, (uid_t)-1) == -1)
869 goto err;
870 /* change group if necessary, in case of failure some editors reset
871 * the group permissions to the same as for others */
872 if (oldmeta.st_gid != getgid() && fchown(ctx->fd, (uid_t)-1, oldmeta.st_gid) == -1)
873 goto err;
874 close(oldfd);
877 ctx->type = TEXT_SAVE_ATOMIC;
878 return true;
879 err:
880 saved_errno = errno;
881 if (oldfd != -1)
882 close(oldfd);
883 if (ctx->fd != -1)
884 close(ctx->fd);
885 ctx->fd = -1;
886 errno = saved_errno;
887 return false;
890 static bool text_save_commit_atomic(TextSave *ctx) {
891 if (fsync(ctx->fd) == -1)
892 return false;
894 struct stat meta = { 0 };
895 if (fstat(ctx->fd, &meta) == -1)
896 return false;
898 bool close_failed = (close(ctx->fd) == -1);
899 ctx->fd = -1;
900 if (close_failed)
901 return false;
903 if (rename(ctx->tmpname, ctx->filename) == -1)
904 return false;
906 free(ctx->tmpname);
907 ctx->tmpname = NULL;
909 int dir = open(dirname(ctx->filename), O_DIRECTORY|O_RDONLY);
910 if (dir == -1)
911 return false;
913 if (fsync(dir) == -1) {
914 close(dir);
915 return false;
918 if (close(dir) == -1)
919 return false;
921 if (meta.st_mtime)
922 ctx->txt->info = meta;
923 return true;
926 static bool text_save_begin_inplace(TextSave *ctx) {
927 Text *txt = ctx->txt;
928 struct stat meta = { 0 };
929 int newfd = -1, saved_errno;
930 if ((ctx->fd = open(ctx->filename, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR)) == -1)
931 goto err;
932 if (fstat(ctx->fd, &meta) == -1)
933 goto err;
934 if (meta.st_dev == txt->info.st_dev && meta.st_ino == txt->info.st_ino &&
935 txt->buf && txt->buf->type == MMAP_ORIG && txt->buf->size) {
936 /* The file we are going to overwrite is currently mmap-ed from
937 * text_load, therefore we copy the mmap-ed buffer to a temporary
938 * file and remap it at the same position such that all pointers
939 * from the various pieces are still valid.
941 size_t size = txt->buf->size;
942 char tmpname[32] = "/tmp/vis-XXXXXX";
943 mode_t mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
944 newfd = mkstemp(tmpname);
945 umask(mask);
946 if (newfd == -1)
947 goto err;
948 if (unlink(tmpname) == -1)
949 goto err;
950 ssize_t written = write_all(newfd, txt->buf->data, size);
951 if (written == -1 || (size_t)written != size)
952 goto err;
953 if (munmap(txt->buf->data, size) == -1)
954 goto err;
956 void *data = mmap(txt->buf->data, size, PROT_READ, MAP_SHARED, newfd, 0);
957 if (data == MAP_FAILED)
958 goto err;
959 if (data != txt->buf->data) {
960 munmap(data, size);
961 goto err;
963 bool close_failed = (close(newfd) == -1);
964 newfd = -1;
965 if (close_failed)
966 goto err;
967 txt->buf->data = data;
968 txt->buf->type = MMAP;
969 newfd = -1;
971 /* overwrite the exisiting file content, if somehting goes wrong
972 * here we are screwed, TODO: make a backup before? */
973 if (ftruncate(ctx->fd, 0) == -1)
974 goto err;
975 ctx->type = TEXT_SAVE_INPLACE;
976 return true;
977 err:
978 saved_errno = errno;
979 if (newfd != -1)
980 close(newfd);
981 if (ctx->fd != -1)
982 close(ctx->fd);
983 ctx->fd = -1;
984 errno = saved_errno;
985 return false;
988 static bool text_save_commit_inplace(TextSave *ctx) {
989 if (fsync(ctx->fd) == -1)
990 return false;
991 struct stat meta = { 0 };
992 if (fstat(ctx->fd, &meta) == -1)
993 return false;
994 if (close(ctx->fd) == -1)
995 return false;
996 ctx->txt->info = meta;
997 return true;
1000 TextSave *text_save_begin(Text *txt, const char *filename) {
1001 if (!filename)
1002 return NULL;
1003 TextSave *ctx = calloc(1, sizeof *ctx);
1004 if (!ctx)
1005 return NULL;
1006 ctx->txt = txt;
1007 ctx->fd = -1;
1008 if (!(ctx->filename = strdup(filename)))
1009 goto err;
1010 errno = 0;
1011 if (text_save_begin_atomic(ctx))
1012 return ctx;
1013 if (errno == ENOSPC)
1014 goto err;
1015 if (text_save_begin_inplace(ctx))
1016 return ctx;
1017 err:
1018 text_save_cancel(ctx);
1019 return NULL;
1022 bool text_save_commit(TextSave *ctx) {
1023 if (!ctx)
1024 return true;
1025 bool ret;
1026 Text *txt = ctx->txt;
1027 switch (ctx->type) {
1028 case TEXT_SAVE_ATOMIC:
1029 ret = text_save_commit_atomic(ctx);
1030 break;
1031 case TEXT_SAVE_INPLACE:
1032 ret = text_save_commit_inplace(ctx);
1033 break;
1034 default:
1035 ret = false;
1036 break;
1039 if (ret) {
1040 txt->saved_revision = txt->history;
1041 text_snapshot(txt);
1043 text_save_cancel(ctx);
1044 return ret;
1047 void text_save_cancel(TextSave *ctx) {
1048 if (!ctx)
1049 return;
1050 int saved_errno = errno;
1051 if (ctx->fd != -1)
1052 close(ctx->fd);
1053 if (ctx->tmpname && ctx->tmpname[0])
1054 unlink(ctx->tmpname);
1055 free(ctx->tmpname);
1056 free(ctx->filename);
1057 free(ctx);
1058 errno = saved_errno;
1061 bool text_save(Text *txt, const char *filename) {
1062 Filerange r = (Filerange){ .start = 0, .end = text_size(txt) };
1063 return text_save_range(txt, &r, filename);
1066 /* First try to save the file atomically using rename(2) if this does not
1067 * work overwrite the file in place. However if something goes wrong during
1068 * this overwrite the original file is permanently damaged.
1070 bool text_save_range(Text *txt, Filerange *range, const char *filename) {
1071 if (!filename) {
1072 txt->saved_revision = txt->history;
1073 text_snapshot(txt);
1074 return true;
1076 TextSave *ctx = text_save_begin(txt, filename);
1077 if (!ctx)
1078 return false;
1079 ssize_t written = text_write_range(txt, range, ctx->fd);
1080 if (written == -1 || (size_t)written != text_range_size(range)) {
1081 text_save_cancel(ctx);
1082 return false;
1084 return text_save_commit(ctx);
1087 ssize_t text_save_write_range(TextSave *ctx, Filerange *range) {
1088 return text_write_range(ctx->txt, range, ctx->fd);
1091 ssize_t text_write(Text *txt, int fd) {
1092 Filerange r = (Filerange){ .start = 0, .end = text_size(txt) };
1093 return text_write_range(txt, &r, fd);
1096 ssize_t text_write_range(Text *txt, Filerange *range, int fd) {
1097 size_t size = text_range_size(range), rem = size;
1098 for (Iterator it = text_iterator_get(txt, range->start);
1099 rem > 0 && text_iterator_valid(&it);
1100 text_iterator_next(&it)) {
1101 size_t prem = it.end - it.text;
1102 if (prem > rem)
1103 prem = rem;
1104 ssize_t written = write_all(fd, it.text, prem);
1105 if (written == -1)
1106 return -1;
1107 rem -= written;
1108 if ((size_t)written != prem)
1109 break;
1111 return size - rem;
1114 /* load the given file as starting point for further editing operations.
1115 * to start with an empty document, pass NULL as filename. */
1116 Text *text_load(const char *filename) {
1117 Text *txt = calloc(1, sizeof(Text));
1118 if (!txt)
1119 return NULL;
1120 int fd = -1;
1121 piece_init(&txt->begin, NULL, &txt->end, NULL, 0);
1122 piece_init(&txt->end, &txt->begin, NULL, NULL, 0);
1123 lineno_cache_invalidate(&txt->lines);
1124 if (filename) {
1125 if ((fd = open(filename, O_RDONLY)) == -1)
1126 goto out;
1127 if (fstat(fd, &txt->info) == -1)
1128 goto out;
1129 if (!S_ISREG(txt->info.st_mode)) {
1130 errno = S_ISDIR(txt->info.st_mode) ? EISDIR : ENOTSUP;
1131 goto out;
1133 // XXX: use lseek(fd, 0, SEEK_END); instead?
1134 size_t size = txt->info.st_size;
1135 if (size < BUFFER_MMAP_SIZE)
1136 txt->buf = buffer_read(txt, size, fd);
1137 else
1138 txt->buf = buffer_mmap(txt, size, fd, 0);
1139 if (!txt->buf)
1140 goto out;
1141 Piece *p = piece_alloc(txt);
1142 if (!p)
1143 goto out;
1144 piece_init(&txt->begin, NULL, p, NULL, 0);
1145 piece_init(p, &txt->begin, &txt->end, txt->buf->data, txt->buf->len);
1146 piece_init(&txt->end, p, NULL, NULL, 0);
1147 txt->size = txt->buf->len;
1149 /* write an empty revision */
1150 change_alloc(txt, EPOS);
1151 text_snapshot(txt);
1152 txt->saved_revision = txt->history;
1154 if (fd != -1)
1155 close(fd);
1156 return txt;
1157 out:
1158 if (fd != -1)
1159 close(fd);
1160 text_free(txt);
1161 return NULL;
1164 struct stat text_stat(Text *txt) {
1165 return txt->info;
1168 /* A delete operation can either start/stop midway through a piece or at
1169 * a boundry. In the former case a new piece is created to represent the
1170 * remaining text before/after the modification point.
1172 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
1173 * | | | existing| |demo | |text | | |
1174 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
1175 * ^ ^
1176 * |------ delete range -----|
1178 * /-+ --> +----+ --> +--+ --> +-\
1179 * | | | exi| |t | | |
1180 * \-+ <-- +----+ <-- +--+ <-- +-/
1182 bool text_delete(Text *txt, size_t pos, size_t len) {
1183 if (len == 0)
1184 return true;
1185 if (pos + len > txt->size)
1186 return false;
1187 if (pos < txt->lines.pos)
1188 lineno_cache_invalidate(&txt->lines);
1190 Location loc = piece_get_intern(txt, pos);
1191 Piece *p = loc.piece;
1192 if (!p)
1193 return false;
1194 size_t off = loc.off;
1195 if (cache_delete(txt, p, off, len))
1196 return true;
1197 Change *c = change_alloc(txt, pos);
1198 if (!c)
1199 return false;
1201 bool midway_start = false, midway_end = false; /* split pieces? */
1202 Piece *before, *after; /* unmodified pieces before/after deletion point */
1203 Piece *start, *end; /* span which is removed */
1204 size_t cur; /* how much has already been deleted */
1206 if (off == p->len) {
1207 /* deletion starts at a piece boundry */
1208 cur = 0;
1209 before = p;
1210 start = p->next;
1211 } else {
1212 /* deletion starts midway through a piece */
1213 midway_start = true;
1214 cur = p->len - off;
1215 start = p;
1216 before = piece_alloc(txt);
1217 if (!before)
1218 return false;
1221 /* skip all pieces which fall into deletion range */
1222 while (cur < len) {
1223 p = p->next;
1224 cur += p->len;
1227 if (cur == len) {
1228 /* deletion stops at a piece boundry */
1229 end = p;
1230 after = p->next;
1231 } else {
1232 /* cur > len: deletion stops midway through a piece */
1233 midway_end = true;
1234 end = p;
1235 after = piece_alloc(txt);
1236 if (!after)
1237 return false;
1238 piece_init(after, before, p->next, p->data + p->len - (cur - len), cur - len);
1241 if (midway_start) {
1242 /* we finally know which piece follows our newly allocated before piece */
1243 piece_init(before, start->prev, after, start->data, off);
1246 Piece *new_start = NULL, *new_end = NULL;
1247 if (midway_start) {
1248 new_start = before;
1249 if (!midway_end)
1250 new_end = before;
1252 if (midway_end) {
1253 if (!midway_start)
1254 new_start = after;
1255 new_end = after;
1258 span_init(&c->new, new_start, new_end);
1259 span_init(&c->old, start, end);
1260 span_swap(txt, &c->old, &c->new);
1261 return true;
1264 bool text_delete_range(Text *txt, Filerange *r) {
1265 if (!text_range_valid(r))
1266 return false;
1267 return text_delete(txt, r->start, text_range_size(r));
1270 /* preserve the current text content such that it can be restored by
1271 * means of undo/redo operations */
1272 void text_snapshot(Text *txt) {
1273 if (txt->current_revision)
1274 txt->last_revision = txt->current_revision;
1275 txt->current_revision = NULL;
1276 txt->cache = NULL;
1280 void text_free(Text *txt) {
1281 if (!txt)
1282 return;
1284 // free history
1285 Revision *hist = txt->history;
1286 while (hist && hist->prev)
1287 hist = hist->prev;
1288 while (hist) {
1289 Revision *later = hist->later;
1290 revision_free(hist);
1291 hist = later;
1294 for (Piece *next, *p = txt->pieces; p; p = next) {
1295 next = p->global_next;
1296 piece_free(p);
1299 for (Buffer *next, *buf = txt->buffers; buf; buf = next) {
1300 next = buf->next;
1301 buffer_free(buf);
1304 free(txt);
1307 bool text_modified(Text *txt) {
1308 return txt->saved_revision != txt->history;
1311 bool text_sigbus(Text *txt, const char *addr) {
1312 for (Buffer *buf = txt->buffers; buf; buf = buf->next) {
1313 if ((buf->type == MMAP_ORIG || buf->type == MMAP) &&
1314 buf->data <= addr && addr < buf->data + buf->size)
1315 return true;
1317 return false;
1320 enum TextNewLine text_newline_type(Text *txt){
1321 if (!txt->newlines) {
1322 txt->newlines = TEXT_NEWLINE_NL; /* default to UNIX style \n new lines */
1323 const char *start = txt->buf ? txt->buf->data : NULL;
1324 if (start) {
1325 const char *nl = memchr(start, '\n', txt->buf->len);
1326 if (nl > start && nl[-1] == '\r')
1327 txt->newlines = TEXT_NEWLINE_CRNL;
1328 } else {
1329 char c;
1330 size_t nl = lines_skip_forward(txt, 0, 1, NULL);
1331 if (nl > 1 && text_byte_get(txt, nl-2, &c) && c == '\r')
1332 txt->newlines = TEXT_NEWLINE_CRNL;
1336 return txt->newlines;
1339 const char *text_newline_char(Text *txt) {
1340 static const char *types[] = {
1341 [TEXT_NEWLINE_NL] = "\n",
1342 [TEXT_NEWLINE_CRNL] = "\r\n",
1344 return types[text_newline_type(txt)];
1347 static bool text_iterator_init(Iterator *it, size_t pos, Piece *p, size_t off) {
1348 *it = (Iterator){
1349 .pos = pos,
1350 .piece = p,
1351 .start = p ? p->data : NULL,
1352 .end = p ? p->data + p->len : NULL,
1353 .text = p ? p->data + off : NULL,
1355 return text_iterator_valid(it);
1358 Iterator text_iterator_get(Text *txt, size_t pos) {
1359 Iterator it;
1360 Location loc = piece_get_extern(txt, pos);
1361 text_iterator_init(&it, pos, loc.piece, loc.off);
1362 return it;
1365 bool text_iterator_byte_get(Iterator *it, char *b) {
1366 if (text_iterator_valid(it)) {
1367 if (it->start <= it->text && it->text < it->end) {
1368 *b = *it->text;
1369 return true;
1370 } else if (it->pos == it->piece->text->size) { /* EOF */
1371 *b = '\0';
1372 return true;
1375 return false;
1378 bool text_iterator_next(Iterator *it) {
1379 return text_iterator_init(it, it->pos, it->piece ? it->piece->next : NULL, 0);
1382 bool text_iterator_prev(Iterator *it) {
1383 return text_iterator_init(it, it->pos, it->piece ? it->piece->prev : NULL, 0);
1386 bool text_iterator_valid(const Iterator *it) {
1387 /* filter out sentinel nodes */
1388 return it->piece && it->piece->text;
1391 bool text_iterator_byte_next(Iterator *it, char *b) {
1392 if (!text_iterator_valid(it))
1393 return false;
1394 it->text++;
1395 /* special case for advancement to EOF */
1396 if (it->text == it->end && !it->piece->next->text) {
1397 it->pos++;
1398 if (b)
1399 *b = '\0';
1400 return true;
1402 while (it->text >= it->end) {
1403 if (!text_iterator_next(it))
1404 return false;
1405 it->text = it->start;
1407 it->pos++;
1408 if (b)
1409 *b = *it->text;
1410 return true;
1413 bool text_iterator_byte_prev(Iterator *it, char *b) {
1414 if (!text_iterator_valid(it))
1415 return false;
1416 while (it->text == it->start) {
1417 if (!text_iterator_prev(it))
1418 return false;
1419 it->text = it->end;
1421 --it->text;
1422 --it->pos;
1423 if (b)
1424 *b = *it->text;
1425 return true;
1428 bool text_iterator_codepoint_next(Iterator *it, char *c) {
1429 while (text_iterator_byte_next(it, NULL)) {
1430 if (ISUTF8(*it->text)) {
1431 if (c)
1432 *c = *it->text;
1433 return true;
1436 return false;
1439 bool text_iterator_codepoint_prev(Iterator *it, char *c) {
1440 while (text_iterator_byte_prev(it, NULL)) {
1441 if (ISUTF8(*it->text)) {
1442 if (c)
1443 *c = *it->text;
1444 return true;
1447 return false;
1450 bool text_iterator_char_next(Iterator *it, char *c) {
1451 if (!text_iterator_codepoint_next(it, c))
1452 return false;
1453 mbstate_t ps = { 0 };
1454 for (;;) {
1455 char buf[MB_CUR_MAX];
1456 size_t len = text_bytes_get(it->piece->text, it->pos, sizeof buf, buf);
1457 wchar_t wc;
1458 size_t wclen = mbrtowc(&wc, buf, len, &ps);
1459 if (wclen == (size_t)-1 && errno == EILSEQ) {
1460 return true;
1461 } else if (wclen == (size_t)-2) {
1462 return false;
1463 } else if (wclen == 0) {
1464 return true;
1465 } else {
1466 int width = wcwidth(wc);
1467 if (width != 0)
1468 return true;
1469 if (!text_iterator_codepoint_next(it, c))
1470 return false;
1473 return true;
1476 bool text_iterator_char_prev(Iterator *it, char *c) {
1477 if (!text_iterator_codepoint_prev(it, c))
1478 return false;
1479 for (;;) {
1480 char buf[MB_CUR_MAX];
1481 size_t len = text_bytes_get(it->piece->text, it->pos, sizeof buf, buf);
1482 wchar_t wc;
1483 mbstate_t ps = { 0 };
1484 size_t wclen = mbrtowc(&wc, buf, len, &ps);
1485 if (wclen == (size_t)-1 && errno == EILSEQ) {
1486 return true;
1487 } else if (wclen == (size_t)-2) {
1488 return false;
1489 } else if (wclen == 0) {
1490 return true;
1491 } else {
1492 int width = wcwidth(wc);
1493 if (width != 0)
1494 return true;
1495 if (!text_iterator_codepoint_prev(it, c))
1496 return false;
1499 return true;
1502 bool text_byte_get(Text *txt, size_t pos, char *buf) {
1503 return text_bytes_get(txt, pos, 1, buf);
1506 size_t text_bytes_get(Text *txt, size_t pos, size_t len, char *buf) {
1507 if (!buf)
1508 return 0;
1509 char *cur = buf;
1510 size_t rem = len;
1511 text_iterate(txt, it, pos) {
1512 if (rem == 0)
1513 break;
1514 size_t piece_len = it.end - it.text;
1515 if (piece_len > rem)
1516 piece_len = rem;
1517 memcpy(cur, it.text, piece_len);
1518 cur += piece_len;
1519 rem -= piece_len;
1521 return len - rem;
1524 char *text_bytes_alloc0(Text *txt, size_t pos, size_t len) {
1525 if (len == SIZE_MAX)
1526 return NULL;
1527 char *buf = malloc(len+1);
1528 if (!buf)
1529 return NULL;
1530 len = text_bytes_get(txt, pos, len, buf);
1531 buf[len] = '\0';
1532 return buf;
1535 size_t text_size(Text *txt) {
1536 return txt->size;
1539 /* count the number of new lines '\n' in range [pos, pos+len) */
1540 static size_t lines_count(Text *txt, size_t pos, size_t len) {
1541 size_t lines = 0;
1542 text_iterate(txt, it, pos) {
1543 const char *start = it.text;
1544 while (len > 0 && start < it.end) {
1545 size_t n = MIN(len, (size_t)(it.end - start));
1546 const char *end = memchr(start, '\n', n);
1547 if (!end) {
1548 len -= n;
1549 break;
1551 lines++;
1552 len -= end - start + 1;
1553 start = end + 1;
1556 if (len == 0)
1557 break;
1559 return lines;
1562 /* skip n lines forward and return position afterwards */
1563 static size_t lines_skip_forward(Text *txt, size_t pos, size_t lines, size_t *lines_skipped) {
1564 size_t lines_old = lines;
1565 text_iterate(txt, it, pos) {
1566 const char *start = it.text;
1567 while (lines > 0 && start < it.end) {
1568 size_t n = it.end - start;
1569 const char *end = memchr(start, '\n', n);
1570 if (!end) {
1571 pos += n;
1572 break;
1574 pos += end - start + 1;
1575 start = end + 1;
1576 lines--;
1579 if (lines == 0)
1580 break;
1582 if (lines_skipped)
1583 *lines_skipped = lines_old - lines;
1584 return pos;
1587 static void lineno_cache_invalidate(LineCache *cache) {
1588 cache->pos = 0;
1589 cache->lineno = 1;
1592 size_t text_pos_by_lineno(Text *txt, size_t lineno) {
1593 size_t lines_skipped;
1594 LineCache *cache = &txt->lines;
1595 if (lineno <= 1)
1596 return 0;
1597 if (lineno > cache->lineno) {
1598 cache->pos = lines_skip_forward(txt, cache->pos, lineno - cache->lineno, &lines_skipped);
1599 cache->lineno += lines_skipped;
1600 } else if (lineno < cache->lineno) {
1601 #if 0
1602 // TODO does it make sense to scan memory backwards here?
1603 size_t diff = cache->lineno - lineno;
1604 if (diff < lineno) {
1605 lines_skip_backward(txt, cache->pos, diff);
1606 } else
1607 #endif
1608 cache->pos = lines_skip_forward(txt, 0, lineno - 1, &lines_skipped);
1609 cache->lineno = lines_skipped + 1;
1611 return cache->lineno == lineno ? cache->pos : EPOS;
1614 size_t text_lineno_by_pos(Text *txt, size_t pos) {
1615 LineCache *cache = &txt->lines;
1616 if (pos > txt->size)
1617 pos = txt->size;
1618 if (pos < cache->pos) {
1619 size_t diff = cache->pos - pos;
1620 if (diff < pos)
1621 cache->lineno -= lines_count(txt, pos, diff);
1622 else
1623 cache->lineno = lines_count(txt, 0, pos) + 1;
1624 } else if (pos > cache->pos) {
1625 cache->lineno += lines_count(txt, cache->pos, pos - cache->pos);
1627 cache->pos = text_line_begin(txt, pos);
1628 return cache->lineno;
1631 Mark text_mark_set(Text *txt, size_t pos) {
1632 if (pos == 0)
1633 return (Mark)&txt->begin;
1634 if (pos == txt->size)
1635 return (Mark)&txt->end;
1636 Location loc = piece_get_extern(txt, pos);
1637 if (!loc.piece)
1638 return NULL;
1639 return loc.piece->data + loc.off;
1642 size_t text_mark_get(Text *txt, Mark mark) {
1643 size_t cur = 0;
1645 if (!mark)
1646 return EPOS;
1647 if (mark == (Mark)&txt->begin)
1648 return 0;
1649 if (mark == (Mark)&txt->end)
1650 return txt->size;
1652 for (Piece *p = txt->begin.next; p->next; p = p->next) {
1653 if (p->data <= mark && mark < p->data + p->len)
1654 return cur + (mark - p->data);
1655 cur += p->len;
1658 return EPOS;
1661 size_t text_history_get(Text *txt, size_t index) {
1662 for (Revision *rev = txt->current_revision ? txt->current_revision : txt->history; rev; rev = rev->prev) {
1663 if (index-- == 0) {
1664 Change *c = rev->change;
1665 while (c && c->next)
1666 c = c->next;
1667 return c ? c->pos : EPOS;
1670 return EPOS;