Add explicit build commands to README
[vis.git] / text.c
blobbf597dad93ce5aea457a68f1cfa37120f5abceb9
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 <sys/types.h>
25 #include <sys/stat.h>
26 #include <sys/mman.h>
27 #if CONFIG_ACL
28 #include <sys/acl.h>
29 #endif
30 #if CONFIG_SELINUX
31 #include <selinux/selinux.h>
32 #endif
34 #include "text.h"
35 #include "text-util.h"
36 #include "util.h"
38 /* Allocate buffers holding the actual file content in junks of size: */
39 #define BUFFER_SIZE (1 << 20)
40 /* Files smaller than this value are copied on load, larger ones are mmap(2)-ed
41 * directely. Hence the former can be truncated, while doing so on the latter
42 * results in havoc. */
43 #define BUFFER_MMAP_SIZE (1 << 23)
45 /* Buffer holding the file content, either readonly mmap(2)-ed from the original
46 * file or heap allocated to store the modifications.
48 typedef struct Buffer Buffer;
49 struct Buffer {
50 size_t size; /* maximal capacity */
51 size_t len; /* current used length / insertion position */
52 char *data; /* actual data */
53 enum { MMAP, MALLOC} type; /* type of allocation */
54 Buffer *next; /* next junk */
57 /* A piece holds a reference (but doesn't itself store) a certain amount of data.
58 * All active pieces chained together form the whole content of the document.
59 * At the beginning there exists only one piece, spanning the whole document.
60 * Upon insertion/deletion new pieces will be created to represent the changes.
61 * Generally pieces are never destroyed, but kept around to peform undo/redo
62 * operations.
64 struct Piece {
65 Text *text; /* text to which this piece belongs */
66 Piece *prev, *next; /* pointers to the logical predecessor/successor */
67 Piece *global_prev; /* double linked list in order of allocation, */
68 Piece *global_next; /* used to free individual pieces */
69 const char *data; /* pointer into a Buffer holding the data */
70 size_t len; /* the length in number of bytes of the data */
73 /* used to transform a global position (byte offset starting from the beginning
74 * of the text) into an offset relative to a piece.
76 typedef struct {
77 Piece *piece; /* piece holding the location */
78 size_t off; /* offset into the piece in bytes */
79 } Location;
81 /* A Span holds a certain range of pieces. Changes to the document are always
82 * performed by swapping out an existing span with a new one.
84 typedef struct {
85 Piece *start, *end; /* start/end of the span */
86 size_t len; /* the sum of the lengths of the pieces which form this span */
87 } Span;
89 /* A Change keeps all needed information to redo/undo an insertion/deletion. */
90 typedef struct Change Change;
91 struct Change {
92 Span old; /* all pieces which are being modified/swapped out by the change */
93 Span new; /* all pieces which are introduced/swapped in by the change */
94 size_t pos; /* absolute position at which the change occured */
95 Change *next; /* next change which is part of the same action */
96 Change *prev; /* previous change which is part of the same action */
99 /* An Action is a list of Changes which are used to undo/redo all modifications
100 * since the last snapshot operation. Actions are stored in a directed graph structure.
102 typedef struct Action Action;
103 struct Action {
104 Change *change; /* the most recent change */
105 Action *next; /* the next (child) action in the undo tree */
106 Action *prev; /* the previous (parent) operation in the undo tree */
107 Action *earlier; /* the previous Action, chronologically */
108 Action *later; /* the next Action, chronologically */
109 time_t time; /* when the first change of this action was performed */
110 size_t seq; /* a unique, strictly increasing identifier */
113 typedef struct {
114 size_t pos; /* position in bytes from start of file */
115 size_t lineno; /* line number in file i.e. number of '\n' in [0, pos) */
116 } LineCache;
118 /* The main struct holding all information of a given file */
119 struct Text {
120 Buffer *buf; /* original file content at the time of load operation */
121 Buffer *buffers; /* all buffers which have been allocated to hold insertion data */
122 Piece *pieces; /* all pieces which have been allocated, used to free them */
123 Piece *cache; /* most recently modified piece */
124 Piece begin, end; /* sentinel nodes which always exists but don't hold any data */
125 Action *history; /* undo tree */
126 Action *current_action; /* action holding all file changes until a snapshot is performed */
127 Action *last_action; /* the last action added to the tree, chronologically */
128 Action *saved_action; /* the last action at the time of the save operation */
129 size_t size; /* current file content size in bytes */
130 struct stat info; /* stat as probed at load time */
131 LineCache lines; /* mapping between absolute pos in bytes and logical line breaks */
132 enum TextNewLine newlines; /* which type of new lines does the file use */
135 /* buffer management */
136 static Buffer *buffer_alloc(Text *txt, size_t size);
137 static Buffer *buffer_read(Text *txt, size_t size, int fd);
138 static Buffer *buffer_mmap(Text *txt, size_t size, int fd, off_t offset);
139 static void buffer_free(Buffer *buf);
140 static bool buffer_capacity(Buffer *buf, size_t len);
141 static const char *buffer_append(Buffer *buf, const char *data, size_t len);
142 static bool buffer_insert(Buffer *buf, size_t pos, const char *data, size_t len);
143 static bool buffer_delete(Buffer *buf, size_t pos, size_t len);
144 static const char *buffer_store(Text *txt, const char *data, size_t len);
145 /* cache layer */
146 static void cache_piece(Text *txt, Piece *p);
147 static bool cache_contains(Text *txt, Piece *p);
148 static bool cache_insert(Text *txt, Piece *p, size_t off, const char *data, size_t len);
149 static bool cache_delete(Text *txt, Piece *p, size_t off, size_t len);
150 /* piece management */
151 static Piece *piece_alloc(Text *txt);
152 static void piece_free(Piece *p);
153 static void piece_init(Piece *p, Piece *prev, Piece *next, const char *data, size_t len);
154 static Location piece_get_intern(Text *txt, size_t pos);
155 static Location piece_get_extern(Text *txt, size_t pos);
156 /* span management */
157 static void span_init(Span *span, Piece *start, Piece *end);
158 static void span_swap(Text *txt, Span *old, Span *new);
159 /* change management */
160 static Change *change_alloc(Text *txt, size_t pos);
161 static void change_free(Change *c);
162 /* action management */
163 static Action *action_alloc(Text *txt);
164 static void action_free(Action *a);
165 /* logical line counting cache */
166 static void lineno_cache_invalidate(LineCache *cache);
167 static size_t lines_skip_forward(Text *txt, size_t pos, size_t lines, size_t *lines_skiped);
168 static size_t lines_count(Text *txt, size_t pos, size_t len);
170 static ssize_t write_all(int fd, const char *buf, size_t count) {
171 size_t rem = count;
172 while (rem > 0) {
173 ssize_t written = write(fd, buf, rem);
174 if (written < 0) {
175 if (errno == EAGAIN || errno == EINTR)
176 continue;
177 return -1;
178 } else if (written == 0) {
179 break;
181 rem -= written;
182 buf += written;
184 return count - rem;
187 /* allocate a new buffer of MAX(size, BUFFER_SIZE) bytes */
188 static Buffer *buffer_alloc(Text *txt, size_t size) {
189 Buffer *buf = calloc(1, sizeof(Buffer));
190 if (!buf)
191 return NULL;
192 if (BUFFER_SIZE > size)
193 size = BUFFER_SIZE;
194 if (!(buf->data = malloc(size))) {
195 free(buf);
196 return NULL;
198 buf->type = MALLOC;
199 buf->size = size;
200 buf->next = txt->buffers;
201 txt->buffers = buf;
202 return buf;
205 static Buffer *buffer_read(Text *txt, size_t size, int fd) {
206 Buffer *buf = buffer_alloc(txt, size);
207 if (!buf)
208 return NULL;
209 while (size > 0) {
210 char data[4096];
211 ssize_t len = read(fd, data, MIN(sizeof(data), size));
212 if (len == -1) {
213 txt->buffers = buf->next;
214 buffer_free(buf);
215 return NULL;
216 } else if (len == 0) {
217 break;
218 } else {
219 buffer_append(buf, data, len);
220 size -= len;
223 return buf;
226 static Buffer *buffer_mmap(Text *txt, size_t size, int fd, off_t offset) {
227 Buffer *buf = calloc(1, sizeof(Buffer));
228 if (!buf)
229 return NULL;
230 if (size) {
231 buf->data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset);
232 if (buf->data == MAP_FAILED) {
233 free(buf);
234 return NULL;
237 buf->type = MMAP;
238 buf->size = size;
239 buf->len = size;
240 buf->next = txt->buffers;
241 txt->buffers = buf;
242 return buf;
245 static void buffer_free(Buffer *buf) {
246 if (!buf)
247 return;
248 if (buf->type == MALLOC)
249 free(buf->data);
250 else if (buf->type == MMAP && buf->data)
251 munmap(buf->data, buf->size);
252 free(buf);
255 /* check whether buffer has enough free space to store len bytes */
256 static bool buffer_capacity(Buffer *buf, size_t len) {
257 return buf->size - buf->len >= len;
260 /* append data to buffer, assumes there is enough space available */
261 static const char *buffer_append(Buffer *buf, const char *data, size_t len) {
262 char *dest = memcpy(buf->data + buf->len, data, len);
263 buf->len += len;
264 return dest;
267 /* stores the given data in a buffer, allocates a new one if necessary. returns
268 * a pointer to the storage location or NULL if allocation failed. */
269 static const char *buffer_store(Text *txt, const char *data, size_t len) {
270 Buffer *buf = txt->buffers;
271 if ((!buf || !buffer_capacity(buf, len)) && !(buf = buffer_alloc(txt, len)))
272 return NULL;
273 return buffer_append(buf, data, len);
276 /* insert data into buffer at an arbitrary position, this should only be used with
277 * data of the most recently created piece. */
278 static bool buffer_insert(Buffer *buf, size_t pos, const char *data, size_t len) {
279 if (pos > buf->len || !buffer_capacity(buf, len))
280 return false;
281 if (buf->len == pos)
282 return buffer_append(buf, data, len);
283 char *insert = buf->data + pos;
284 memmove(insert + len, insert, buf->len - pos);
285 memcpy(insert, data, len);
286 buf->len += len;
287 return true;
290 /* delete data from a buffer at an arbitrary position, this should only be used with
291 * data of the most recently created piece. */
292 static bool buffer_delete(Buffer *buf, size_t pos, size_t len) {
293 if (pos + len > buf->len)
294 return false;
295 if (buf->len == pos) {
296 buf->len -= len;
297 return true;
299 char *delete = buf->data + pos;
300 memmove(delete, delete + len, buf->len - pos - len);
301 buf->len -= len;
302 return true;
305 /* cache the given piece if it is the most recently changed one */
306 static void cache_piece(Text *txt, Piece *p) {
307 Buffer *buf = txt->buffers;
308 if (!buf || p->data < buf->data || p->data + p->len != buf->data + buf->len)
309 return;
310 txt->cache = p;
313 /* check whether the given piece was the most recently modified one */
314 static bool cache_contains(Text *txt, Piece *p) {
315 Buffer *buf = txt->buffers;
316 Action *a = txt->current_action;
317 if (!buf || !txt->cache || txt->cache != p || !a || !a->change)
318 return false;
320 Piece *start = a->change->new.start;
321 Piece *end = a->change->new.end;
322 bool found = false;
323 for (Piece *cur = start; !found; cur = cur->next) {
324 if (cur == p)
325 found = true;
326 if (cur == end)
327 break;
330 return found && p->data + p->len == buf->data + buf->len;
333 /* try to insert a junk of data at a given piece offset. the insertion is only
334 * performed if the piece is the most recenetly changed one. the legnth of the
335 * piece, the span containing it and the whole text is adjusted accordingly */
336 static bool cache_insert(Text *txt, Piece *p, size_t off, const char *data, size_t len) {
337 if (!cache_contains(txt, p))
338 return false;
339 Buffer *buf = txt->buffers;
340 size_t bufpos = p->data + off - buf->data;
341 if (!buffer_insert(buf, bufpos, data, len))
342 return false;
343 p->len += len;
344 txt->current_action->change->new.len += len;
345 txt->size += len;
346 return true;
349 /* try to delete a junk of data at a given piece offset. the deletion is only
350 * performed if the piece is the most recenetly changed one and the whole
351 * affected range lies within it. the legnth of the piece, the span containing it
352 * and the whole text is adjusted accordingly */
353 static bool cache_delete(Text *txt, Piece *p, size_t off, size_t len) {
354 if (!cache_contains(txt, p))
355 return false;
356 Buffer *buf = txt->buffers;
357 size_t bufpos = p->data + off - buf->data;
358 if (off + len > p->len || !buffer_delete(buf, bufpos, len))
359 return false;
360 p->len -= len;
361 txt->current_action->change->new.len -= len;
362 txt->size -= len;
363 return true;
366 /* initialize a span and calculate its length */
367 static void span_init(Span *span, Piece *start, Piece *end) {
368 size_t len = 0;
369 span->start = start;
370 span->end = end;
371 for (Piece *p = start; p; p = p->next) {
372 len += p->len;
373 if (p == end)
374 break;
376 span->len = len;
379 /* swap out an old span and replace it with a new one.
381 * - if old is an empty span do not remove anything, just insert the new one
382 * - if new is an empty span do not insert anything, just remove the old one
384 * adjusts the document size accordingly.
386 static void span_swap(Text *txt, Span *old, Span *new) {
387 if (old->len == 0 && new->len == 0) {
388 return;
389 } else if (old->len == 0) {
390 /* insert new span */
391 new->start->prev->next = new->start;
392 new->end->next->prev = new->end;
393 } else if (new->len == 0) {
394 /* delete old span */
395 old->start->prev->next = old->end->next;
396 old->end->next->prev = old->start->prev;
397 } else {
398 /* replace old with new */
399 old->start->prev->next = new->start;
400 old->end->next->prev = new->end;
402 txt->size -= old->len;
403 txt->size += new->len;
406 /* allocate a new action, set its pointers to the other actions in the history,
407 * and set it as txt->history. All further changes will be associated with this action. */
408 static Action *action_alloc(Text *txt) {
409 Action *new = calloc(1, sizeof(Action));
410 if (!new)
411 return NULL;
412 new->time = time(NULL);
413 txt->current_action = new;
415 /* set sequence number */
416 if (!txt->last_action)
417 new->seq = 0;
418 else
419 new->seq = txt->last_action->seq + 1;
421 /* set earlier, later pointers */
422 if (txt->last_action)
423 txt->last_action->later = new;
424 new->earlier = txt->last_action;
426 if (!txt->history) {
427 txt->history = new;
428 return new;
431 /* set prev, next pointers */
432 new->prev = txt->history;
433 txt->history->next = new;
434 txt->history = new;
435 return new;
438 static void action_free(Action *a) {
439 if (!a)
440 return;
441 for (Change *next, *c = a->change; c; c = next) {
442 next = c->next;
443 change_free(c);
445 free(a);
448 static Piece *piece_alloc(Text *txt) {
449 Piece *p = calloc(1, sizeof(Piece));
450 if (!p)
451 return NULL;
452 p->text = txt;
453 p->global_next = txt->pieces;
454 if (txt->pieces)
455 txt->pieces->global_prev = p;
456 txt->pieces = p;
457 return p;
460 static void piece_free(Piece *p) {
461 if (!p)
462 return;
463 if (p->global_prev)
464 p->global_prev->global_next = p->global_next;
465 if (p->global_next)
466 p->global_next->global_prev = p->global_prev;
467 if (p->text->pieces == p)
468 p->text->pieces = p->global_next;
469 if (p->text->cache == p)
470 p->text->cache = NULL;
471 free(p);
474 static void piece_init(Piece *p, Piece *prev, Piece *next, const char *data, size_t len) {
475 p->prev = prev;
476 p->next = next;
477 p->data = data;
478 p->len = len;
481 /* returns the piece holding the text at byte offset pos. if pos happens to
482 * be at a piece boundry i.e. the first byte of a piece then the previous piece
483 * to the left is returned with an offset of piece->len. this is convenient for
484 * modifications to the piece chain where both pieces (the returned one and the
485 * one following it) are needed, but unsuitable as a public interface.
487 * in particular if pos is zero, the begin sentinel piece is returned.
489 static Location piece_get_intern(Text *txt, size_t pos) {
490 size_t cur = 0;
491 for (Piece *p = &txt->begin; p->next; p = p->next) {
492 if (cur <= pos && pos <= cur + p->len)
493 return (Location){ .piece = p, .off = pos - cur };
494 cur += p->len;
497 return (Location){ 0 };
500 /* similiar to piece_get_intern but usable as a public API. returns the piece
501 * holding the text at byte offset pos. never returns a sentinel piece.
502 * it pos is the end of file (== text_size()) and the file is not empty then
503 * the last piece holding data is returned.
505 static Location piece_get_extern(Text *txt, size_t pos) {
506 size_t cur = 0;
508 if (pos > 0 && pos == txt->size) {
509 Piece *p = txt->begin.next;
510 while (p->next->next)
511 p = p->next;
512 return (Location){ .piece = p, .off = p->len };
515 for (Piece *p = txt->begin.next; p->next; p = p->next) {
516 if (cur <= pos && pos < cur + p->len)
517 return (Location){ .piece = p, .off = pos - cur };
518 cur += p->len;
521 return (Location){ 0 };
524 /* allocate a new change, associate it with current action or a newly
525 * allocated one if none exists. */
526 static Change *change_alloc(Text *txt, size_t pos) {
527 Action *a = txt->current_action;
528 if (!a) {
529 a = action_alloc(txt);
530 if (!a)
531 return NULL;
533 Change *c = calloc(1, sizeof(Change));
534 if (!c)
535 return NULL;
536 c->pos = pos;
537 c->next = a->change;
538 if (a->change)
539 a->change->prev = c;
540 a->change = c;
541 return c;
544 static void change_free(Change *c) {
545 if (!c)
546 return;
547 /* only free the new part of the span, the old one is still in use */
548 piece_free(c->new.start);
549 if (c->new.start != c->new.end)
550 piece_free(c->new.end);
551 free(c);
554 /* When inserting new data there are 2 cases to consider.
556 * - in the first the insertion point falls into the middle of an exisiting
557 * piece which is replaced by three new pieces:
559 * /-+ --> +---------------+ --> +-\
560 * | | | existing text | | |
561 * \-+ <-- +---------------+ <-- +-/
563 * Insertion point for "demo "
565 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
566 * | | | existing| |demo | |text | | |
567 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
569 * - the second case deals with an insertion point at a piece boundry:
571 * /-+ --> +---------------+ --> +-\
572 * | | | existing text | | |
573 * \-+ <-- +---------------+ <-- +-/
575 * Insertion point for "short"
577 * /-+ --> +-----+ --> +---------------+ --> +-\
578 * | | |short| | existing text | | |
579 * \-+ <-- +-----+ <-- +---------------+ <-- +-/
581 bool text_insert(Text *txt, size_t pos, const char *data, size_t len) {
582 if (len == 0)
583 return true;
584 if (pos > txt->size)
585 return false;
586 if (pos < txt->lines.pos)
587 lineno_cache_invalidate(&txt->lines);
589 Location loc = piece_get_intern(txt, pos);
590 Piece *p = loc.piece;
591 if (!p)
592 return false;
593 size_t off = loc.off;
594 if (cache_insert(txt, p, off, data, len))
595 return true;
597 Change *c = change_alloc(txt, pos);
598 if (!c)
599 return false;
601 if (!(data = buffer_store(txt, data, len)))
602 return false;
604 Piece *new = NULL;
606 if (off == p->len) {
607 /* insert between two existing pieces, hence there is nothing to
608 * remove, just add a new piece holding the extra text */
609 if (!(new = piece_alloc(txt)))
610 return false;
611 piece_init(new, p, p->next, data, len);
612 span_init(&c->new, new, new);
613 span_init(&c->old, NULL, NULL);
614 } else {
615 /* insert into middle of an existing piece, therfore split the old
616 * piece. that is we have 3 new pieces one containing the content
617 * before the insertion point then one holding the newly inserted
618 * text and one holding the content after the insertion point.
620 Piece *before = piece_alloc(txt);
621 new = piece_alloc(txt);
622 Piece *after = piece_alloc(txt);
623 if (!before || !new || !after)
624 return false;
625 piece_init(before, p->prev, new, p->data, off);
626 piece_init(new, before, after, data, len);
627 piece_init(after, new, p->next, p->data + off, p->len - off);
629 span_init(&c->new, before, after);
630 span_init(&c->old, p, p);
633 cache_piece(txt, new);
634 span_swap(txt, &c->old, &c->new);
635 return true;
638 bool text_appendf(Text *txt, const char *format, ...) {
639 va_list ap;
640 va_start(ap, format);
641 bool ret = text_vprintf(txt, text_size(txt), format, ap);
642 va_end(ap);
643 return ret;
646 bool text_printf(Text *txt, size_t pos, const char *format, ...) {
647 va_list ap;
648 va_start(ap, format);
649 bool ret = text_vprintf(txt, pos, format, ap);
650 va_end(ap);
651 return ret;
654 bool text_vprintf(Text *txt, size_t pos, const char *format, va_list ap) {
655 va_list ap_save;
656 va_copy(ap_save, ap);
657 int len = vsnprintf(NULL, 0, format, ap);
658 if (len == -1)
659 return false;
660 char *buf = malloc(len+1);
661 bool ret = buf && (vsnprintf(buf, len+1, format, ap_save) == len) && text_insert(txt, pos, buf, len);
662 free(buf);
663 va_end(ap_save);
664 return ret;
667 bool text_insert_newline(Text *txt, size_t pos) {
668 switch (text_newline_type(txt)) {
669 case TEXT_NEWLINE_NL:
670 return text_insert(txt, pos, "\n", 1);
671 case TEXT_NEWLINE_CRNL:
672 return text_insert(txt, pos, "\r\n", 2);
673 default:
674 return false;
678 static size_t action_undo(Text *txt, Action *a) {
679 size_t pos = EPOS;
680 for (Change *c = a->change; c; c = c->next) {
681 span_swap(txt, &c->new, &c->old);
682 pos = c->pos;
684 return pos;
687 static size_t action_redo(Text *txt, Action *a) {
688 size_t pos = EPOS;
689 Change *c = a->change;
690 while (c->next)
691 c = c->next;
692 for ( ; c; c = c->prev) {
693 span_swap(txt, &c->old, &c->new);
694 pos = c->pos;
695 if (c->new.len > c->old.len)
696 pos += c->new.len - c->old.len;
698 return pos;
701 size_t text_undo(Text *txt) {
702 size_t pos = EPOS;
703 /* taking a snapshot makes sure that txt->current_action is reset */
704 text_snapshot(txt);
705 Action *a = txt->history->prev;
706 if (!a)
707 return pos;
708 pos = action_undo(txt, txt->history);
709 txt->history = a;
710 lineno_cache_invalidate(&txt->lines);
711 return pos;
714 size_t text_redo(Text *txt) {
715 size_t pos = EPOS;
716 /* taking a snapshot makes sure that txt->current_action is reset */
717 text_snapshot(txt);
718 Action *a = txt->history->next;
719 if (!a)
720 return pos;
721 pos = action_redo(txt, a);
722 txt->history = a;
723 lineno_cache_invalidate(&txt->lines);
724 return pos;
727 static bool history_change_branch(Action *a) {
728 bool changed = false;
729 while (a->prev) {
730 if (a->prev->next != a) {
731 a->prev->next = a;
732 changed = true;
734 a = a->prev;
736 return changed;
739 static size_t history_traverse_to(Text *txt, Action *a) {
740 size_t pos = EPOS;
741 if (!a)
742 return pos;
743 bool changed = history_change_branch(a);
744 if (!changed) {
745 if (a->seq == txt->history->seq) {
746 return txt->lines.pos;
747 } else if (a->seq > txt->history->seq) {
748 while (txt->history != a)
749 pos = text_redo(txt);
750 return pos;
751 } else if (a->seq < txt->history->seq) {
752 while (txt->history != a)
753 pos = text_undo(txt);
754 return pos;
756 } else {
757 while (txt->history->prev && txt->history->prev->next == txt->history)
758 text_undo(txt);
759 pos = text_undo(txt);
760 while (txt->history != a)
761 pos = text_redo(txt);
762 return pos;
764 return pos;
767 size_t text_earlier(Text *txt, int count) {
768 Action *a = txt->history;
769 while (count-- > 0 && a->earlier)
770 a = a->earlier;
771 return history_traverse_to(txt, a);
774 size_t text_later(Text *txt, int count) {
775 Action *a = txt->history;
776 while (count-- > 0 && a->later)
777 a = a->later;
778 return history_traverse_to(txt, a);
781 size_t text_restore(Text *txt, time_t time) {
782 Action *a = txt->history;
783 while (time < a->time && a->earlier)
784 a = a->earlier;
785 while (time > a->time && a->later)
786 a = a->later;
787 time_t diff = labs(a->time - time);
788 if (a->earlier && a->earlier != txt->history && labs(a->earlier->time - time) < diff)
789 a = a->earlier;
790 if (a->later && a->later != txt->history && labs(a->later->time - time) < diff)
791 a = a->later;
792 return history_traverse_to(txt, a);
795 time_t text_state(Text *txt) {
796 return txt->history->time;
799 static bool preserve_acl(int src, int dest) {
800 #if CONFIG_ACL
801 acl_t acl = acl_get_fd(src);
802 if (!acl)
803 return errno == ENOTSUP ? true : false;
804 if (acl_set_fd(dest, acl) == -1) {
805 acl_free(acl);
806 return false;
808 acl_free(acl);
809 #endif /* CONFIG_ACL */
810 return true;
813 static bool preserve_selinux_context(int src, int dest) {
814 #if CONFIG_SELINUX
815 char *context = NULL;
816 if (!is_selinux_enabled())
817 return true;
818 if (fgetfilecon(src, &context) == -1)
819 return errno == ENOTSUP ? true : false;
820 if (fsetfilecon(dest, context) == -1) {
821 freecon(context);
822 return false;
824 freecon(context);
825 #endif /* CONFIG_SELINUX */
826 return true;
829 /* Save current content to given filename. The data is first saved to `filename~`
830 * and then atomically moved to its final (possibly alredy existing) destination
831 * using rename(2). This approach does not work if:
833 * - the file is a symbolic link
834 * - the file is a hard link
835 * - file ownership can not be preserved
836 * - file group can not be preserved
837 * - directory permissions do not allow creation of a new file
838 * - POSXI ACL can not be preserved (if enabled)
839 * - SELinux security context can not be preserved (if enabled)
841 static bool text_save_atomic_range(Text *txt, Filerange *range, const char *filename) {
842 struct stat meta = { 0 }, oldmeta = { 0 };
843 int fd = -1, oldfd = -1, saved_errno;
844 char *tmpname = NULL;
845 size_t size = text_range_size(range);
846 size_t namelen = strlen(filename) + 1 /* ~ */ + 1 /* \0 */;
848 if ((oldfd = open(filename, O_RDONLY)) == -1 && errno != ENOENT)
849 goto err;
850 if (oldfd != -1 && lstat(filename, &oldmeta) == -1)
851 goto err;
852 if (oldfd != -1) {
853 if (S_ISLNK(oldmeta.st_mode)) /* symbolic link */
854 goto err;
855 if (oldmeta.st_nlink > 1) /* hard link */
856 goto err;
858 if (!(tmpname = calloc(1, namelen)))
859 goto err;
860 snprintf(tmpname, namelen, "%s~", filename);
862 /* O_RDWR is needed because otherwise we can't map with MAP_SHARED */
863 if ((fd = open(tmpname, O_CREAT|O_RDWR|O_TRUNC, oldfd == -1 ? S_IRUSR|S_IWUSR : oldmeta.st_mode)) == -1)
864 goto err;
865 if (ftruncate(fd, size) == -1)
866 goto err;
867 if (oldfd != -1) {
868 if (!preserve_acl(oldfd, fd) || !preserve_selinux_context(oldfd, fd))
869 goto err;
870 /* change owner if necessary */
871 if (oldmeta.st_uid != getuid() && fchown(fd, oldmeta.st_uid, (uid_t)-1) == -1)
872 goto err;
873 /* change group if necessary, in case of failure some editors reset
874 * the group permissions to the same as for others */
875 if (oldmeta.st_gid != getgid() && fchown(fd, (uid_t)-1, oldmeta.st_gid) == -1)
876 goto err;
879 if (size > 0) {
880 void *buf = mmap(NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
881 if (buf == MAP_FAILED)
882 goto err;
884 char *cur = buf;
885 size_t rem = size;
887 for (Iterator it = text_iterator_get(txt, range->start);
888 rem > 0 && text_iterator_valid(&it);
889 text_iterator_next(&it)) {
890 size_t len = it.end - it.text;
891 if (len > rem)
892 len = rem;
893 memcpy(cur, it.text, len);
894 cur += len;
895 rem -= len;
898 if (munmap(buf, size) == -1)
899 goto err;
902 if (oldfd != -1) {
903 close(oldfd);
904 oldfd = -1;
907 if (fsync(fd) == -1)
908 goto err;
910 if (fstat(fd, &meta) == -1)
911 goto err;
913 if (close(fd) == -1) {
914 fd = -1;
915 goto err;
917 fd = -1;
919 if (rename(tmpname, filename) == -1)
920 goto err;
922 if (meta.st_mtime)
923 txt->info = meta;
924 free(tmpname);
925 return true;
926 err:
927 saved_errno = errno;
928 if (oldfd != -1)
929 close(oldfd);
930 if (fd != -1)
931 close(fd);
932 if (tmpname && *tmpname)
933 unlink(tmpname);
934 free(tmpname);
935 errno = saved_errno;
936 return false;
939 bool text_save(Text *txt, const char *filename) {
940 Filerange r = (Filerange){ .start = 0, .end = text_size(txt) };
941 return text_save_range(txt, &r, filename);
944 /* First try to save the file atomically using rename(2) if this does not
945 * work overwrite the file in place. However if something goes wrong during
946 * this overwrite the original file is permanently damaged.
948 bool text_save_range(Text *txt, Filerange *range, const char *filename) {
949 struct stat meta;
950 int fd = -1, newfd = -1;
951 if (!filename || text_save_atomic_range(txt, range, filename))
952 goto ok;
953 if ((fd = open(filename, O_CREAT|O_WRONLY, S_IRUSR|S_IWUSR)) == -1)
954 goto err;
955 if (fstat(fd, &meta) == -1)
956 goto err;
957 if (meta.st_dev == txt->info.st_dev && meta.st_ino == txt->info.st_ino &&
958 txt->buf && txt->buf->type == MMAP && txt->buf->size) {
959 /* The file we are going to overwrite is currently mmap-ed from
960 * text_load, therefore we copy the mmap-ed buffer to a temporary
961 * file and remap it at the same position such that all pointers
962 * from the various pieces are still valid.
964 size_t size = txt->buf->size;
965 char tmpname[32] = "/tmp/vis-XXXXXX";
966 mode_t mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
967 newfd = mkstemp(tmpname);
968 umask(mask);
969 if (newfd == -1)
970 goto err;
971 if (unlink(tmpname) == -1)
972 goto err;
973 ssize_t written = write_all(newfd, txt->buf->data, size);
974 if (written == -1 || (size_t)written != size)
975 goto err;
976 if (munmap(txt->buf->data, size) == -1)
977 goto err;
979 void *data = mmap(txt->buf->data, size, PROT_READ, MAP_SHARED, newfd, 0);
980 if (data == MAP_FAILED)
981 goto err;
982 if (data != txt->buf->data) {
983 munmap(data, size);
984 goto err;
986 if (close(newfd) == -1) {
987 newfd = -1;
988 goto err;
990 txt->buf->data = data;
991 newfd = -1;
993 /* overwrite the exisiting file content, if somehting goes wrong
994 * here we are screwed, TODO: make a backup before? */
995 if (ftruncate(fd, 0) == -1)
996 goto err;
997 ssize_t written = text_write_range(txt, range, fd);
998 if (written == -1 || (size_t)written != text_range_size(range))
999 goto err;
1001 if (fsync(fd) == -1)
1002 goto err;
1003 if (fstat(fd, &meta) == -1)
1004 goto err;
1005 if (close(fd) == -1)
1006 return false;
1007 txt->info = meta;
1009 txt->saved_action = txt->history;
1010 text_snapshot(txt);
1011 return true;
1012 err:
1013 if (fd != -1)
1014 close(fd);
1015 if (newfd != -1)
1016 close(newfd);
1017 return false;
1020 ssize_t text_write(Text *txt, int fd) {
1021 Filerange r = (Filerange){ .start = 0, .end = text_size(txt) };
1022 return text_write_range(txt, &r, fd);
1025 ssize_t text_write_range(Text *txt, Filerange *range, int fd) {
1026 size_t size = text_range_size(range), rem = size;
1027 for (Iterator it = text_iterator_get(txt, range->start);
1028 rem > 0 && text_iterator_valid(&it);
1029 text_iterator_next(&it)) {
1030 size_t prem = it.end - it.text;
1031 if (prem > rem)
1032 prem = rem;
1033 ssize_t written = write_all(fd, it.text, prem);
1034 if (written == -1)
1035 return -1;
1036 rem -= written;
1037 if ((size_t)written != prem)
1038 break;
1040 return size - rem;
1043 /* load the given file as starting point for further editing operations.
1044 * to start with an empty document, pass NULL as filename. */
1045 Text *text_load(const char *filename) {
1046 Text *txt = calloc(1, sizeof(Text));
1047 if (!txt)
1048 return NULL;
1049 int fd = -1;
1050 piece_init(&txt->begin, NULL, &txt->end, NULL, 0);
1051 piece_init(&txt->end, &txt->begin, NULL, NULL, 0);
1052 lineno_cache_invalidate(&txt->lines);
1053 if (filename) {
1054 if ((fd = open(filename, O_RDONLY)) == -1)
1055 goto out;
1056 if (fstat(fd, &txt->info) == -1)
1057 goto out;
1058 if (!S_ISREG(txt->info.st_mode)) {
1059 errno = S_ISDIR(txt->info.st_mode) ? EISDIR : ENOTSUP;
1060 goto out;
1062 // XXX: use lseek(fd, 0, SEEK_END); instead?
1063 size_t size = txt->info.st_size;
1064 if (size < BUFFER_MMAP_SIZE)
1065 txt->buf = buffer_read(txt, size, fd);
1066 else
1067 txt->buf = buffer_mmap(txt, size, fd, 0);
1068 if (!txt->buf)
1069 goto out;
1070 Piece *p = piece_alloc(txt);
1071 if (!p)
1072 goto out;
1073 piece_init(&txt->begin, NULL, p, NULL, 0);
1074 piece_init(p, &txt->begin, &txt->end, txt->buf->data, txt->buf->len);
1075 piece_init(&txt->end, p, NULL, NULL, 0);
1076 txt->size = txt->buf->len;
1078 /* write an empty action */
1079 change_alloc(txt, EPOS);
1080 text_snapshot(txt);
1081 txt->saved_action = txt->history;
1083 if (fd != -1)
1084 close(fd);
1085 return txt;
1086 out:
1087 if (fd != -1)
1088 close(fd);
1089 text_free(txt);
1090 return NULL;
1093 struct stat text_stat(Text *txt) {
1094 return txt->info;
1097 /* A delete operation can either start/stop midway through a piece or at
1098 * a boundry. In the former case a new piece is created to represent the
1099 * remaining text before/after the modification point.
1101 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
1102 * | | | existing| |demo | |text | | |
1103 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
1104 * ^ ^
1105 * |------ delete range -----|
1107 * /-+ --> +----+ --> +--+ --> +-\
1108 * | | | exi| |t | | |
1109 * \-+ <-- +----+ <-- +--+ <-- +-/
1111 bool text_delete(Text *txt, size_t pos, size_t len) {
1112 if (len == 0)
1113 return true;
1114 if (pos + len > txt->size)
1115 return false;
1116 if (pos < txt->lines.pos)
1117 lineno_cache_invalidate(&txt->lines);
1119 Location loc = piece_get_intern(txt, pos);
1120 Piece *p = loc.piece;
1121 if (!p)
1122 return false;
1123 size_t off = loc.off;
1124 if (cache_delete(txt, p, off, len))
1125 return true;
1126 Change *c = change_alloc(txt, pos);
1127 if (!c)
1128 return false;
1130 bool midway_start = false, midway_end = false; /* split pieces? */
1131 Piece *before, *after; /* unmodified pieces before/after deletion point */
1132 Piece *start, *end; /* span which is removed */
1133 size_t cur; /* how much has already been deleted */
1135 if (off == p->len) {
1136 /* deletion starts at a piece boundry */
1137 cur = 0;
1138 before = p;
1139 start = p->next;
1140 } else {
1141 /* deletion starts midway through a piece */
1142 midway_start = true;
1143 cur = p->len - off;
1144 start = p;
1145 before = piece_alloc(txt);
1146 if (!before)
1147 return false;
1150 /* skip all pieces which fall into deletion range */
1151 while (cur < len) {
1152 p = p->next;
1153 cur += p->len;
1156 if (cur == len) {
1157 /* deletion stops at a piece boundry */
1158 end = p;
1159 after = p->next;
1160 } else {
1161 /* cur > len: deletion stops midway through a piece */
1162 midway_end = true;
1163 end = p;
1164 after = piece_alloc(txt);
1165 if (!after)
1166 return false;
1167 piece_init(after, before, p->next, p->data + p->len - (cur - len), cur - len);
1170 if (midway_start) {
1171 /* we finally know which piece follows our newly allocated before piece */
1172 piece_init(before, start->prev, after, start->data, off);
1175 Piece *new_start = NULL, *new_end = NULL;
1176 if (midway_start) {
1177 new_start = before;
1178 if (!midway_end)
1179 new_end = before;
1181 if (midway_end) {
1182 if (!midway_start)
1183 new_start = after;
1184 new_end = after;
1187 span_init(&c->new, new_start, new_end);
1188 span_init(&c->old, start, end);
1189 span_swap(txt, &c->old, &c->new);
1190 return true;
1193 bool text_delete_range(Text *txt, Filerange *r) {
1194 if (!text_range_valid(r))
1195 return false;
1196 return text_delete(txt, r->start, text_range_size(r));
1199 /* preserve the current text content such that it can be restored by
1200 * means of undo/redo operations */
1201 void text_snapshot(Text *txt) {
1202 if (txt->current_action)
1203 txt->last_action = txt->current_action;
1204 txt->current_action = NULL;
1205 txt->cache = NULL;
1209 void text_free(Text *txt) {
1210 if (!txt)
1211 return;
1213 // free history
1214 Action *hist = txt->history;
1215 while (hist && hist->prev)
1216 hist = hist->prev;
1217 while (hist) {
1218 Action *later = hist->later;
1219 action_free(hist);
1220 hist = later;
1223 for (Piece *next, *p = txt->pieces; p; p = next) {
1224 next = p->global_next;
1225 piece_free(p);
1228 for (Buffer *next, *buf = txt->buffers; buf; buf = next) {
1229 next = buf->next;
1230 buffer_free(buf);
1233 free(txt);
1236 bool text_modified(Text *txt) {
1237 return txt->saved_action != txt->history;
1240 bool text_sigbus(Text *txt, const char *addr) {
1241 for (Buffer *buf = txt->buffers; buf; buf = buf->next) {
1242 if (buf->type == MMAP && buf->data <= addr && addr < buf->data + buf->size)
1243 return true;
1245 return false;
1248 enum TextNewLine text_newline_type(Text *txt){
1249 if (!txt->newlines) {
1250 txt->newlines = TEXT_NEWLINE_NL; /* default to UNIX style \n new lines */
1251 const char *start = txt->buf ? txt->buf->data : NULL;
1252 if (start) {
1253 const char *nl = memchr(start, '\n', txt->buf->len);
1254 if (nl > start && nl[-1] == '\r')
1255 txt->newlines = TEXT_NEWLINE_CRNL;
1256 } else {
1257 char c;
1258 size_t nl = lines_skip_forward(txt, 0, 1, NULL);
1259 if (nl > 1 && text_byte_get(txt, nl-2, &c) && c == '\r')
1260 txt->newlines = TEXT_NEWLINE_CRNL;
1264 return txt->newlines;
1267 static bool text_iterator_init(Iterator *it, size_t pos, Piece *p, size_t off) {
1268 *it = (Iterator){
1269 .pos = pos,
1270 .piece = p,
1271 .start = p ? p->data : NULL,
1272 .end = p ? p->data + p->len : NULL,
1273 .text = p ? p->data + off : NULL,
1275 return text_iterator_valid(it);
1278 Iterator text_iterator_get(Text *txt, size_t pos) {
1279 Iterator it;
1280 Location loc = piece_get_extern(txt, pos);
1281 text_iterator_init(&it, pos, loc.piece, loc.off);
1282 return it;
1285 bool text_iterator_byte_get(Iterator *it, char *b) {
1286 if (text_iterator_valid(it)) {
1287 if (it->start <= it->text && it->text < it->end) {
1288 *b = *it->text;
1289 return true;
1290 } else if (it->pos == it->piece->text->size) { /* EOF */
1291 *b = '\0';
1292 return true;
1295 return false;
1298 bool text_iterator_next(Iterator *it) {
1299 return text_iterator_init(it, it->pos, it->piece ? it->piece->next : NULL, 0);
1302 bool text_iterator_prev(Iterator *it) {
1303 return text_iterator_init(it, it->pos, it->piece ? it->piece->prev : NULL, 0);
1306 bool text_iterator_valid(const Iterator *it) {
1307 /* filter out sentinel nodes */
1308 return it->piece && it->piece->text;
1311 bool text_iterator_byte_next(Iterator *it, char *b) {
1312 if (!text_iterator_valid(it))
1313 return false;
1314 it->text++;
1315 /* special case for advancement to EOF */
1316 if (it->text == it->end && !it->piece->next->text) {
1317 it->pos++;
1318 if (b)
1319 *b = '\0';
1320 return true;
1322 while (it->text >= it->end) {
1323 if (!text_iterator_next(it))
1324 return false;
1325 it->text = it->start;
1327 it->pos++;
1328 if (b)
1329 *b = *it->text;
1330 return true;
1333 bool text_iterator_byte_prev(Iterator *it, char *b) {
1334 if (!text_iterator_valid(it))
1335 return false;
1336 while (it->text == it->start) {
1337 if (!text_iterator_prev(it))
1338 return false;
1339 it->text = it->end;
1341 --it->text;
1342 --it->pos;
1343 if (b)
1344 *b = *it->text;
1345 return true;
1348 bool text_iterator_codepoint_next(Iterator *it, char *c) {
1349 while (text_iterator_byte_next(it, NULL)) {
1350 if (ISUTF8(*it->text)) {
1351 if (c)
1352 *c = *it->text;
1353 return true;
1356 return false;
1359 bool text_iterator_codepoint_prev(Iterator *it, char *c) {
1360 while (text_iterator_byte_prev(it, NULL)) {
1361 if (ISUTF8(*it->text)) {
1362 if (c)
1363 *c = *it->text;
1364 return true;
1367 return false;
1370 bool text_iterator_char_next(Iterator *it, char *c) {
1371 if (!text_iterator_codepoint_next(it, c))
1372 return false;
1373 mbstate_t ps = { 0 };
1374 for (;;) {
1375 char buf[MB_CUR_MAX];
1376 size_t len = text_bytes_get(it->piece->text, it->pos, sizeof buf, buf);
1377 wchar_t wc;
1378 size_t wclen = mbrtowc(&wc, buf, len, &ps);
1379 if (wclen == (size_t)-1 && errno == EILSEQ) {
1380 return true;
1381 } else if (wclen == (size_t)-2) {
1382 return false;
1383 } else if (wclen == 0) {
1384 return true;
1385 } else {
1386 int width = wcwidth(wc);
1387 if (width != 0)
1388 return true;
1389 if (!text_iterator_codepoint_next(it, c))
1390 return false;
1393 return true;
1396 bool text_iterator_char_prev(Iterator *it, char *c) {
1397 if (!text_iterator_codepoint_prev(it, c))
1398 return false;
1399 for (;;) {
1400 char buf[MB_CUR_MAX];
1401 size_t len = text_bytes_get(it->piece->text, it->pos, sizeof buf, buf);
1402 wchar_t wc;
1403 mbstate_t ps = { 0 };
1404 size_t wclen = mbrtowc(&wc, buf, len, &ps);
1405 if (wclen == (size_t)-1 && errno == EILSEQ) {
1406 return true;
1407 } else if (wclen == (size_t)-2) {
1408 return false;
1409 } else if (wclen == 0) {
1410 return true;
1411 } else {
1412 int width = wcwidth(wc);
1413 if (width != 0)
1414 return true;
1415 if (!text_iterator_codepoint_prev(it, c))
1416 return false;
1419 return true;
1422 bool text_byte_get(Text *txt, size_t pos, char *buf) {
1423 return text_bytes_get(txt, pos, 1, buf);
1426 size_t text_bytes_get(Text *txt, size_t pos, size_t len, char *buf) {
1427 if (!buf)
1428 return 0;
1429 char *cur = buf;
1430 size_t rem = len;
1431 text_iterate(txt, it, pos) {
1432 if (rem == 0)
1433 break;
1434 size_t piece_len = it.end - it.text;
1435 if (piece_len > rem)
1436 piece_len = rem;
1437 memcpy(cur, it.text, piece_len);
1438 cur += piece_len;
1439 rem -= piece_len;
1441 return len - rem;
1444 size_t text_size(Text *txt) {
1445 return txt->size;
1448 /* count the number of new lines '\n' in range [pos, pos+len) */
1449 static size_t lines_count(Text *txt, size_t pos, size_t len) {
1450 size_t lines = 0;
1451 text_iterate(txt, it, pos) {
1452 const char *start = it.text;
1453 while (len > 0 && start < it.end) {
1454 size_t n = MIN(len, (size_t)(it.end - start));
1455 const char *end = memchr(start, '\n', n);
1456 if (!end) {
1457 len -= n;
1458 break;
1460 lines++;
1461 len -= end - start + 1;
1462 start = end + 1;
1465 if (len == 0)
1466 break;
1468 return lines;
1471 /* skip n lines forward and return position afterwards */
1472 static size_t lines_skip_forward(Text *txt, size_t pos, size_t lines, size_t *lines_skipped) {
1473 size_t lines_old = lines;
1474 text_iterate(txt, it, pos) {
1475 const char *start = it.text;
1476 while (lines > 0 && start < it.end) {
1477 size_t n = it.end - start;
1478 const char *end = memchr(start, '\n', n);
1479 if (!end) {
1480 pos += n;
1481 break;
1483 pos += end - start + 1;
1484 start = end + 1;
1485 lines--;
1488 if (lines == 0)
1489 break;
1491 if (lines_skipped)
1492 *lines_skipped = lines_old - lines;
1493 return pos;
1496 static void lineno_cache_invalidate(LineCache *cache) {
1497 cache->pos = 0;
1498 cache->lineno = 1;
1501 size_t text_pos_by_lineno(Text *txt, size_t lineno) {
1502 size_t lines_skipped;
1503 LineCache *cache = &txt->lines;
1504 if (lineno <= 1)
1505 return 0;
1506 if (lineno > cache->lineno) {
1507 cache->pos = lines_skip_forward(txt, cache->pos, lineno - cache->lineno, &lines_skipped);
1508 cache->lineno += lines_skipped;
1509 } else if (lineno < cache->lineno) {
1510 #if 0
1511 // TODO does it make sense to scan memory backwards here?
1512 size_t diff = cache->lineno - lineno;
1513 if (diff < lineno) {
1514 lines_skip_backward(txt, cache->pos, diff);
1515 } else
1516 #endif
1517 cache->pos = lines_skip_forward(txt, 0, lineno - 1, &lines_skipped);
1518 cache->lineno = lines_skipped + 1;
1520 return cache->lineno == lineno ? cache->pos : EPOS;
1523 size_t text_lineno_by_pos(Text *txt, size_t pos) {
1524 LineCache *cache = &txt->lines;
1525 if (pos > txt->size)
1526 pos = txt->size;
1527 if (pos < cache->pos) {
1528 size_t diff = cache->pos - pos;
1529 if (diff < pos)
1530 cache->lineno -= lines_count(txt, pos, diff);
1531 else
1532 cache->lineno = lines_count(txt, 0, pos) + 1;
1533 } else if (pos > cache->pos) {
1534 cache->lineno += lines_count(txt, cache->pos, pos - cache->pos);
1536 cache->pos = pos;
1537 return cache->lineno;
1540 Mark text_mark_set(Text *txt, size_t pos) {
1541 if (pos == 0)
1542 return (Mark)&txt->begin;
1543 if (pos == txt->size)
1544 return (Mark)&txt->end;
1545 Location loc = piece_get_extern(txt, pos);
1546 if (!loc.piece)
1547 return NULL;
1548 return loc.piece->data + loc.off;
1551 size_t text_mark_get(Text *txt, Mark mark) {
1552 size_t cur = 0;
1554 if (!mark)
1555 return EPOS;
1556 if (mark == (Mark)&txt->begin)
1557 return 0;
1558 if (mark == (Mark)&txt->end)
1559 return txt->size;
1561 for (Piece *p = txt->begin.next; p->next; p = p->next) {
1562 if (p->data <= mark && mark < p->data + p->len)
1563 return cur + (mark - p->data);
1564 cur += p->len;
1567 return EPOS;
1570 size_t text_history_get(Text *txt, size_t index) {
1571 for (Action *a = txt->current_action ? txt->current_action : txt->history; a; a = a->prev) {
1572 if (index-- == 0) {
1573 Change *c = a->change;
1574 while (c && c->next)
1575 c = c->next;
1576 return c ? c->pos : EPOS;
1579 return EPOS;