vis: remove __DATE__ and __TIME__ references to aid with reproducible builds
[vis.git] / text.c
blob8bdb5e518ccc6deeed21d5a88c2bed3edd4d1354
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 <sys/types.h>
11 #include <sys/stat.h>
12 #include <sys/mman.h>
13 #if CONFIG_ACL
14 #include <sys/acl.h>
15 #endif
16 #if CONFIG_SELINUX
17 #include <selinux/selinux.h>
18 #endif
20 #include "text.h"
21 #include "text-util.h"
22 #include "util.h"
24 /* Allocate buffers holding the actual file content in junks of size: */
25 #define BUFFER_SIZE (1 << 20)
26 /* Files smaller than this value are copied on load, larger ones are mmap(2)-ed
27 * directely. Hence the former can be truncated, while doing so on the latter
28 * results in havoc. */
29 #define BUFFER_MMAP_SIZE (1 << 23)
31 /* Buffer holding the file content, either readonly mmap(2)-ed from the original
32 * file or heap allocated to store the modifications.
34 typedef struct Buffer Buffer;
35 struct Buffer {
36 size_t size; /* maximal capacity */
37 size_t len; /* current used length / insertion position */
38 char *data; /* actual data */
39 enum { MMAP, MALLOC} type; /* type of allocation */
40 Buffer *next; /* next junk */
43 /* A piece holds a reference (but doesn't itself store) a certain amount of data.
44 * All active pieces chained together form the whole content of the document.
45 * At the beginning there exists only one piece, spanning the whole document.
46 * Upon insertion/deletion new pieces will be created to represent the changes.
47 * Generally pieces are never destroyed, but kept around to peform undo/redo
48 * operations.
50 struct Piece {
51 Text *text; /* text to which this piece belongs */
52 Piece *prev, *next; /* pointers to the logical predecessor/successor */
53 Piece *global_prev; /* double linked list in order of allocation, */
54 Piece *global_next; /* used to free individual pieces */
55 const char *data; /* pointer into a Buffer holding the data */
56 size_t len; /* the length in number of bytes of the data */
59 /* used to transform a global position (byte offset starting from the beginning
60 * of the text) into an offset relative to a piece.
62 typedef struct {
63 Piece *piece; /* piece holding the location */
64 size_t off; /* offset into the piece in bytes */
65 } Location;
67 /* A Span holds a certain range of pieces. Changes to the document are always
68 * performed by swapping out an existing span with a new one.
70 typedef struct {
71 Piece *start, *end; /* start/end of the span */
72 size_t len; /* the sum of the lengths of the pieces which form this span */
73 } Span;
75 /* A Change keeps all needed information to redo/undo an insertion/deletion. */
76 typedef struct Change Change;
77 struct Change {
78 Span old; /* all pieces which are being modified/swapped out by the change */
79 Span new; /* all pieces which are introduced/swapped in by the change */
80 size_t pos; /* absolute position at which the change occured */
81 Change *next; /* next change which is part of the same action */
82 Change *prev; /* previous change which is part of the same action */
85 /* An Action is a list of Changes which are used to undo/redo all modifications
86 * since the last snapshot operation. Actions are stored in a directed graph structure.
88 typedef struct Action Action;
89 struct Action {
90 Change *change; /* the most recent change */
91 Action *next; /* the next (child) action in the undo tree */
92 Action *prev; /* the previous (parent) operation in the undo tree */
93 Action *earlier; /* the previous Action, chronologically */
94 Action *later; /* the next Action, chronologically */
95 time_t time; /* when the first change of this action was performed */
96 size_t seq; /* a unique, strictly increasing identifier */
99 typedef struct {
100 size_t pos; /* position in bytes from start of file */
101 size_t lineno; /* line number in file i.e. number of '\n' in [0, pos) */
102 } LineCache;
104 /* The main struct holding all information of a given file */
105 struct Text {
106 Buffer *buf; /* original file content at the time of load operation */
107 Buffer *buffers; /* all buffers which have been allocated to hold insertion data */
108 Piece *pieces; /* all pieces which have been allocated, used to free them */
109 Piece *cache; /* most recently modified piece */
110 Piece begin, end; /* sentinel nodes which always exists but don't hold any data */
111 Action *history; /* undo tree */
112 Action *current_action; /* action holding all file changes until a snapshot is performed */
113 Action *last_action; /* the last action added to the tree, chronologically */
114 Action *saved_action; /* the last action at the time of the save operation */
115 size_t size; /* current file content size in bytes */
116 struct stat info; /* stat as probed at load time */
117 LineCache lines; /* mapping between absolute pos in bytes and logical line breaks */
118 enum TextNewLine newlines; /* which type of new lines does the file use */
121 /* buffer management */
122 static Buffer *buffer_alloc(Text *txt, size_t size);
123 static Buffer *buffer_read(Text *txt, size_t size, int fd);
124 static Buffer *buffer_mmap(Text *txt, size_t size, int fd, off_t offset);
125 static void buffer_free(Buffer *buf);
126 static bool buffer_capacity(Buffer *buf, size_t len);
127 static const char *buffer_append(Buffer *buf, const char *data, size_t len);
128 static bool buffer_insert(Buffer *buf, size_t pos, const char *data, size_t len);
129 static bool buffer_delete(Buffer *buf, size_t pos, size_t len);
130 static const char *buffer_store(Text *txt, const char *data, size_t len);
131 /* cache layer */
132 static void cache_piece(Text *txt, Piece *p);
133 static bool cache_contains(Text *txt, Piece *p);
134 static bool cache_insert(Text *txt, Piece *p, size_t off, const char *data, size_t len);
135 static bool cache_delete(Text *txt, Piece *p, size_t off, size_t len);
136 /* piece management */
137 static Piece *piece_alloc(Text *txt);
138 static void piece_free(Piece *p);
139 static void piece_init(Piece *p, Piece *prev, Piece *next, const char *data, size_t len);
140 static Location piece_get_intern(Text *txt, size_t pos);
141 static Location piece_get_extern(Text *txt, size_t pos);
142 /* span management */
143 static void span_init(Span *span, Piece *start, Piece *end);
144 static void span_swap(Text *txt, Span *old, Span *new);
145 /* change management */
146 static Change *change_alloc(Text *txt, size_t pos);
147 static void change_free(Change *c);
148 /* action management */
149 static Action *action_alloc(Text *txt);
150 static void action_free(Action *a);
151 /* logical line counting cache */
152 static void lineno_cache_invalidate(LineCache *cache);
153 static size_t lines_skip_forward(Text *txt, size_t pos, size_t lines, size_t *lines_skiped);
154 static size_t lines_count(Text *txt, size_t pos, size_t len);
156 static ssize_t write_all(int fd, const char *buf, size_t count) {
157 size_t rem = count;
158 while (rem > 0) {
159 ssize_t written = write(fd, buf, rem);
160 if (written < 0) {
161 if (errno == EAGAIN || errno == EINTR)
162 continue;
163 return -1;
164 } else if (written == 0) {
165 break;
167 rem -= written;
168 buf += written;
170 return count - rem;
173 /* allocate a new buffer of MAX(size, BUFFER_SIZE) bytes */
174 static Buffer *buffer_alloc(Text *txt, size_t size) {
175 Buffer *buf = calloc(1, sizeof(Buffer));
176 if (!buf)
177 return NULL;
178 if (BUFFER_SIZE > size)
179 size = BUFFER_SIZE;
180 if (!(buf->data = malloc(size))) {
181 free(buf);
182 return NULL;
184 buf->type = MALLOC;
185 buf->size = size;
186 buf->next = txt->buffers;
187 txt->buffers = buf;
188 return buf;
191 static Buffer *buffer_read(Text *txt, size_t size, int fd) {
192 Buffer *buf = buffer_alloc(txt, size);
193 if (!buf)
194 return NULL;
195 while (size > 0) {
196 char data[4096];
197 ssize_t len = read(fd, data, MIN(sizeof(data), size));
198 if (len == -1) {
199 txt->buffers = buf->next;
200 buffer_free(buf);
201 return NULL;
202 } else if (len == 0) {
203 break;
204 } else {
205 buffer_append(buf, data, len);
206 size -= len;
209 return buf;
212 static Buffer *buffer_mmap(Text *txt, size_t size, int fd, off_t offset) {
213 Buffer *buf = calloc(1, sizeof(Buffer));
214 if (!buf)
215 return NULL;
216 if (size) {
217 buf->data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset);
218 if (buf->data == MAP_FAILED) {
219 free(buf);
220 return NULL;
223 buf->type = MMAP;
224 buf->size = size;
225 buf->len = size;
226 buf->next = txt->buffers;
227 txt->buffers = buf;
228 return buf;
231 static void buffer_free(Buffer *buf) {
232 if (!buf)
233 return;
234 if (buf->type == MALLOC)
235 free(buf->data);
236 else if (buf->type == MMAP && buf->data)
237 munmap(buf->data, buf->size);
238 free(buf);
241 /* check whether buffer has enough free space to store len bytes */
242 static bool buffer_capacity(Buffer *buf, size_t len) {
243 return buf->size - buf->len >= len;
246 /* append data to buffer, assumes there is enough space available */
247 static const char *buffer_append(Buffer *buf, const char *data, size_t len) {
248 char *dest = memcpy(buf->data + buf->len, data, len);
249 buf->len += len;
250 return dest;
253 /* stores the given data in a buffer, allocates a new one if necessary. returns
254 * a pointer to the storage location or NULL if allocation failed. */
255 static const char *buffer_store(Text *txt, const char *data, size_t len) {
256 Buffer *buf = txt->buffers;
257 if ((!buf || !buffer_capacity(buf, len)) && !(buf = buffer_alloc(txt, len)))
258 return NULL;
259 return buffer_append(buf, data, len);
262 /* insert data into buffer at an arbitrary position, this should only be used with
263 * data of the most recently created piece. */
264 static bool buffer_insert(Buffer *buf, size_t pos, const char *data, size_t len) {
265 if (pos > buf->len || !buffer_capacity(buf, len))
266 return false;
267 if (buf->len == pos)
268 return buffer_append(buf, data, len);
269 char *insert = buf->data + pos;
270 memmove(insert + len, insert, buf->len - pos);
271 memcpy(insert, data, len);
272 buf->len += len;
273 return true;
276 /* delete data from a buffer at an arbitrary position, this should only be used with
277 * data of the most recently created piece. */
278 static bool buffer_delete(Buffer *buf, size_t pos, size_t len) {
279 if (pos + len > buf->len)
280 return false;
281 if (buf->len == pos) {
282 buf->len -= len;
283 return true;
285 char *delete = buf->data + pos;
286 memmove(delete, delete + len, buf->len - pos - len);
287 buf->len -= len;
288 return true;
291 /* cache the given piece if it is the most recently changed one */
292 static void cache_piece(Text *txt, Piece *p) {
293 Buffer *buf = txt->buffers;
294 if (!buf || p->data < buf->data || p->data + p->len != buf->data + buf->len)
295 return;
296 txt->cache = p;
299 /* check whether the given piece was the most recently modified one */
300 static bool cache_contains(Text *txt, Piece *p) {
301 Buffer *buf = txt->buffers;
302 Action *a = txt->current_action;
303 if (!buf || !txt->cache || txt->cache != p || !a || !a->change)
304 return false;
306 Piece *start = a->change->new.start;
307 Piece *end = a->change->new.end;
308 bool found = false;
309 for (Piece *cur = start; !found; cur = cur->next) {
310 if (cur == p)
311 found = true;
312 if (cur == end)
313 break;
316 return found && p->data + p->len == buf->data + buf->len;
319 /* try to insert a junk of data at a given piece offset. the insertion is only
320 * performed if the piece is the most recenetly changed one. the legnth of the
321 * piece, the span containing it and the whole text is adjusted accordingly */
322 static bool cache_insert(Text *txt, Piece *p, size_t off, const char *data, size_t len) {
323 if (!cache_contains(txt, p))
324 return false;
325 Buffer *buf = txt->buffers;
326 size_t bufpos = p->data + off - buf->data;
327 if (!buffer_insert(buf, bufpos, data, len))
328 return false;
329 p->len += len;
330 txt->current_action->change->new.len += len;
331 txt->size += len;
332 return true;
335 /* try to delete a junk of data at a given piece offset. the deletion is only
336 * performed if the piece is the most recenetly changed one and the whole
337 * affected range lies within it. the legnth of the piece, the span containing it
338 * and the whole text is adjusted accordingly */
339 static bool cache_delete(Text *txt, Piece *p, size_t off, size_t len) {
340 if (!cache_contains(txt, p))
341 return false;
342 Buffer *buf = txt->buffers;
343 size_t bufpos = p->data + off - buf->data;
344 if (off + len > p->len || !buffer_delete(buf, bufpos, len))
345 return false;
346 p->len -= len;
347 txt->current_action->change->new.len -= len;
348 txt->size -= len;
349 return true;
352 /* initialize a span and calculate its length */
353 static void span_init(Span *span, Piece *start, Piece *end) {
354 size_t len = 0;
355 span->start = start;
356 span->end = end;
357 for (Piece *p = start; p; p = p->next) {
358 len += p->len;
359 if (p == end)
360 break;
362 span->len = len;
365 /* swap out an old span and replace it with a new one.
367 * - if old is an empty span do not remove anything, just insert the new one
368 * - if new is an empty span do not insert anything, just remove the old one
370 * adjusts the document size accordingly.
372 static void span_swap(Text *txt, Span *old, Span *new) {
373 if (old->len == 0 && new->len == 0) {
374 return;
375 } else if (old->len == 0) {
376 /* insert new span */
377 new->start->prev->next = new->start;
378 new->end->next->prev = new->end;
379 } else if (new->len == 0) {
380 /* delete old span */
381 old->start->prev->next = old->end->next;
382 old->end->next->prev = old->start->prev;
383 } else {
384 /* replace old with new */
385 old->start->prev->next = new->start;
386 old->end->next->prev = new->end;
388 txt->size -= old->len;
389 txt->size += new->len;
392 /* allocate a new action, set its pointers to the other actions in the history,
393 * and set it as txt->history. All further changes will be associated with this action. */
394 static Action *action_alloc(Text *txt) {
395 Action *new = calloc(1, sizeof(Action));
396 if (!new)
397 return NULL;
398 new->time = time(NULL);
399 txt->current_action = new;
401 /* set sequence number */
402 if (!txt->last_action)
403 new->seq = 0;
404 else
405 new->seq = txt->last_action->seq + 1;
407 /* set earlier, later pointers */
408 if (txt->last_action)
409 txt->last_action->later = new;
410 new->earlier = txt->last_action;
412 if (!txt->history) {
413 txt->history = new;
414 return new;
417 /* set prev, next pointers */
418 new->prev = txt->history;
419 txt->history->next = new;
420 txt->history = new;
421 return new;
424 static void action_free(Action *a) {
425 if (!a)
426 return;
427 for (Change *next, *c = a->change; c; c = next) {
428 next = c->next;
429 change_free(c);
431 free(a);
434 static Piece *piece_alloc(Text *txt) {
435 Piece *p = calloc(1, sizeof(Piece));
436 if (!p)
437 return NULL;
438 p->text = txt;
439 p->global_next = txt->pieces;
440 if (txt->pieces)
441 txt->pieces->global_prev = p;
442 txt->pieces = p;
443 return p;
446 static void piece_free(Piece *p) {
447 if (!p)
448 return;
449 if (p->global_prev)
450 p->global_prev->global_next = p->global_next;
451 if (p->global_next)
452 p->global_next->global_prev = p->global_prev;
453 if (p->text->pieces == p)
454 p->text->pieces = p->global_next;
455 if (p->text->cache == p)
456 p->text->cache = NULL;
457 free(p);
460 static void piece_init(Piece *p, Piece *prev, Piece *next, const char *data, size_t len) {
461 p->prev = prev;
462 p->next = next;
463 p->data = data;
464 p->len = len;
467 /* returns the piece holding the text at byte offset pos. if pos happens to
468 * be at a piece boundry i.e. the first byte of a piece then the previous piece
469 * to the left is returned with an offset of piece->len. this is convenient for
470 * modifications to the piece chain where both pieces (the returned one and the
471 * one following it) are needed, but unsuitable as a public interface.
473 * in particular if pos is zero, the begin sentinel piece is returned.
475 static Location piece_get_intern(Text *txt, size_t pos) {
476 size_t cur = 0;
477 for (Piece *p = &txt->begin; p->next; p = p->next) {
478 if (cur <= pos && pos <= cur + p->len)
479 return (Location){ .piece = p, .off = pos - cur };
480 cur += p->len;
483 return (Location){ 0 };
486 /* similiar to piece_get_intern but usable as a public API. returns the piece
487 * holding the text at byte offset pos. never returns a sentinel piece.
488 * it pos is the end of file (== text_size()) and the file is not empty then
489 * the last piece holding data is returned.
491 static Location piece_get_extern(Text *txt, size_t pos) {
492 size_t cur = 0;
494 if (pos > 0 && pos == txt->size) {
495 Piece *p = txt->begin.next;
496 while (p->next->next)
497 p = p->next;
498 return (Location){ .piece = p, .off = p->len };
501 for (Piece *p = txt->begin.next; p->next; p = p->next) {
502 if (cur <= pos && pos < cur + p->len)
503 return (Location){ .piece = p, .off = pos - cur };
504 cur += p->len;
507 return (Location){ 0 };
510 /* allocate a new change, associate it with current action or a newly
511 * allocated one if none exists. */
512 static Change *change_alloc(Text *txt, size_t pos) {
513 Action *a = txt->current_action;
514 if (!a) {
515 a = action_alloc(txt);
516 if (!a)
517 return NULL;
519 Change *c = calloc(1, sizeof(Change));
520 if (!c)
521 return NULL;
522 c->pos = pos;
523 c->next = a->change;
524 if (a->change)
525 a->change->prev = c;
526 a->change = c;
527 return c;
530 static void change_free(Change *c) {
531 if (!c)
532 return;
533 /* only free the new part of the span, the old one is still in use */
534 piece_free(c->new.start);
535 if (c->new.start != c->new.end)
536 piece_free(c->new.end);
537 free(c);
540 /* When inserting new data there are 2 cases to consider.
542 * - in the first the insertion point falls into the middle of an exisiting
543 * piece which is replaced by three new pieces:
545 * /-+ --> +---------------+ --> +-\
546 * | | | existing text | | |
547 * \-+ <-- +---------------+ <-- +-/
549 * Insertion point for "demo "
551 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
552 * | | | existing| |demo | |text | | |
553 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
555 * - the second case deals with an insertion point at a piece boundry:
557 * /-+ --> +---------------+ --> +-\
558 * | | | existing text | | |
559 * \-+ <-- +---------------+ <-- +-/
561 * Insertion point for "short"
563 * /-+ --> +-----+ --> +---------------+ --> +-\
564 * | | |short| | existing text | | |
565 * \-+ <-- +-----+ <-- +---------------+ <-- +-/
567 bool text_insert(Text *txt, size_t pos, const char *data, size_t len) {
568 if (len == 0)
569 return true;
570 if (pos > txt->size)
571 return false;
572 if (pos < txt->lines.pos)
573 lineno_cache_invalidate(&txt->lines);
575 Location loc = piece_get_intern(txt, pos);
576 Piece *p = loc.piece;
577 if (!p)
578 return false;
579 size_t off = loc.off;
580 if (cache_insert(txt, p, off, data, len))
581 return true;
583 Change *c = change_alloc(txt, pos);
584 if (!c)
585 return false;
587 if (!(data = buffer_store(txt, data, len)))
588 return false;
590 Piece *new = NULL;
592 if (off == p->len) {
593 /* insert between two existing pieces, hence there is nothing to
594 * remove, just add a new piece holding the extra text */
595 if (!(new = piece_alloc(txt)))
596 return false;
597 piece_init(new, p, p->next, data, len);
598 span_init(&c->new, new, new);
599 span_init(&c->old, NULL, NULL);
600 } else {
601 /* insert into middle of an existing piece, therfore split the old
602 * piece. that is we have 3 new pieces one containing the content
603 * before the insertion point then one holding the newly inserted
604 * text and one holding the content after the insertion point.
606 Piece *before = piece_alloc(txt);
607 new = piece_alloc(txt);
608 Piece *after = piece_alloc(txt);
609 if (!before || !new || !after)
610 return false;
611 piece_init(before, p->prev, new, p->data, off);
612 piece_init(new, before, after, data, len);
613 piece_init(after, new, p->next, p->data + off, p->len - off);
615 span_init(&c->new, before, after);
616 span_init(&c->old, p, p);
619 cache_piece(txt, new);
620 span_swap(txt, &c->old, &c->new);
621 return true;
624 bool text_appendf(Text *txt, const char *format, ...) {
625 va_list ap;
626 va_start(ap, format);
627 bool ret = text_vprintf(txt, text_size(txt), format, ap);
628 va_end(ap);
629 return ret;
632 bool text_printf(Text *txt, size_t pos, const char *format, ...) {
633 va_list ap;
634 va_start(ap, format);
635 bool ret = text_vprintf(txt, pos, format, ap);
636 va_end(ap);
637 return ret;
640 bool text_vprintf(Text *txt, size_t pos, const char *format, va_list ap) {
641 va_list ap_save;
642 va_copy(ap_save, ap);
643 int len = vsnprintf(NULL, 0, format, ap);
644 if (len == -1)
645 return false;
646 char *buf = malloc(len+1);
647 bool ret = buf && (vsnprintf(buf, len+1, format, ap_save) == len) && text_insert(txt, pos, buf, len);
648 free(buf);
649 va_end(ap_save);
650 return ret;
653 size_t text_insert_newline(Text *txt, size_t pos) {
654 const char *data = text_newline_char(txt);
655 size_t len = strlen(data);
656 return text_insert(txt, pos, data, len) ? len : 0;
659 static size_t action_undo(Text *txt, Action *a) {
660 size_t pos = EPOS;
661 for (Change *c = a->change; c; c = c->next) {
662 span_swap(txt, &c->new, &c->old);
663 pos = c->pos;
665 return pos;
668 static size_t action_redo(Text *txt, Action *a) {
669 size_t pos = EPOS;
670 Change *c = a->change;
671 while (c->next)
672 c = c->next;
673 for ( ; c; c = c->prev) {
674 span_swap(txt, &c->old, &c->new);
675 pos = c->pos;
676 if (c->new.len > c->old.len)
677 pos += c->new.len - c->old.len;
679 return pos;
682 size_t text_undo(Text *txt) {
683 size_t pos = EPOS;
684 /* taking a snapshot makes sure that txt->current_action is reset */
685 text_snapshot(txt);
686 Action *a = txt->history->prev;
687 if (!a)
688 return pos;
689 pos = action_undo(txt, txt->history);
690 txt->history = a;
691 lineno_cache_invalidate(&txt->lines);
692 return pos;
695 size_t text_redo(Text *txt) {
696 size_t pos = EPOS;
697 /* taking a snapshot makes sure that txt->current_action is reset */
698 text_snapshot(txt);
699 Action *a = txt->history->next;
700 if (!a)
701 return pos;
702 pos = action_redo(txt, a);
703 txt->history = a;
704 lineno_cache_invalidate(&txt->lines);
705 return pos;
708 static bool history_change_branch(Action *a) {
709 bool changed = false;
710 while (a->prev) {
711 if (a->prev->next != a) {
712 a->prev->next = a;
713 changed = true;
715 a = a->prev;
717 return changed;
720 static size_t history_traverse_to(Text *txt, Action *a) {
721 size_t pos = EPOS;
722 if (!a)
723 return pos;
724 bool changed = history_change_branch(a);
725 if (!changed) {
726 if (a->seq == txt->history->seq) {
727 return txt->lines.pos;
728 } else if (a->seq > txt->history->seq) {
729 while (txt->history != a)
730 pos = text_redo(txt);
731 return pos;
732 } else if (a->seq < txt->history->seq) {
733 while (txt->history != a)
734 pos = text_undo(txt);
735 return pos;
737 } else {
738 while (txt->history->prev && txt->history->prev->next == txt->history)
739 text_undo(txt);
740 pos = text_undo(txt);
741 while (txt->history != a)
742 pos = text_redo(txt);
743 return pos;
745 return pos;
748 size_t text_earlier(Text *txt, int count) {
749 Action *a = txt->history;
750 while (count-- > 0 && a->earlier)
751 a = a->earlier;
752 return history_traverse_to(txt, a);
755 size_t text_later(Text *txt, int count) {
756 Action *a = txt->history;
757 while (count-- > 0 && a->later)
758 a = a->later;
759 return history_traverse_to(txt, a);
762 size_t text_restore(Text *txt, time_t time) {
763 Action *a = txt->history;
764 while (time < a->time && a->earlier)
765 a = a->earlier;
766 while (time > a->time && a->later)
767 a = a->later;
768 time_t diff = labs(a->time - time);
769 if (a->earlier && a->earlier != txt->history && labs(a->earlier->time - time) < diff)
770 a = a->earlier;
771 if (a->later && a->later != txt->history && labs(a->later->time - time) < diff)
772 a = a->later;
773 return history_traverse_to(txt, a);
776 time_t text_state(Text *txt) {
777 return txt->history->time;
780 static bool preserve_acl(int src, int dest) {
781 #if CONFIG_ACL
782 acl_t acl = acl_get_fd(src);
783 if (!acl)
784 return errno == ENOTSUP ? true : false;
785 if (acl_set_fd(dest, acl) == -1) {
786 acl_free(acl);
787 return false;
789 acl_free(acl);
790 #endif /* CONFIG_ACL */
791 return true;
794 static bool preserve_selinux_context(int src, int dest) {
795 #if CONFIG_SELINUX
796 char *context = NULL;
797 if (!is_selinux_enabled())
798 return true;
799 if (fgetfilecon(src, &context) == -1)
800 return errno == ENOTSUP ? true : false;
801 if (fsetfilecon(dest, context) == -1) {
802 freecon(context);
803 return false;
805 freecon(context);
806 #endif /* CONFIG_SELINUX */
807 return true;
810 /* Save current content to given filename. The data is first saved to `filename~`
811 * and then atomically moved to its final (possibly alredy existing) destination
812 * using rename(2). This approach does not work if:
814 * - the file is a symbolic link
815 * - the file is a hard link
816 * - file ownership can not be preserved
817 * - file group can not be preserved
818 * - directory permissions do not allow creation of a new file
819 * - POSXI ACL can not be preserved (if enabled)
820 * - SELinux security context can not be preserved (if enabled)
822 static bool text_save_atomic_range(Text *txt, Filerange *range, const char *filename) {
823 struct stat meta = { 0 }, oldmeta = { 0 };
824 int fd = -1, oldfd = -1, saved_errno;
825 char *tmpname = NULL;
826 size_t size = text_range_size(range);
827 size_t namelen = strlen(filename) + 1 /* ~ */ + 1 /* \0 */;
829 if ((oldfd = open(filename, O_RDONLY)) == -1 && errno != ENOENT)
830 goto err;
831 if (oldfd != -1 && lstat(filename, &oldmeta) == -1)
832 goto err;
833 if (oldfd != -1) {
834 if (S_ISLNK(oldmeta.st_mode)) /* symbolic link */
835 goto err;
836 if (oldmeta.st_nlink > 1) /* hard link */
837 goto err;
839 if (!(tmpname = calloc(1, namelen)))
840 goto err;
841 snprintf(tmpname, namelen, "%s~", filename);
843 /* O_RDWR is needed because otherwise we can't map with MAP_SHARED */
844 if ((fd = open(tmpname, O_CREAT|O_RDWR|O_TRUNC, oldfd == -1 ? S_IRUSR|S_IWUSR : oldmeta.st_mode)) == -1)
845 goto err;
846 if (ftruncate(fd, size) == -1)
847 goto err;
848 if (oldfd != -1) {
849 if (!preserve_acl(oldfd, fd) || !preserve_selinux_context(oldfd, fd))
850 goto err;
851 /* change owner if necessary */
852 if (oldmeta.st_uid != getuid() && fchown(fd, oldmeta.st_uid, (uid_t)-1) == -1)
853 goto err;
854 /* change group if necessary, in case of failure some editors reset
855 * the group permissions to the same as for others */
856 if (oldmeta.st_gid != getgid() && fchown(fd, (uid_t)-1, oldmeta.st_gid) == -1)
857 goto err;
860 if (size > 0) {
861 void *buf = mmap(NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
862 if (buf == MAP_FAILED)
863 goto err;
865 char *cur = buf;
866 size_t rem = size;
868 for (Iterator it = text_iterator_get(txt, range->start);
869 rem > 0 && text_iterator_valid(&it);
870 text_iterator_next(&it)) {
871 size_t len = it.end - it.text;
872 if (len > rem)
873 len = rem;
874 memcpy(cur, it.text, len);
875 cur += len;
876 rem -= len;
879 if (munmap(buf, size) == -1)
880 goto err;
883 if (oldfd != -1) {
884 close(oldfd);
885 oldfd = -1;
888 if (fsync(fd) == -1)
889 goto err;
891 if (fstat(fd, &meta) == -1)
892 goto err;
894 if (close(fd) == -1) {
895 fd = -1;
896 goto err;
898 fd = -1;
900 if (rename(tmpname, filename) == -1)
901 goto err;
903 if (meta.st_mtime)
904 txt->info = meta;
905 free(tmpname);
906 return true;
907 err:
908 saved_errno = errno;
909 if (oldfd != -1)
910 close(oldfd);
911 if (fd != -1)
912 close(fd);
913 if (tmpname && *tmpname)
914 unlink(tmpname);
915 free(tmpname);
916 errno = saved_errno;
917 return false;
920 bool text_save(Text *txt, const char *filename) {
921 Filerange r = (Filerange){ .start = 0, .end = text_size(txt) };
922 return text_save_range(txt, &r, filename);
925 /* First try to save the file atomically using rename(2) if this does not
926 * work overwrite the file in place. However if something goes wrong during
927 * this overwrite the original file is permanently damaged.
929 bool text_save_range(Text *txt, Filerange *range, const char *filename) {
930 struct stat meta;
931 int fd = -1, newfd = -1;
932 errno = 0;
933 if (!filename || text_save_atomic_range(txt, range, filename))
934 goto ok;
935 if (errno == ENOSPC)
936 goto err;
937 if ((fd = open(filename, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR)) == -1)
938 goto err;
939 if (fstat(fd, &meta) == -1)
940 goto err;
941 if (meta.st_dev == txt->info.st_dev && meta.st_ino == txt->info.st_ino &&
942 txt->buf && txt->buf->type == MMAP && txt->buf->size) {
943 /* The file we are going to overwrite is currently mmap-ed from
944 * text_load, therefore we copy the mmap-ed buffer to a temporary
945 * file and remap it at the same position such that all pointers
946 * from the various pieces are still valid.
948 size_t size = txt->buf->size;
949 char tmpname[32] = "/tmp/vis-XXXXXX";
950 mode_t mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
951 newfd = mkstemp(tmpname);
952 umask(mask);
953 if (newfd == -1)
954 goto err;
955 if (unlink(tmpname) == -1)
956 goto err;
957 ssize_t written = write_all(newfd, txt->buf->data, size);
958 if (written == -1 || (size_t)written != size)
959 goto err;
960 if (munmap(txt->buf->data, size) == -1)
961 goto err;
963 void *data = mmap(txt->buf->data, size, PROT_READ, MAP_SHARED, newfd, 0);
964 if (data == MAP_FAILED)
965 goto err;
966 if (data != txt->buf->data) {
967 munmap(data, size);
968 goto err;
970 if (close(newfd) == -1) {
971 newfd = -1;
972 goto err;
974 txt->buf->data = data;
975 newfd = -1;
977 /* overwrite the exisiting file content, if somehting goes wrong
978 * here we are screwed, TODO: make a backup before? */
979 if (ftruncate(fd, 0) == -1)
980 goto err;
981 ssize_t written = text_write_range(txt, range, fd);
982 if (written == -1 || (size_t)written != text_range_size(range))
983 goto err;
985 if (fsync(fd) == -1)
986 goto err;
987 if (fstat(fd, &meta) == -1)
988 goto err;
989 if (close(fd) == -1)
990 return false;
991 txt->info = meta;
993 txt->saved_action = txt->history;
994 text_snapshot(txt);
995 return true;
996 err:
997 if (fd != -1)
998 close(fd);
999 if (newfd != -1)
1000 close(newfd);
1001 return false;
1004 ssize_t text_write(Text *txt, int fd) {
1005 Filerange r = (Filerange){ .start = 0, .end = text_size(txt) };
1006 return text_write_range(txt, &r, fd);
1009 ssize_t text_write_range(Text *txt, Filerange *range, int fd) {
1010 size_t size = text_range_size(range), rem = size;
1011 for (Iterator it = text_iterator_get(txt, range->start);
1012 rem > 0 && text_iterator_valid(&it);
1013 text_iterator_next(&it)) {
1014 size_t prem = it.end - it.text;
1015 if (prem > rem)
1016 prem = rem;
1017 ssize_t written = write_all(fd, it.text, prem);
1018 if (written == -1)
1019 return -1;
1020 rem -= written;
1021 if ((size_t)written != prem)
1022 break;
1024 return size - rem;
1027 /* load the given file as starting point for further editing operations.
1028 * to start with an empty document, pass NULL as filename. */
1029 Text *text_load(const char *filename) {
1030 Text *txt = calloc(1, sizeof(Text));
1031 if (!txt)
1032 return NULL;
1033 int fd = -1;
1034 piece_init(&txt->begin, NULL, &txt->end, NULL, 0);
1035 piece_init(&txt->end, &txt->begin, NULL, NULL, 0);
1036 lineno_cache_invalidate(&txt->lines);
1037 if (filename) {
1038 if ((fd = open(filename, O_RDONLY)) == -1)
1039 goto out;
1040 if (fstat(fd, &txt->info) == -1)
1041 goto out;
1042 if (!S_ISREG(txt->info.st_mode)) {
1043 errno = S_ISDIR(txt->info.st_mode) ? EISDIR : ENOTSUP;
1044 goto out;
1046 // XXX: use lseek(fd, 0, SEEK_END); instead?
1047 size_t size = txt->info.st_size;
1048 if (size < BUFFER_MMAP_SIZE)
1049 txt->buf = buffer_read(txt, size, fd);
1050 else
1051 txt->buf = buffer_mmap(txt, size, fd, 0);
1052 if (!txt->buf)
1053 goto out;
1054 Piece *p = piece_alloc(txt);
1055 if (!p)
1056 goto out;
1057 piece_init(&txt->begin, NULL, p, NULL, 0);
1058 piece_init(p, &txt->begin, &txt->end, txt->buf->data, txt->buf->len);
1059 piece_init(&txt->end, p, NULL, NULL, 0);
1060 txt->size = txt->buf->len;
1062 /* write an empty action */
1063 change_alloc(txt, EPOS);
1064 text_snapshot(txt);
1065 txt->saved_action = txt->history;
1067 if (fd != -1)
1068 close(fd);
1069 return txt;
1070 out:
1071 if (fd != -1)
1072 close(fd);
1073 text_free(txt);
1074 return NULL;
1077 struct stat text_stat(Text *txt) {
1078 return txt->info;
1081 /* A delete operation can either start/stop midway through a piece or at
1082 * a boundry. In the former case a new piece is created to represent the
1083 * remaining text before/after the modification point.
1085 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
1086 * | | | existing| |demo | |text | | |
1087 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
1088 * ^ ^
1089 * |------ delete range -----|
1091 * /-+ --> +----+ --> +--+ --> +-\
1092 * | | | exi| |t | | |
1093 * \-+ <-- +----+ <-- +--+ <-- +-/
1095 bool text_delete(Text *txt, size_t pos, size_t len) {
1096 if (len == 0)
1097 return true;
1098 if (pos + len > txt->size)
1099 return false;
1100 if (pos < txt->lines.pos)
1101 lineno_cache_invalidate(&txt->lines);
1103 Location loc = piece_get_intern(txt, pos);
1104 Piece *p = loc.piece;
1105 if (!p)
1106 return false;
1107 size_t off = loc.off;
1108 if (cache_delete(txt, p, off, len))
1109 return true;
1110 Change *c = change_alloc(txt, pos);
1111 if (!c)
1112 return false;
1114 bool midway_start = false, midway_end = false; /* split pieces? */
1115 Piece *before, *after; /* unmodified pieces before/after deletion point */
1116 Piece *start, *end; /* span which is removed */
1117 size_t cur; /* how much has already been deleted */
1119 if (off == p->len) {
1120 /* deletion starts at a piece boundry */
1121 cur = 0;
1122 before = p;
1123 start = p->next;
1124 } else {
1125 /* deletion starts midway through a piece */
1126 midway_start = true;
1127 cur = p->len - off;
1128 start = p;
1129 before = piece_alloc(txt);
1130 if (!before)
1131 return false;
1134 /* skip all pieces which fall into deletion range */
1135 while (cur < len) {
1136 p = p->next;
1137 cur += p->len;
1140 if (cur == len) {
1141 /* deletion stops at a piece boundry */
1142 end = p;
1143 after = p->next;
1144 } else {
1145 /* cur > len: deletion stops midway through a piece */
1146 midway_end = true;
1147 end = p;
1148 after = piece_alloc(txt);
1149 if (!after)
1150 return false;
1151 piece_init(after, before, p->next, p->data + p->len - (cur - len), cur - len);
1154 if (midway_start) {
1155 /* we finally know which piece follows our newly allocated before piece */
1156 piece_init(before, start->prev, after, start->data, off);
1159 Piece *new_start = NULL, *new_end = NULL;
1160 if (midway_start) {
1161 new_start = before;
1162 if (!midway_end)
1163 new_end = before;
1165 if (midway_end) {
1166 if (!midway_start)
1167 new_start = after;
1168 new_end = after;
1171 span_init(&c->new, new_start, new_end);
1172 span_init(&c->old, start, end);
1173 span_swap(txt, &c->old, &c->new);
1174 return true;
1177 bool text_delete_range(Text *txt, Filerange *r) {
1178 if (!text_range_valid(r))
1179 return false;
1180 return text_delete(txt, r->start, text_range_size(r));
1183 /* preserve the current text content such that it can be restored by
1184 * means of undo/redo operations */
1185 void text_snapshot(Text *txt) {
1186 if (txt->current_action)
1187 txt->last_action = txt->current_action;
1188 txt->current_action = NULL;
1189 txt->cache = NULL;
1193 void text_free(Text *txt) {
1194 if (!txt)
1195 return;
1197 // free history
1198 Action *hist = txt->history;
1199 while (hist && hist->prev)
1200 hist = hist->prev;
1201 while (hist) {
1202 Action *later = hist->later;
1203 action_free(hist);
1204 hist = later;
1207 for (Piece *next, *p = txt->pieces; p; p = next) {
1208 next = p->global_next;
1209 piece_free(p);
1212 for (Buffer *next, *buf = txt->buffers; buf; buf = next) {
1213 next = buf->next;
1214 buffer_free(buf);
1217 free(txt);
1220 bool text_modified(Text *txt) {
1221 return txt->saved_action != txt->history;
1224 bool text_sigbus(Text *txt, const char *addr) {
1225 for (Buffer *buf = txt->buffers; buf; buf = buf->next) {
1226 if (buf->type == MMAP && buf->data <= addr && addr < buf->data + buf->size)
1227 return true;
1229 return false;
1232 enum TextNewLine text_newline_type(Text *txt){
1233 if (!txt->newlines) {
1234 txt->newlines = TEXT_NEWLINE_NL; /* default to UNIX style \n new lines */
1235 const char *start = txt->buf ? txt->buf->data : NULL;
1236 if (start) {
1237 const char *nl = memchr(start, '\n', txt->buf->len);
1238 if (nl > start && nl[-1] == '\r')
1239 txt->newlines = TEXT_NEWLINE_CRNL;
1240 } else {
1241 char c;
1242 size_t nl = lines_skip_forward(txt, 0, 1, NULL);
1243 if (nl > 1 && text_byte_get(txt, nl-2, &c) && c == '\r')
1244 txt->newlines = TEXT_NEWLINE_CRNL;
1248 return txt->newlines;
1251 const char *text_newline_char(Text *txt) {
1252 static const char *types[] = {
1253 [TEXT_NEWLINE_NL] = "\n",
1254 [TEXT_NEWLINE_CRNL] = "\r\n",
1256 return types[text_newline_type(txt)];
1259 static bool text_iterator_init(Iterator *it, size_t pos, Piece *p, size_t off) {
1260 *it = (Iterator){
1261 .pos = pos,
1262 .piece = p,
1263 .start = p ? p->data : NULL,
1264 .end = p ? p->data + p->len : NULL,
1265 .text = p ? p->data + off : NULL,
1267 return text_iterator_valid(it);
1270 Iterator text_iterator_get(Text *txt, size_t pos) {
1271 Iterator it;
1272 Location loc = piece_get_extern(txt, pos);
1273 text_iterator_init(&it, pos, loc.piece, loc.off);
1274 return it;
1277 bool text_iterator_byte_get(Iterator *it, char *b) {
1278 if (text_iterator_valid(it)) {
1279 if (it->start <= it->text && it->text < it->end) {
1280 *b = *it->text;
1281 return true;
1282 } else if (it->pos == it->piece->text->size) { /* EOF */
1283 *b = '\0';
1284 return true;
1287 return false;
1290 bool text_iterator_next(Iterator *it) {
1291 return text_iterator_init(it, it->pos, it->piece ? it->piece->next : NULL, 0);
1294 bool text_iterator_prev(Iterator *it) {
1295 return text_iterator_init(it, it->pos, it->piece ? it->piece->prev : NULL, 0);
1298 bool text_iterator_valid(const Iterator *it) {
1299 /* filter out sentinel nodes */
1300 return it->piece && it->piece->text;
1303 bool text_iterator_byte_next(Iterator *it, char *b) {
1304 if (!text_iterator_valid(it))
1305 return false;
1306 it->text++;
1307 /* special case for advancement to EOF */
1308 if (it->text == it->end && !it->piece->next->text) {
1309 it->pos++;
1310 if (b)
1311 *b = '\0';
1312 return true;
1314 while (it->text >= it->end) {
1315 if (!text_iterator_next(it))
1316 return false;
1317 it->text = it->start;
1319 it->pos++;
1320 if (b)
1321 *b = *it->text;
1322 return true;
1325 bool text_iterator_byte_prev(Iterator *it, char *b) {
1326 if (!text_iterator_valid(it))
1327 return false;
1328 while (it->text == it->start) {
1329 if (!text_iterator_prev(it))
1330 return false;
1331 it->text = it->end;
1333 --it->text;
1334 --it->pos;
1335 if (b)
1336 *b = *it->text;
1337 return true;
1340 bool text_iterator_codepoint_next(Iterator *it, char *c) {
1341 while (text_iterator_byte_next(it, NULL)) {
1342 if (ISUTF8(*it->text)) {
1343 if (c)
1344 *c = *it->text;
1345 return true;
1348 return false;
1351 bool text_iterator_codepoint_prev(Iterator *it, char *c) {
1352 while (text_iterator_byte_prev(it, NULL)) {
1353 if (ISUTF8(*it->text)) {
1354 if (c)
1355 *c = *it->text;
1356 return true;
1359 return false;
1362 bool text_iterator_char_next(Iterator *it, char *c) {
1363 if (!text_iterator_codepoint_next(it, c))
1364 return false;
1365 mbstate_t ps = { 0 };
1366 for (;;) {
1367 char buf[MB_CUR_MAX];
1368 size_t len = text_bytes_get(it->piece->text, it->pos, sizeof buf, buf);
1369 wchar_t wc;
1370 size_t wclen = mbrtowc(&wc, buf, len, &ps);
1371 if (wclen == (size_t)-1 && errno == EILSEQ) {
1372 return true;
1373 } else if (wclen == (size_t)-2) {
1374 return false;
1375 } else if (wclen == 0) {
1376 return true;
1377 } else {
1378 int width = wcwidth(wc);
1379 if (width != 0)
1380 return true;
1381 if (!text_iterator_codepoint_next(it, c))
1382 return false;
1385 return true;
1388 bool text_iterator_char_prev(Iterator *it, char *c) {
1389 if (!text_iterator_codepoint_prev(it, c))
1390 return false;
1391 for (;;) {
1392 char buf[MB_CUR_MAX];
1393 size_t len = text_bytes_get(it->piece->text, it->pos, sizeof buf, buf);
1394 wchar_t wc;
1395 mbstate_t ps = { 0 };
1396 size_t wclen = mbrtowc(&wc, buf, len, &ps);
1397 if (wclen == (size_t)-1 && errno == EILSEQ) {
1398 return true;
1399 } else if (wclen == (size_t)-2) {
1400 return false;
1401 } else if (wclen == 0) {
1402 return true;
1403 } else {
1404 int width = wcwidth(wc);
1405 if (width != 0)
1406 return true;
1407 if (!text_iterator_codepoint_prev(it, c))
1408 return false;
1411 return true;
1414 bool text_byte_get(Text *txt, size_t pos, char *buf) {
1415 return text_bytes_get(txt, pos, 1, buf);
1418 size_t text_bytes_get(Text *txt, size_t pos, size_t len, char *buf) {
1419 if (!buf)
1420 return 0;
1421 char *cur = buf;
1422 size_t rem = len;
1423 text_iterate(txt, it, pos) {
1424 if (rem == 0)
1425 break;
1426 size_t piece_len = it.end - it.text;
1427 if (piece_len > rem)
1428 piece_len = rem;
1429 memcpy(cur, it.text, piece_len);
1430 cur += piece_len;
1431 rem -= piece_len;
1433 return len - rem;
1436 char *text_bytes_alloc0(Text *txt, size_t pos, size_t len) {
1437 if (len == SIZE_MAX)
1438 return NULL;
1439 char *buf = malloc(len+1);
1440 if (!buf)
1441 return NULL;
1442 len = text_bytes_get(txt, pos, len, buf);
1443 buf[len] = '\0';
1444 return buf;
1447 size_t text_size(Text *txt) {
1448 return txt->size;
1451 /* count the number of new lines '\n' in range [pos, pos+len) */
1452 static size_t lines_count(Text *txt, size_t pos, size_t len) {
1453 size_t lines = 0;
1454 text_iterate(txt, it, pos) {
1455 const char *start = it.text;
1456 while (len > 0 && start < it.end) {
1457 size_t n = MIN(len, (size_t)(it.end - start));
1458 const char *end = memchr(start, '\n', n);
1459 if (!end) {
1460 len -= n;
1461 break;
1463 lines++;
1464 len -= end - start + 1;
1465 start = end + 1;
1468 if (len == 0)
1469 break;
1471 return lines;
1474 /* skip n lines forward and return position afterwards */
1475 static size_t lines_skip_forward(Text *txt, size_t pos, size_t lines, size_t *lines_skipped) {
1476 size_t lines_old = lines;
1477 text_iterate(txt, it, pos) {
1478 const char *start = it.text;
1479 while (lines > 0 && start < it.end) {
1480 size_t n = it.end - start;
1481 const char *end = memchr(start, '\n', n);
1482 if (!end) {
1483 pos += n;
1484 break;
1486 pos += end - start + 1;
1487 start = end + 1;
1488 lines--;
1491 if (lines == 0)
1492 break;
1494 if (lines_skipped)
1495 *lines_skipped = lines_old - lines;
1496 return pos;
1499 static void lineno_cache_invalidate(LineCache *cache) {
1500 cache->pos = 0;
1501 cache->lineno = 1;
1504 size_t text_pos_by_lineno(Text *txt, size_t lineno) {
1505 size_t lines_skipped;
1506 LineCache *cache = &txt->lines;
1507 if (lineno <= 1)
1508 return 0;
1509 if (lineno > cache->lineno) {
1510 cache->pos = lines_skip_forward(txt, cache->pos, lineno - cache->lineno, &lines_skipped);
1511 cache->lineno += lines_skipped;
1512 } else if (lineno < cache->lineno) {
1513 #if 0
1514 // TODO does it make sense to scan memory backwards here?
1515 size_t diff = cache->lineno - lineno;
1516 if (diff < lineno) {
1517 lines_skip_backward(txt, cache->pos, diff);
1518 } else
1519 #endif
1520 cache->pos = lines_skip_forward(txt, 0, lineno - 1, &lines_skipped);
1521 cache->lineno = lines_skipped + 1;
1523 return cache->lineno == lineno ? cache->pos : EPOS;
1526 size_t text_lineno_by_pos(Text *txt, size_t pos) {
1527 LineCache *cache = &txt->lines;
1528 if (pos > txt->size)
1529 pos = txt->size;
1530 if (pos < cache->pos) {
1531 size_t diff = cache->pos - pos;
1532 if (diff < pos)
1533 cache->lineno -= lines_count(txt, pos, diff);
1534 else
1535 cache->lineno = lines_count(txt, 0, pos) + 1;
1536 } else if (pos > cache->pos) {
1537 cache->lineno += lines_count(txt, cache->pos, pos - cache->pos);
1539 cache->pos = pos;
1540 return cache->lineno;
1543 Mark text_mark_set(Text *txt, size_t pos) {
1544 if (pos == 0)
1545 return (Mark)&txt->begin;
1546 if (pos == txt->size)
1547 return (Mark)&txt->end;
1548 Location loc = piece_get_extern(txt, pos);
1549 if (!loc.piece)
1550 return NULL;
1551 return loc.piece->data + loc.off;
1554 size_t text_mark_get(Text *txt, Mark mark) {
1555 size_t cur = 0;
1557 if (!mark)
1558 return EPOS;
1559 if (mark == (Mark)&txt->begin)
1560 return 0;
1561 if (mark == (Mark)&txt->end)
1562 return txt->size;
1564 for (Piece *p = txt->begin.next; p->next; p = p->next) {
1565 if (p->data <= mark && mark < p->data + p->len)
1566 return cur + (mark - p->data);
1567 cur += p->len;
1570 return EPOS;
1573 size_t text_history_get(Text *txt, size_t index) {
1574 for (Action *a = txt->current_action ? txt->current_action : txt->history; a; a = a->prev) {
1575 if (index-- == 0) {
1576 Change *c = a->change;
1577 while (c && c->next)
1578 c = c->next;
1579 return c ? c->pos : EPOS;
1582 return EPOS;