configure: fix condition for libacl
[vis.git] / text.c
blob44c16cd8772bda7c09d6d92215477ba40c899f96
1 /*
2 * Copyright (c) 2014-2015 Marc André Tanner <mat at brain-dump.org>
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #include <unistd.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <time.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <wchar.h>
24 #include <stdint.h>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/mman.h>
28 #if CONFIG_ACL
29 #include <sys/acl.h>
30 #endif
31 #if CONFIG_SELINUX
32 #include <selinux/selinux.h>
33 #endif
35 #include "text.h"
36 #include "text-util.h"
37 #include "util.h"
39 /* Allocate buffers holding the actual file content in junks of size: */
40 #define BUFFER_SIZE (1 << 20)
41 /* Files smaller than this value are copied on load, larger ones are mmap(2)-ed
42 * directely. Hence the former can be truncated, while doing so on the latter
43 * results in havoc. */
44 #define BUFFER_MMAP_SIZE (1 << 23)
46 /* Buffer holding the file content, either readonly mmap(2)-ed from the original
47 * file or heap allocated to store the modifications.
49 typedef struct Buffer Buffer;
50 struct Buffer {
51 size_t size; /* maximal capacity */
52 size_t len; /* current used length / insertion position */
53 char *data; /* actual data */
54 enum { MMAP, MALLOC} type; /* type of allocation */
55 Buffer *next; /* next junk */
58 /* A piece holds a reference (but doesn't itself store) a certain amount of data.
59 * All active pieces chained together form the whole content of the document.
60 * At the beginning there exists only one piece, spanning the whole document.
61 * Upon insertion/deletion new pieces will be created to represent the changes.
62 * Generally pieces are never destroyed, but kept around to peform undo/redo
63 * operations.
65 struct Piece {
66 Text *text; /* text to which this piece belongs */
67 Piece *prev, *next; /* pointers to the logical predecessor/successor */
68 Piece *global_prev; /* double linked list in order of allocation, */
69 Piece *global_next; /* used to free individual pieces */
70 const char *data; /* pointer into a Buffer holding the data */
71 size_t len; /* the length in number of bytes of the data */
74 /* used to transform a global position (byte offset starting from the beginning
75 * of the text) into an offset relative to a piece.
77 typedef struct {
78 Piece *piece; /* piece holding the location */
79 size_t off; /* offset into the piece in bytes */
80 } Location;
82 /* A Span holds a certain range of pieces. Changes to the document are always
83 * performed by swapping out an existing span with a new one.
85 typedef struct {
86 Piece *start, *end; /* start/end of the span */
87 size_t len; /* the sum of the lengths of the pieces which form this span */
88 } Span;
90 /* A Change keeps all needed information to redo/undo an insertion/deletion. */
91 typedef struct Change Change;
92 struct Change {
93 Span old; /* all pieces which are being modified/swapped out by the change */
94 Span new; /* all pieces which are introduced/swapped in by the change */
95 size_t pos; /* absolute position at which the change occured */
96 Change *next; /* next change which is part of the same action */
97 Change *prev; /* previous change which is part of the same action */
100 /* An Action is a list of Changes which are used to undo/redo all modifications
101 * since the last snapshot operation. Actions are stored in a directed graph structure.
103 typedef struct Action Action;
104 struct Action {
105 Change *change; /* the most recent change */
106 Action *next; /* the next (child) action in the undo tree */
107 Action *prev; /* the previous (parent) operation in the undo tree */
108 Action *earlier; /* the previous Action, chronologically */
109 Action *later; /* the next Action, chronologically */
110 time_t time; /* when the first change of this action was performed */
111 size_t seq; /* a unique, strictly increasing identifier */
114 typedef struct {
115 size_t pos; /* position in bytes from start of file */
116 size_t lineno; /* line number in file i.e. number of '\n' in [0, pos) */
117 } LineCache;
119 /* The main struct holding all information of a given file */
120 struct Text {
121 Buffer *buf; /* original file content at the time of load operation */
122 Buffer *buffers; /* all buffers which have been allocated to hold insertion data */
123 Piece *pieces; /* all pieces which have been allocated, used to free them */
124 Piece *cache; /* most recently modified piece */
125 Piece begin, end; /* sentinel nodes which always exists but don't hold any data */
126 Action *history; /* undo tree */
127 Action *current_action; /* action holding all file changes until a snapshot is performed */
128 Action *last_action; /* the last action added to the tree, chronologically */
129 Action *saved_action; /* the last action at the time of the save operation */
130 size_t size; /* current file content size in bytes */
131 struct stat info; /* stat as probed at load time */
132 LineCache lines; /* mapping between absolute pos in bytes and logical line breaks */
133 enum TextNewLine newlines; /* which type of new lines does the file use */
136 /* buffer management */
137 static Buffer *buffer_alloc(Text *txt, size_t size);
138 static Buffer *buffer_read(Text *txt, size_t size, int fd);
139 static Buffer *buffer_mmap(Text *txt, size_t size, int fd, off_t offset);
140 static void buffer_free(Buffer *buf);
141 static bool buffer_capacity(Buffer *buf, size_t len);
142 static const char *buffer_append(Buffer *buf, const char *data, size_t len);
143 static bool buffer_insert(Buffer *buf, size_t pos, const char *data, size_t len);
144 static bool buffer_delete(Buffer *buf, size_t pos, size_t len);
145 static const char *buffer_store(Text *txt, const char *data, size_t len);
146 /* cache layer */
147 static void cache_piece(Text *txt, Piece *p);
148 static bool cache_contains(Text *txt, Piece *p);
149 static bool cache_insert(Text *txt, Piece *p, size_t off, const char *data, size_t len);
150 static bool cache_delete(Text *txt, Piece *p, size_t off, size_t len);
151 /* piece management */
152 static Piece *piece_alloc(Text *txt);
153 static void piece_free(Piece *p);
154 static void piece_init(Piece *p, Piece *prev, Piece *next, const char *data, size_t len);
155 static Location piece_get_intern(Text *txt, size_t pos);
156 static Location piece_get_extern(Text *txt, size_t pos);
157 /* span management */
158 static void span_init(Span *span, Piece *start, Piece *end);
159 static void span_swap(Text *txt, Span *old, Span *new);
160 /* change management */
161 static Change *change_alloc(Text *txt, size_t pos);
162 static void change_free(Change *c);
163 /* action management */
164 static Action *action_alloc(Text *txt);
165 static void action_free(Action *a);
166 /* logical line counting cache */
167 static void lineno_cache_invalidate(LineCache *cache);
168 static size_t lines_skip_forward(Text *txt, size_t pos, size_t lines, size_t *lines_skiped);
169 static size_t lines_count(Text *txt, size_t pos, size_t len);
171 static ssize_t write_all(int fd, const char *buf, size_t count) {
172 size_t rem = count;
173 while (rem > 0) {
174 ssize_t written = write(fd, buf, rem);
175 if (written < 0) {
176 if (errno == EAGAIN || errno == EINTR)
177 continue;
178 return -1;
179 } else if (written == 0) {
180 break;
182 rem -= written;
183 buf += written;
185 return count - rem;
188 /* allocate a new buffer of MAX(size, BUFFER_SIZE) bytes */
189 static Buffer *buffer_alloc(Text *txt, size_t size) {
190 Buffer *buf = calloc(1, sizeof(Buffer));
191 if (!buf)
192 return NULL;
193 if (BUFFER_SIZE > size)
194 size = BUFFER_SIZE;
195 if (!(buf->data = malloc(size))) {
196 free(buf);
197 return NULL;
199 buf->type = MALLOC;
200 buf->size = size;
201 buf->next = txt->buffers;
202 txt->buffers = buf;
203 return buf;
206 static Buffer *buffer_read(Text *txt, size_t size, int fd) {
207 Buffer *buf = buffer_alloc(txt, size);
208 if (!buf)
209 return NULL;
210 while (size > 0) {
211 char data[4096];
212 ssize_t len = read(fd, data, MIN(sizeof(data), size));
213 if (len == -1) {
214 txt->buffers = buf->next;
215 buffer_free(buf);
216 return NULL;
217 } else if (len == 0) {
218 break;
219 } else {
220 buffer_append(buf, data, len);
221 size -= len;
224 return buf;
227 static Buffer *buffer_mmap(Text *txt, size_t size, int fd, off_t offset) {
228 Buffer *buf = calloc(1, sizeof(Buffer));
229 if (!buf)
230 return NULL;
231 if (size) {
232 buf->data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset);
233 if (buf->data == MAP_FAILED) {
234 free(buf);
235 return NULL;
238 buf->type = MMAP;
239 buf->size = size;
240 buf->len = size;
241 buf->next = txt->buffers;
242 txt->buffers = buf;
243 return buf;
246 static void buffer_free(Buffer *buf) {
247 if (!buf)
248 return;
249 if (buf->type == MALLOC)
250 free(buf->data);
251 else if (buf->type == MMAP && buf->data)
252 munmap(buf->data, buf->size);
253 free(buf);
256 /* check whether buffer has enough free space to store len bytes */
257 static bool buffer_capacity(Buffer *buf, size_t len) {
258 return buf->size - buf->len >= len;
261 /* append data to buffer, assumes there is enough space available */
262 static const char *buffer_append(Buffer *buf, const char *data, size_t len) {
263 char *dest = memcpy(buf->data + buf->len, data, len);
264 buf->len += len;
265 return dest;
268 /* stores the given data in a buffer, allocates a new one if necessary. returns
269 * a pointer to the storage location or NULL if allocation failed. */
270 static const char *buffer_store(Text *txt, const char *data, size_t len) {
271 Buffer *buf = txt->buffers;
272 if ((!buf || !buffer_capacity(buf, len)) && !(buf = buffer_alloc(txt, len)))
273 return NULL;
274 return buffer_append(buf, data, len);
277 /* insert data into buffer at an arbitrary position, this should only be used with
278 * data of the most recently created piece. */
279 static bool buffer_insert(Buffer *buf, size_t pos, const char *data, size_t len) {
280 if (pos > buf->len || !buffer_capacity(buf, len))
281 return false;
282 if (buf->len == pos)
283 return buffer_append(buf, data, len);
284 char *insert = buf->data + pos;
285 memmove(insert + len, insert, buf->len - pos);
286 memcpy(insert, data, len);
287 buf->len += len;
288 return true;
291 /* delete data from a buffer at an arbitrary position, this should only be used with
292 * data of the most recently created piece. */
293 static bool buffer_delete(Buffer *buf, size_t pos, size_t len) {
294 if (pos + len > buf->len)
295 return false;
296 if (buf->len == pos) {
297 buf->len -= len;
298 return true;
300 char *delete = buf->data + pos;
301 memmove(delete, delete + len, buf->len - pos - len);
302 buf->len -= len;
303 return true;
306 /* cache the given piece if it is the most recently changed one */
307 static void cache_piece(Text *txt, Piece *p) {
308 Buffer *buf = txt->buffers;
309 if (!buf || p->data < buf->data || p->data + p->len != buf->data + buf->len)
310 return;
311 txt->cache = p;
314 /* check whether the given piece was the most recently modified one */
315 static bool cache_contains(Text *txt, Piece *p) {
316 Buffer *buf = txt->buffers;
317 Action *a = txt->current_action;
318 if (!buf || !txt->cache || txt->cache != p || !a || !a->change)
319 return false;
321 Piece *start = a->change->new.start;
322 Piece *end = a->change->new.end;
323 bool found = false;
324 for (Piece *cur = start; !found; cur = cur->next) {
325 if (cur == p)
326 found = true;
327 if (cur == end)
328 break;
331 return found && p->data + p->len == buf->data + buf->len;
334 /* try to insert a junk of data at a given piece offset. the insertion is only
335 * performed if the piece is the most recenetly changed one. the legnth of the
336 * piece, the span containing it and the whole text is adjusted accordingly */
337 static bool cache_insert(Text *txt, Piece *p, size_t off, const char *data, size_t len) {
338 if (!cache_contains(txt, p))
339 return false;
340 Buffer *buf = txt->buffers;
341 size_t bufpos = p->data + off - buf->data;
342 if (!buffer_insert(buf, bufpos, data, len))
343 return false;
344 p->len += len;
345 txt->current_action->change->new.len += len;
346 txt->size += len;
347 return true;
350 /* try to delete a junk of data at a given piece offset. the deletion is only
351 * performed if the piece is the most recenetly changed one and the whole
352 * affected range lies within it. the legnth of the piece, the span containing it
353 * and the whole text is adjusted accordingly */
354 static bool cache_delete(Text *txt, Piece *p, size_t off, size_t len) {
355 if (!cache_contains(txt, p))
356 return false;
357 Buffer *buf = txt->buffers;
358 size_t bufpos = p->data + off - buf->data;
359 if (off + len > p->len || !buffer_delete(buf, bufpos, len))
360 return false;
361 p->len -= len;
362 txt->current_action->change->new.len -= len;
363 txt->size -= len;
364 return true;
367 /* initialize a span and calculate its length */
368 static void span_init(Span *span, Piece *start, Piece *end) {
369 size_t len = 0;
370 span->start = start;
371 span->end = end;
372 for (Piece *p = start; p; p = p->next) {
373 len += p->len;
374 if (p == end)
375 break;
377 span->len = len;
380 /* swap out an old span and replace it with a new one.
382 * - if old is an empty span do not remove anything, just insert the new one
383 * - if new is an empty span do not insert anything, just remove the old one
385 * adjusts the document size accordingly.
387 static void span_swap(Text *txt, Span *old, Span *new) {
388 if (old->len == 0 && new->len == 0) {
389 return;
390 } else if (old->len == 0) {
391 /* insert new span */
392 new->start->prev->next = new->start;
393 new->end->next->prev = new->end;
394 } else if (new->len == 0) {
395 /* delete old span */
396 old->start->prev->next = old->end->next;
397 old->end->next->prev = old->start->prev;
398 } else {
399 /* replace old with new */
400 old->start->prev->next = new->start;
401 old->end->next->prev = new->end;
403 txt->size -= old->len;
404 txt->size += new->len;
407 /* allocate a new action, set its pointers to the other actions in the history,
408 * and set it as txt->history. All further changes will be associated with this action. */
409 static Action *action_alloc(Text *txt) {
410 Action *new = calloc(1, sizeof(Action));
411 if (!new)
412 return NULL;
413 new->time = time(NULL);
414 txt->current_action = new;
416 /* set sequence number */
417 if (!txt->last_action)
418 new->seq = 0;
419 else
420 new->seq = txt->last_action->seq + 1;
422 /* set earlier, later pointers */
423 if (txt->last_action)
424 txt->last_action->later = new;
425 new->earlier = txt->last_action;
427 if (!txt->history) {
428 txt->history = new;
429 return new;
432 /* set prev, next pointers */
433 new->prev = txt->history;
434 txt->history->next = new;
435 txt->history = new;
436 return new;
439 static void action_free(Action *a) {
440 if (!a)
441 return;
442 for (Change *next, *c = a->change; c; c = next) {
443 next = c->next;
444 change_free(c);
446 free(a);
449 static Piece *piece_alloc(Text *txt) {
450 Piece *p = calloc(1, sizeof(Piece));
451 if (!p)
452 return NULL;
453 p->text = txt;
454 p->global_next = txt->pieces;
455 if (txt->pieces)
456 txt->pieces->global_prev = p;
457 txt->pieces = p;
458 return p;
461 static void piece_free(Piece *p) {
462 if (!p)
463 return;
464 if (p->global_prev)
465 p->global_prev->global_next = p->global_next;
466 if (p->global_next)
467 p->global_next->global_prev = p->global_prev;
468 if (p->text->pieces == p)
469 p->text->pieces = p->global_next;
470 if (p->text->cache == p)
471 p->text->cache = NULL;
472 free(p);
475 static void piece_init(Piece *p, Piece *prev, Piece *next, const char *data, size_t len) {
476 p->prev = prev;
477 p->next = next;
478 p->data = data;
479 p->len = len;
482 /* returns the piece holding the text at byte offset pos. if pos happens to
483 * be at a piece boundry i.e. the first byte of a piece then the previous piece
484 * to the left is returned with an offset of piece->len. this is convenient for
485 * modifications to the piece chain where both pieces (the returned one and the
486 * one following it) are needed, but unsuitable as a public interface.
488 * in particular if pos is zero, the begin sentinel piece is returned.
490 static Location piece_get_intern(Text *txt, size_t pos) {
491 size_t cur = 0;
492 for (Piece *p = &txt->begin; p->next; p = p->next) {
493 if (cur <= pos && pos <= cur + p->len)
494 return (Location){ .piece = p, .off = pos - cur };
495 cur += p->len;
498 return (Location){ 0 };
501 /* similiar to piece_get_intern but usable as a public API. returns the piece
502 * holding the text at byte offset pos. never returns a sentinel piece.
503 * it pos is the end of file (== text_size()) and the file is not empty then
504 * the last piece holding data is returned.
506 static Location piece_get_extern(Text *txt, size_t pos) {
507 size_t cur = 0;
509 if (pos > 0 && pos == txt->size) {
510 Piece *p = txt->begin.next;
511 while (p->next->next)
512 p = p->next;
513 return (Location){ .piece = p, .off = p->len };
516 for (Piece *p = txt->begin.next; p->next; p = p->next) {
517 if (cur <= pos && pos < cur + p->len)
518 return (Location){ .piece = p, .off = pos - cur };
519 cur += p->len;
522 return (Location){ 0 };
525 /* allocate a new change, associate it with current action or a newly
526 * allocated one if none exists. */
527 static Change *change_alloc(Text *txt, size_t pos) {
528 Action *a = txt->current_action;
529 if (!a) {
530 a = action_alloc(txt);
531 if (!a)
532 return NULL;
534 Change *c = calloc(1, sizeof(Change));
535 if (!c)
536 return NULL;
537 c->pos = pos;
538 c->next = a->change;
539 if (a->change)
540 a->change->prev = c;
541 a->change = c;
542 return c;
545 static void change_free(Change *c) {
546 if (!c)
547 return;
548 /* only free the new part of the span, the old one is still in use */
549 piece_free(c->new.start);
550 if (c->new.start != c->new.end)
551 piece_free(c->new.end);
552 free(c);
555 /* When inserting new data there are 2 cases to consider.
557 * - in the first the insertion point falls into the middle of an exisiting
558 * piece which is replaced by three new pieces:
560 * /-+ --> +---------------+ --> +-\
561 * | | | existing text | | |
562 * \-+ <-- +---------------+ <-- +-/
564 * Insertion point for "demo "
566 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
567 * | | | existing| |demo | |text | | |
568 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
570 * - the second case deals with an insertion point at a piece boundry:
572 * /-+ --> +---------------+ --> +-\
573 * | | | existing text | | |
574 * \-+ <-- +---------------+ <-- +-/
576 * Insertion point for "short"
578 * /-+ --> +-----+ --> +---------------+ --> +-\
579 * | | |short| | existing text | | |
580 * \-+ <-- +-----+ <-- +---------------+ <-- +-/
582 bool text_insert(Text *txt, size_t pos, const char *data, size_t len) {
583 if (len == 0)
584 return true;
585 if (pos > txt->size)
586 return false;
587 if (pos < txt->lines.pos)
588 lineno_cache_invalidate(&txt->lines);
590 Location loc = piece_get_intern(txt, pos);
591 Piece *p = loc.piece;
592 if (!p)
593 return false;
594 size_t off = loc.off;
595 if (cache_insert(txt, p, off, data, len))
596 return true;
598 Change *c = change_alloc(txt, pos);
599 if (!c)
600 return false;
602 if (!(data = buffer_store(txt, data, len)))
603 return false;
605 Piece *new = NULL;
607 if (off == p->len) {
608 /* insert between two existing pieces, hence there is nothing to
609 * remove, just add a new piece holding the extra text */
610 if (!(new = piece_alloc(txt)))
611 return false;
612 piece_init(new, p, p->next, data, len);
613 span_init(&c->new, new, new);
614 span_init(&c->old, NULL, NULL);
615 } else {
616 /* insert into middle of an existing piece, therfore split the old
617 * piece. that is we have 3 new pieces one containing the content
618 * before the insertion point then one holding the newly inserted
619 * text and one holding the content after the insertion point.
621 Piece *before = piece_alloc(txt);
622 new = piece_alloc(txt);
623 Piece *after = piece_alloc(txt);
624 if (!before || !new || !after)
625 return false;
626 piece_init(before, p->prev, new, p->data, off);
627 piece_init(new, before, after, data, len);
628 piece_init(after, new, p->next, p->data + off, p->len - off);
630 span_init(&c->new, before, after);
631 span_init(&c->old, p, p);
634 cache_piece(txt, new);
635 span_swap(txt, &c->old, &c->new);
636 return true;
639 bool text_appendf(Text *txt, const char *format, ...) {
640 va_list ap;
641 va_start(ap, format);
642 bool ret = text_vprintf(txt, text_size(txt), format, ap);
643 va_end(ap);
644 return ret;
647 bool text_printf(Text *txt, size_t pos, const char *format, ...) {
648 va_list ap;
649 va_start(ap, format);
650 bool ret = text_vprintf(txt, pos, format, ap);
651 va_end(ap);
652 return ret;
655 bool text_vprintf(Text *txt, size_t pos, const char *format, va_list ap) {
656 va_list ap_save;
657 va_copy(ap_save, ap);
658 int len = vsnprintf(NULL, 0, format, ap);
659 if (len == -1)
660 return false;
661 char *buf = malloc(len+1);
662 bool ret = buf && (vsnprintf(buf, len+1, format, ap_save) == len) && text_insert(txt, pos, buf, len);
663 free(buf);
664 va_end(ap_save);
665 return ret;
668 size_t text_insert_newline(Text *txt, size_t pos) {
669 const char *data = text_newline_char(txt);
670 size_t len = strlen(data);
671 return text_insert(txt, pos, data, len) ? len : 0;
674 static size_t action_undo(Text *txt, Action *a) {
675 size_t pos = EPOS;
676 for (Change *c = a->change; c; c = c->next) {
677 span_swap(txt, &c->new, &c->old);
678 pos = c->pos;
680 return pos;
683 static size_t action_redo(Text *txt, Action *a) {
684 size_t pos = EPOS;
685 Change *c = a->change;
686 while (c->next)
687 c = c->next;
688 for ( ; c; c = c->prev) {
689 span_swap(txt, &c->old, &c->new);
690 pos = c->pos;
691 if (c->new.len > c->old.len)
692 pos += c->new.len - c->old.len;
694 return pos;
697 size_t text_undo(Text *txt) {
698 size_t pos = EPOS;
699 /* taking a snapshot makes sure that txt->current_action is reset */
700 text_snapshot(txt);
701 Action *a = txt->history->prev;
702 if (!a)
703 return pos;
704 pos = action_undo(txt, txt->history);
705 txt->history = a;
706 lineno_cache_invalidate(&txt->lines);
707 return pos;
710 size_t text_redo(Text *txt) {
711 size_t pos = EPOS;
712 /* taking a snapshot makes sure that txt->current_action is reset */
713 text_snapshot(txt);
714 Action *a = txt->history->next;
715 if (!a)
716 return pos;
717 pos = action_redo(txt, a);
718 txt->history = a;
719 lineno_cache_invalidate(&txt->lines);
720 return pos;
723 static bool history_change_branch(Action *a) {
724 bool changed = false;
725 while (a->prev) {
726 if (a->prev->next != a) {
727 a->prev->next = a;
728 changed = true;
730 a = a->prev;
732 return changed;
735 static size_t history_traverse_to(Text *txt, Action *a) {
736 size_t pos = EPOS;
737 if (!a)
738 return pos;
739 bool changed = history_change_branch(a);
740 if (!changed) {
741 if (a->seq == txt->history->seq) {
742 return txt->lines.pos;
743 } else if (a->seq > txt->history->seq) {
744 while (txt->history != a)
745 pos = text_redo(txt);
746 return pos;
747 } else if (a->seq < txt->history->seq) {
748 while (txt->history != a)
749 pos = text_undo(txt);
750 return pos;
752 } else {
753 while (txt->history->prev && txt->history->prev->next == txt->history)
754 text_undo(txt);
755 pos = text_undo(txt);
756 while (txt->history != a)
757 pos = text_redo(txt);
758 return pos;
760 return pos;
763 size_t text_earlier(Text *txt, int count) {
764 Action *a = txt->history;
765 while (count-- > 0 && a->earlier)
766 a = a->earlier;
767 return history_traverse_to(txt, a);
770 size_t text_later(Text *txt, int count) {
771 Action *a = txt->history;
772 while (count-- > 0 && a->later)
773 a = a->later;
774 return history_traverse_to(txt, a);
777 size_t text_restore(Text *txt, time_t time) {
778 Action *a = txt->history;
779 while (time < a->time && a->earlier)
780 a = a->earlier;
781 while (time > a->time && a->later)
782 a = a->later;
783 time_t diff = labs(a->time - time);
784 if (a->earlier && a->earlier != txt->history && labs(a->earlier->time - time) < diff)
785 a = a->earlier;
786 if (a->later && a->later != txt->history && labs(a->later->time - time) < diff)
787 a = a->later;
788 return history_traverse_to(txt, a);
791 time_t text_state(Text *txt) {
792 return txt->history->time;
795 static bool preserve_acl(int src, int dest) {
796 #if CONFIG_ACL
797 acl_t acl = acl_get_fd(src);
798 if (!acl)
799 return errno == ENOTSUP ? true : false;
800 if (acl_set_fd(dest, acl) == -1) {
801 acl_free(acl);
802 return false;
804 acl_free(acl);
805 #endif /* CONFIG_ACL */
806 return true;
809 static bool preserve_selinux_context(int src, int dest) {
810 #if CONFIG_SELINUX
811 char *context = NULL;
812 if (!is_selinux_enabled())
813 return true;
814 if (fgetfilecon(src, &context) == -1)
815 return errno == ENOTSUP ? true : false;
816 if (fsetfilecon(dest, context) == -1) {
817 freecon(context);
818 return false;
820 freecon(context);
821 #endif /* CONFIG_SELINUX */
822 return true;
825 /* Save current content to given filename. The data is first saved to `filename~`
826 * and then atomically moved to its final (possibly alredy existing) destination
827 * using rename(2). This approach does not work if:
829 * - the file is a symbolic link
830 * - the file is a hard link
831 * - file ownership can not be preserved
832 * - file group can not be preserved
833 * - directory permissions do not allow creation of a new file
834 * - POSXI ACL can not be preserved (if enabled)
835 * - SELinux security context can not be preserved (if enabled)
837 static bool text_save_atomic_range(Text *txt, Filerange *range, const char *filename) {
838 struct stat meta = { 0 }, oldmeta = { 0 };
839 int fd = -1, oldfd = -1, saved_errno;
840 char *tmpname = NULL;
841 size_t size = text_range_size(range);
842 size_t namelen = strlen(filename) + 1 /* ~ */ + 1 /* \0 */;
844 if ((oldfd = open(filename, O_RDONLY)) == -1 && errno != ENOENT)
845 goto err;
846 if (oldfd != -1 && lstat(filename, &oldmeta) == -1)
847 goto err;
848 if (oldfd != -1) {
849 if (S_ISLNK(oldmeta.st_mode)) /* symbolic link */
850 goto err;
851 if (oldmeta.st_nlink > 1) /* hard link */
852 goto err;
854 if (!(tmpname = calloc(1, namelen)))
855 goto err;
856 snprintf(tmpname, namelen, "%s~", filename);
858 /* O_RDWR is needed because otherwise we can't map with MAP_SHARED */
859 if ((fd = open(tmpname, O_CREAT|O_RDWR|O_TRUNC, oldfd == -1 ? S_IRUSR|S_IWUSR : oldmeta.st_mode)) == -1)
860 goto err;
861 if (ftruncate(fd, size) == -1)
862 goto err;
863 if (oldfd != -1) {
864 if (!preserve_acl(oldfd, fd) || !preserve_selinux_context(oldfd, fd))
865 goto err;
866 /* change owner if necessary */
867 if (oldmeta.st_uid != getuid() && fchown(fd, oldmeta.st_uid, (uid_t)-1) == -1)
868 goto err;
869 /* change group if necessary, in case of failure some editors reset
870 * the group permissions to the same as for others */
871 if (oldmeta.st_gid != getgid() && fchown(fd, (uid_t)-1, oldmeta.st_gid) == -1)
872 goto err;
875 if (size > 0) {
876 void *buf = mmap(NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
877 if (buf == MAP_FAILED)
878 goto err;
880 char *cur = buf;
881 size_t rem = size;
883 for (Iterator it = text_iterator_get(txt, range->start);
884 rem > 0 && text_iterator_valid(&it);
885 text_iterator_next(&it)) {
886 size_t len = it.end - it.text;
887 if (len > rem)
888 len = rem;
889 memcpy(cur, it.text, len);
890 cur += len;
891 rem -= len;
894 if (munmap(buf, size) == -1)
895 goto err;
898 if (oldfd != -1) {
899 close(oldfd);
900 oldfd = -1;
903 if (fsync(fd) == -1)
904 goto err;
906 if (fstat(fd, &meta) == -1)
907 goto err;
909 if (close(fd) == -1) {
910 fd = -1;
911 goto err;
913 fd = -1;
915 if (rename(tmpname, filename) == -1)
916 goto err;
918 if (meta.st_mtime)
919 txt->info = meta;
920 free(tmpname);
921 return true;
922 err:
923 saved_errno = errno;
924 if (oldfd != -1)
925 close(oldfd);
926 if (fd != -1)
927 close(fd);
928 if (tmpname && *tmpname)
929 unlink(tmpname);
930 free(tmpname);
931 errno = saved_errno;
932 return false;
935 bool text_save(Text *txt, const char *filename) {
936 Filerange r = (Filerange){ .start = 0, .end = text_size(txt) };
937 return text_save_range(txt, &r, filename);
940 /* First try to save the file atomically using rename(2) if this does not
941 * work overwrite the file in place. However if something goes wrong during
942 * this overwrite the original file is permanently damaged.
944 bool text_save_range(Text *txt, Filerange *range, const char *filename) {
945 struct stat meta;
946 int fd = -1, newfd = -1;
947 errno = 0;
948 if (!filename || text_save_atomic_range(txt, range, filename))
949 goto ok;
950 if (errno == ENOSPC)
951 goto err;
952 if ((fd = open(filename, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR)) == -1)
953 goto err;
954 if (fstat(fd, &meta) == -1)
955 goto err;
956 if (meta.st_dev == txt->info.st_dev && meta.st_ino == txt->info.st_ino &&
957 txt->buf && txt->buf->type == MMAP && txt->buf->size) {
958 /* The file we are going to overwrite is currently mmap-ed from
959 * text_load, therefore we copy the mmap-ed buffer to a temporary
960 * file and remap it at the same position such that all pointers
961 * from the various pieces are still valid.
963 size_t size = txt->buf->size;
964 char tmpname[32] = "/tmp/vis-XXXXXX";
965 mode_t mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
966 newfd = mkstemp(tmpname);
967 umask(mask);
968 if (newfd == -1)
969 goto err;
970 if (unlink(tmpname) == -1)
971 goto err;
972 ssize_t written = write_all(newfd, txt->buf->data, size);
973 if (written == -1 || (size_t)written != size)
974 goto err;
975 if (munmap(txt->buf->data, size) == -1)
976 goto err;
978 void *data = mmap(txt->buf->data, size, PROT_READ, MAP_SHARED, newfd, 0);
979 if (data == MAP_FAILED)
980 goto err;
981 if (data != txt->buf->data) {
982 munmap(data, size);
983 goto err;
985 if (close(newfd) == -1) {
986 newfd = -1;
987 goto err;
989 txt->buf->data = data;
990 newfd = -1;
992 /* overwrite the exisiting file content, if somehting goes wrong
993 * here we are screwed, TODO: make a backup before? */
994 if (ftruncate(fd, 0) == -1)
995 goto err;
996 ssize_t written = text_write_range(txt, range, fd);
997 if (written == -1 || (size_t)written != text_range_size(range))
998 goto err;
1000 if (fsync(fd) == -1)
1001 goto err;
1002 if (fstat(fd, &meta) == -1)
1003 goto err;
1004 if (close(fd) == -1)
1005 return false;
1006 txt->info = meta;
1008 txt->saved_action = txt->history;
1009 text_snapshot(txt);
1010 return true;
1011 err:
1012 if (fd != -1)
1013 close(fd);
1014 if (newfd != -1)
1015 close(newfd);
1016 return false;
1019 ssize_t text_write(Text *txt, int fd) {
1020 Filerange r = (Filerange){ .start = 0, .end = text_size(txt) };
1021 return text_write_range(txt, &r, fd);
1024 ssize_t text_write_range(Text *txt, Filerange *range, int fd) {
1025 size_t size = text_range_size(range), rem = size;
1026 for (Iterator it = text_iterator_get(txt, range->start);
1027 rem > 0 && text_iterator_valid(&it);
1028 text_iterator_next(&it)) {
1029 size_t prem = it.end - it.text;
1030 if (prem > rem)
1031 prem = rem;
1032 ssize_t written = write_all(fd, it.text, prem);
1033 if (written == -1)
1034 return -1;
1035 rem -= written;
1036 if ((size_t)written != prem)
1037 break;
1039 return size - rem;
1042 /* load the given file as starting point for further editing operations.
1043 * to start with an empty document, pass NULL as filename. */
1044 Text *text_load(const char *filename) {
1045 Text *txt = calloc(1, sizeof(Text));
1046 if (!txt)
1047 return NULL;
1048 int fd = -1;
1049 piece_init(&txt->begin, NULL, &txt->end, NULL, 0);
1050 piece_init(&txt->end, &txt->begin, NULL, NULL, 0);
1051 lineno_cache_invalidate(&txt->lines);
1052 if (filename) {
1053 if ((fd = open(filename, O_RDONLY)) == -1)
1054 goto out;
1055 if (fstat(fd, &txt->info) == -1)
1056 goto out;
1057 if (!S_ISREG(txt->info.st_mode)) {
1058 errno = S_ISDIR(txt->info.st_mode) ? EISDIR : ENOTSUP;
1059 goto out;
1061 // XXX: use lseek(fd, 0, SEEK_END); instead?
1062 size_t size = txt->info.st_size;
1063 if (size < BUFFER_MMAP_SIZE)
1064 txt->buf = buffer_read(txt, size, fd);
1065 else
1066 txt->buf = buffer_mmap(txt, size, fd, 0);
1067 if (!txt->buf)
1068 goto out;
1069 Piece *p = piece_alloc(txt);
1070 if (!p)
1071 goto out;
1072 piece_init(&txt->begin, NULL, p, NULL, 0);
1073 piece_init(p, &txt->begin, &txt->end, txt->buf->data, txt->buf->len);
1074 piece_init(&txt->end, p, NULL, NULL, 0);
1075 txt->size = txt->buf->len;
1077 /* write an empty action */
1078 change_alloc(txt, EPOS);
1079 text_snapshot(txt);
1080 txt->saved_action = txt->history;
1082 if (fd != -1)
1083 close(fd);
1084 return txt;
1085 out:
1086 if (fd != -1)
1087 close(fd);
1088 text_free(txt);
1089 return NULL;
1092 struct stat text_stat(Text *txt) {
1093 return txt->info;
1096 /* A delete operation can either start/stop midway through a piece or at
1097 * a boundry. In the former case a new piece is created to represent the
1098 * remaining text before/after the modification point.
1100 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
1101 * | | | existing| |demo | |text | | |
1102 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
1103 * ^ ^
1104 * |------ delete range -----|
1106 * /-+ --> +----+ --> +--+ --> +-\
1107 * | | | exi| |t | | |
1108 * \-+ <-- +----+ <-- +--+ <-- +-/
1110 bool text_delete(Text *txt, size_t pos, size_t len) {
1111 if (len == 0)
1112 return true;
1113 if (pos + len > txt->size)
1114 return false;
1115 if (pos < txt->lines.pos)
1116 lineno_cache_invalidate(&txt->lines);
1118 Location loc = piece_get_intern(txt, pos);
1119 Piece *p = loc.piece;
1120 if (!p)
1121 return false;
1122 size_t off = loc.off;
1123 if (cache_delete(txt, p, off, len))
1124 return true;
1125 Change *c = change_alloc(txt, pos);
1126 if (!c)
1127 return false;
1129 bool midway_start = false, midway_end = false; /* split pieces? */
1130 Piece *before, *after; /* unmodified pieces before/after deletion point */
1131 Piece *start, *end; /* span which is removed */
1132 size_t cur; /* how much has already been deleted */
1134 if (off == p->len) {
1135 /* deletion starts at a piece boundry */
1136 cur = 0;
1137 before = p;
1138 start = p->next;
1139 } else {
1140 /* deletion starts midway through a piece */
1141 midway_start = true;
1142 cur = p->len - off;
1143 start = p;
1144 before = piece_alloc(txt);
1145 if (!before)
1146 return false;
1149 /* skip all pieces which fall into deletion range */
1150 while (cur < len) {
1151 p = p->next;
1152 cur += p->len;
1155 if (cur == len) {
1156 /* deletion stops at a piece boundry */
1157 end = p;
1158 after = p->next;
1159 } else {
1160 /* cur > len: deletion stops midway through a piece */
1161 midway_end = true;
1162 end = p;
1163 after = piece_alloc(txt);
1164 if (!after)
1165 return false;
1166 piece_init(after, before, p->next, p->data + p->len - (cur - len), cur - len);
1169 if (midway_start) {
1170 /* we finally know which piece follows our newly allocated before piece */
1171 piece_init(before, start->prev, after, start->data, off);
1174 Piece *new_start = NULL, *new_end = NULL;
1175 if (midway_start) {
1176 new_start = before;
1177 if (!midway_end)
1178 new_end = before;
1180 if (midway_end) {
1181 if (!midway_start)
1182 new_start = after;
1183 new_end = after;
1186 span_init(&c->new, new_start, new_end);
1187 span_init(&c->old, start, end);
1188 span_swap(txt, &c->old, &c->new);
1189 return true;
1192 bool text_delete_range(Text *txt, Filerange *r) {
1193 if (!text_range_valid(r))
1194 return false;
1195 return text_delete(txt, r->start, text_range_size(r));
1198 /* preserve the current text content such that it can be restored by
1199 * means of undo/redo operations */
1200 void text_snapshot(Text *txt) {
1201 if (txt->current_action)
1202 txt->last_action = txt->current_action;
1203 txt->current_action = NULL;
1204 txt->cache = NULL;
1208 void text_free(Text *txt) {
1209 if (!txt)
1210 return;
1212 // free history
1213 Action *hist = txt->history;
1214 while (hist && hist->prev)
1215 hist = hist->prev;
1216 while (hist) {
1217 Action *later = hist->later;
1218 action_free(hist);
1219 hist = later;
1222 for (Piece *next, *p = txt->pieces; p; p = next) {
1223 next = p->global_next;
1224 piece_free(p);
1227 for (Buffer *next, *buf = txt->buffers; buf; buf = next) {
1228 next = buf->next;
1229 buffer_free(buf);
1232 free(txt);
1235 bool text_modified(Text *txt) {
1236 return txt->saved_action != txt->history;
1239 bool text_sigbus(Text *txt, const char *addr) {
1240 for (Buffer *buf = txt->buffers; buf; buf = buf->next) {
1241 if (buf->type == MMAP && buf->data <= addr && addr < buf->data + buf->size)
1242 return true;
1244 return false;
1247 enum TextNewLine text_newline_type(Text *txt){
1248 if (!txt->newlines) {
1249 txt->newlines = TEXT_NEWLINE_NL; /* default to UNIX style \n new lines */
1250 const char *start = txt->buf ? txt->buf->data : NULL;
1251 if (start) {
1252 const char *nl = memchr(start, '\n', txt->buf->len);
1253 if (nl > start && nl[-1] == '\r')
1254 txt->newlines = TEXT_NEWLINE_CRNL;
1255 } else {
1256 char c;
1257 size_t nl = lines_skip_forward(txt, 0, 1, NULL);
1258 if (nl > 1 && text_byte_get(txt, nl-2, &c) && c == '\r')
1259 txt->newlines = TEXT_NEWLINE_CRNL;
1263 return txt->newlines;
1266 const char *text_newline_char(Text *txt) {
1267 static const char *types[] = {
1268 [TEXT_NEWLINE_NL] = "\n",
1269 [TEXT_NEWLINE_CRNL] = "\r\n",
1271 return types[text_newline_type(txt)];
1274 static bool text_iterator_init(Iterator *it, size_t pos, Piece *p, size_t off) {
1275 *it = (Iterator){
1276 .pos = pos,
1277 .piece = p,
1278 .start = p ? p->data : NULL,
1279 .end = p ? p->data + p->len : NULL,
1280 .text = p ? p->data + off : NULL,
1282 return text_iterator_valid(it);
1285 Iterator text_iterator_get(Text *txt, size_t pos) {
1286 Iterator it;
1287 Location loc = piece_get_extern(txt, pos);
1288 text_iterator_init(&it, pos, loc.piece, loc.off);
1289 return it;
1292 bool text_iterator_byte_get(Iterator *it, char *b) {
1293 if (text_iterator_valid(it)) {
1294 if (it->start <= it->text && it->text < it->end) {
1295 *b = *it->text;
1296 return true;
1297 } else if (it->pos == it->piece->text->size) { /* EOF */
1298 *b = '\0';
1299 return true;
1302 return false;
1305 bool text_iterator_next(Iterator *it) {
1306 return text_iterator_init(it, it->pos, it->piece ? it->piece->next : NULL, 0);
1309 bool text_iterator_prev(Iterator *it) {
1310 return text_iterator_init(it, it->pos, it->piece ? it->piece->prev : NULL, 0);
1313 bool text_iterator_valid(const Iterator *it) {
1314 /* filter out sentinel nodes */
1315 return it->piece && it->piece->text;
1318 bool text_iterator_byte_next(Iterator *it, char *b) {
1319 if (!text_iterator_valid(it))
1320 return false;
1321 it->text++;
1322 /* special case for advancement to EOF */
1323 if (it->text == it->end && !it->piece->next->text) {
1324 it->pos++;
1325 if (b)
1326 *b = '\0';
1327 return true;
1329 while (it->text >= it->end) {
1330 if (!text_iterator_next(it))
1331 return false;
1332 it->text = it->start;
1334 it->pos++;
1335 if (b)
1336 *b = *it->text;
1337 return true;
1340 bool text_iterator_byte_prev(Iterator *it, char *b) {
1341 if (!text_iterator_valid(it))
1342 return false;
1343 while (it->text == it->start) {
1344 if (!text_iterator_prev(it))
1345 return false;
1346 it->text = it->end;
1348 --it->text;
1349 --it->pos;
1350 if (b)
1351 *b = *it->text;
1352 return true;
1355 bool text_iterator_codepoint_next(Iterator *it, char *c) {
1356 while (text_iterator_byte_next(it, NULL)) {
1357 if (ISUTF8(*it->text)) {
1358 if (c)
1359 *c = *it->text;
1360 return true;
1363 return false;
1366 bool text_iterator_codepoint_prev(Iterator *it, char *c) {
1367 while (text_iterator_byte_prev(it, NULL)) {
1368 if (ISUTF8(*it->text)) {
1369 if (c)
1370 *c = *it->text;
1371 return true;
1374 return false;
1377 bool text_iterator_char_next(Iterator *it, char *c) {
1378 if (!text_iterator_codepoint_next(it, c))
1379 return false;
1380 mbstate_t ps = { 0 };
1381 for (;;) {
1382 char buf[MB_CUR_MAX];
1383 size_t len = text_bytes_get(it->piece->text, it->pos, sizeof buf, buf);
1384 wchar_t wc;
1385 size_t wclen = mbrtowc(&wc, buf, len, &ps);
1386 if (wclen == (size_t)-1 && errno == EILSEQ) {
1387 return true;
1388 } else if (wclen == (size_t)-2) {
1389 return false;
1390 } else if (wclen == 0) {
1391 return true;
1392 } else {
1393 int width = wcwidth(wc);
1394 if (width != 0)
1395 return true;
1396 if (!text_iterator_codepoint_next(it, c))
1397 return false;
1400 return true;
1403 bool text_iterator_char_prev(Iterator *it, char *c) {
1404 if (!text_iterator_codepoint_prev(it, c))
1405 return false;
1406 for (;;) {
1407 char buf[MB_CUR_MAX];
1408 size_t len = text_bytes_get(it->piece->text, it->pos, sizeof buf, buf);
1409 wchar_t wc;
1410 mbstate_t ps = { 0 };
1411 size_t wclen = mbrtowc(&wc, buf, len, &ps);
1412 if (wclen == (size_t)-1 && errno == EILSEQ) {
1413 return true;
1414 } else if (wclen == (size_t)-2) {
1415 return false;
1416 } else if (wclen == 0) {
1417 return true;
1418 } else {
1419 int width = wcwidth(wc);
1420 if (width != 0)
1421 return true;
1422 if (!text_iterator_codepoint_prev(it, c))
1423 return false;
1426 return true;
1429 bool text_byte_get(Text *txt, size_t pos, char *buf) {
1430 return text_bytes_get(txt, pos, 1, buf);
1433 size_t text_bytes_get(Text *txt, size_t pos, size_t len, char *buf) {
1434 if (!buf)
1435 return 0;
1436 char *cur = buf;
1437 size_t rem = len;
1438 text_iterate(txt, it, pos) {
1439 if (rem == 0)
1440 break;
1441 size_t piece_len = it.end - it.text;
1442 if (piece_len > rem)
1443 piece_len = rem;
1444 memcpy(cur, it.text, piece_len);
1445 cur += piece_len;
1446 rem -= piece_len;
1448 return len - rem;
1451 char *text_bytes_alloc0(Text *txt, size_t pos, size_t len) {
1452 if (len == SIZE_MAX)
1453 return NULL;
1454 char *buf = malloc(len+1);
1455 if (!buf)
1456 return NULL;
1457 len = text_bytes_get(txt, pos, len, buf);
1458 buf[len] = '\0';
1459 return buf;
1462 size_t text_size(Text *txt) {
1463 return txt->size;
1466 /* count the number of new lines '\n' in range [pos, pos+len) */
1467 static size_t lines_count(Text *txt, size_t pos, size_t len) {
1468 size_t lines = 0;
1469 text_iterate(txt, it, pos) {
1470 const char *start = it.text;
1471 while (len > 0 && start < it.end) {
1472 size_t n = MIN(len, (size_t)(it.end - start));
1473 const char *end = memchr(start, '\n', n);
1474 if (!end) {
1475 len -= n;
1476 break;
1478 lines++;
1479 len -= end - start + 1;
1480 start = end + 1;
1483 if (len == 0)
1484 break;
1486 return lines;
1489 /* skip n lines forward and return position afterwards */
1490 static size_t lines_skip_forward(Text *txt, size_t pos, size_t lines, size_t *lines_skipped) {
1491 size_t lines_old = lines;
1492 text_iterate(txt, it, pos) {
1493 const char *start = it.text;
1494 while (lines > 0 && start < it.end) {
1495 size_t n = it.end - start;
1496 const char *end = memchr(start, '\n', n);
1497 if (!end) {
1498 pos += n;
1499 break;
1501 pos += end - start + 1;
1502 start = end + 1;
1503 lines--;
1506 if (lines == 0)
1507 break;
1509 if (lines_skipped)
1510 *lines_skipped = lines_old - lines;
1511 return pos;
1514 static void lineno_cache_invalidate(LineCache *cache) {
1515 cache->pos = 0;
1516 cache->lineno = 1;
1519 size_t text_pos_by_lineno(Text *txt, size_t lineno) {
1520 size_t lines_skipped;
1521 LineCache *cache = &txt->lines;
1522 if (lineno <= 1)
1523 return 0;
1524 if (lineno > cache->lineno) {
1525 cache->pos = lines_skip_forward(txt, cache->pos, lineno - cache->lineno, &lines_skipped);
1526 cache->lineno += lines_skipped;
1527 } else if (lineno < cache->lineno) {
1528 #if 0
1529 // TODO does it make sense to scan memory backwards here?
1530 size_t diff = cache->lineno - lineno;
1531 if (diff < lineno) {
1532 lines_skip_backward(txt, cache->pos, diff);
1533 } else
1534 #endif
1535 cache->pos = lines_skip_forward(txt, 0, lineno - 1, &lines_skipped);
1536 cache->lineno = lines_skipped + 1;
1538 return cache->lineno == lineno ? cache->pos : EPOS;
1541 size_t text_lineno_by_pos(Text *txt, size_t pos) {
1542 LineCache *cache = &txt->lines;
1543 if (pos > txt->size)
1544 pos = txt->size;
1545 if (pos < cache->pos) {
1546 size_t diff = cache->pos - pos;
1547 if (diff < pos)
1548 cache->lineno -= lines_count(txt, pos, diff);
1549 else
1550 cache->lineno = lines_count(txt, 0, pos) + 1;
1551 } else if (pos > cache->pos) {
1552 cache->lineno += lines_count(txt, cache->pos, pos - cache->pos);
1554 cache->pos = pos;
1555 return cache->lineno;
1558 Mark text_mark_set(Text *txt, size_t pos) {
1559 if (pos == 0)
1560 return (Mark)&txt->begin;
1561 if (pos == txt->size)
1562 return (Mark)&txt->end;
1563 Location loc = piece_get_extern(txt, pos);
1564 if (!loc.piece)
1565 return NULL;
1566 return loc.piece->data + loc.off;
1569 size_t text_mark_get(Text *txt, Mark mark) {
1570 size_t cur = 0;
1572 if (!mark)
1573 return EPOS;
1574 if (mark == (Mark)&txt->begin)
1575 return 0;
1576 if (mark == (Mark)&txt->end)
1577 return txt->size;
1579 for (Piece *p = txt->begin.next; p->next; p = p->next) {
1580 if (p->data <= mark && mark < p->data + p->len)
1581 return cur + (mark - p->data);
1582 cur += p->len;
1585 return EPOS;
1588 size_t text_history_get(Text *txt, size_t index) {
1589 for (Action *a = txt->current_action ? txt->current_action : txt->history; a; a = a->prev) {
1590 if (index-- == 0) {
1591 Change *c = a->change;
1592 while (c && c->next)
1593 c = c->next;
1594 return c ? c->pos : EPOS;
1597 return EPOS;