12 #include <sys/types.h>
17 #include "text-util.h"
18 #include "text-motions.h"
21 #include "text-internal.h"
23 /* A piece holds a reference (but doesn't itself store) a certain amount of data.
24 * All active pieces chained together form the whole content of the document.
25 * At the beginning there exists only one piece, spanning the whole document.
26 * Upon insertion/deletion new pieces will be created to represent the changes.
27 * Generally pieces are never destroyed, but kept around to peform undo/redo
31 Text
*text
; /* text to which this piece belongs */
32 Piece
*prev
, *next
; /* pointers to the logical predecessor/successor */
33 Piece
*global_prev
; /* double linked list in order of allocation, */
34 Piece
*global_next
; /* used to free individual pieces */
35 const char *data
; /* pointer into a Block holding the data */
36 size_t len
; /* the length in number of bytes of the data */
39 /* used to transform a global position (byte offset starting from the beginning
40 * of the text) into an offset relative to a piece.
43 Piece
*piece
; /* piece holding the location */
44 size_t off
; /* offset into the piece in bytes */
47 /* A Span holds a certain range of pieces. Changes to the document are always
48 * performed by swapping out an existing span with a new one.
51 Piece
*start
, *end
; /* start/end of the span */
52 size_t len
; /* the sum of the lengths of the pieces which form this span */
55 /* A Change keeps all needed information to redo/undo an insertion/deletion. */
56 typedef struct Change Change
;
58 Span old
; /* all pieces which are being modified/swapped out by the change */
59 Span
new; /* all pieces which are introduced/swapped in by the change */
60 size_t pos
; /* absolute position at which the change occured */
61 Change
*next
; /* next change which is part of the same revision */
62 Change
*prev
; /* previous change which is part of the same revision */
65 /* A Revision is a list of Changes which are used to undo/redo all modifications
66 * since the last snapshot operation. Revisions are stored in a directed graph structure.
68 typedef struct Revision Revision
;
70 Change
*change
; /* the most recent change */
71 Revision
*next
; /* the next (child) revision in the undo tree */
72 Revision
*prev
; /* the previous (parent) revision in the undo tree */
73 Revision
*earlier
; /* the previous Revision, chronologically */
74 Revision
*later
; /* the next Revision, chronologically */
75 time_t time
; /* when the first change of this revision was performed */
76 size_t seq
; /* a unique, strictly increasing identifier */
80 size_t pos
; /* position in bytes from start of file */
81 size_t lineno
; /* line number in file i.e. number of '\n' in [0, pos) */
84 /* The main struct holding all information of a given file */
86 Array blocks
; /* blocks which hold text content */
87 Piece
*pieces
; /* all pieces which have been allocated, used to free them */
88 Piece
*cache
; /* most recently modified piece */
89 Piece begin
, end
; /* sentinel nodes which always exists but don't hold any data */
90 Revision
*history
; /* undo tree */
91 Revision
*current_revision
; /* revision holding all file changes until a snapshot is performed */
92 Revision
*last_revision
; /* the last revision added to the tree, chronologically */
93 Revision
*saved_revision
; /* the last revision at the time of the save operation */
94 size_t size
; /* current file content size in bytes */
95 struct stat info
; /* stat as probed at load time */
96 LineCache lines
; /* mapping between absolute pos in bytes and logical line breaks */
99 /* block management */
100 static const char *block_store(Text
*, const char *data
, size_t len
);
102 static void cache_piece(Text
*txt
, Piece
*p
);
103 static bool cache_contains(Text
*txt
, Piece
*p
);
104 static bool cache_insert(Text
*txt
, Piece
*p
, size_t off
, const char *data
, size_t len
);
105 static bool cache_delete(Text
*txt
, Piece
*p
, size_t off
, size_t len
);
106 /* piece management */
107 static Piece
*piece_alloc(Text
*txt
);
108 static void piece_free(Piece
*p
);
109 static void piece_init(Piece
*p
, Piece
*prev
, Piece
*next
, const char *data
, size_t len
);
110 static Location
piece_get_intern(Text
*txt
, size_t pos
);
111 static Location
piece_get_extern(const Text
*txt
, size_t pos
);
112 /* span management */
113 static void span_init(Span
*span
, Piece
*start
, Piece
*end
);
114 static void span_swap(Text
*txt
, Span
*old
, Span
*new);
115 /* change management */
116 static Change
*change_alloc(Text
*txt
, size_t pos
);
117 static void change_free(Change
*c
);
118 /* revision management */
119 static Revision
*revision_alloc(Text
*txt
);
120 static void revision_free(Revision
*rev
);
121 /* logical line counting cache */
122 static void lineno_cache_invalidate(LineCache
*cache
);
123 static size_t lines_skip_forward(Text
*txt
, size_t pos
, size_t lines
, size_t *lines_skiped
);
124 static size_t lines_count(Text
*txt
, size_t pos
, size_t len
);
126 /* stores the given data in a block, allocates a new one if necessary. returns
127 * a pointer to the storage location or NULL if allocation failed. */
128 static const char *block_store(Text
*txt
, const char *data
, size_t len
) {
129 Block
*blk
= array_get_ptr(&txt
->blocks
, array_length(&txt
->blocks
)-1);
130 if (!blk
|| !block_capacity(blk
, len
)) {
131 blk
= block_alloc(len
);
134 if (!array_add_ptr(&txt
->blocks
, blk
)) {
139 return block_append(blk
, data
, len
);
142 /* cache the given piece if it is the most recently changed one */
143 static void cache_piece(Text
*txt
, Piece
*p
) {
144 Block
*blk
= array_get_ptr(&txt
->blocks
, array_length(&txt
->blocks
)-1);
145 if (!blk
|| p
->data
< blk
->data
|| p
->data
+ p
->len
!= blk
->data
+ blk
->len
)
150 /* check whether the given piece was the most recently modified one */
151 static bool cache_contains(Text
*txt
, Piece
*p
) {
152 Block
*blk
= array_get_ptr(&txt
->blocks
, array_length(&txt
->blocks
)-1);
153 Revision
*rev
= txt
->current_revision
;
154 if (!blk
|| !txt
->cache
|| txt
->cache
!= p
|| !rev
|| !rev
->change
)
157 Piece
*start
= rev
->change
->new.start
;
158 Piece
*end
= rev
->change
->new.end
;
160 for (Piece
*cur
= start
; !found
; cur
= cur
->next
) {
167 return found
&& p
->data
+ p
->len
== blk
->data
+ blk
->len
;
170 /* try to insert a chunk of data at a given piece offset. the insertion is only
171 * performed if the piece is the most recenetly changed one. the legnth of the
172 * piece, the span containing it and the whole text is adjusted accordingly */
173 static bool cache_insert(Text
*txt
, Piece
*p
, size_t off
, const char *data
, size_t len
) {
174 if (!cache_contains(txt
, p
))
176 Block
*blk
= array_get_ptr(&txt
->blocks
, array_length(&txt
->blocks
)-1);
177 size_t bufpos
= p
->data
+ off
- blk
->data
;
178 if (!block_insert(blk
, bufpos
, data
, len
))
181 txt
->current_revision
->change
->new.len
+= len
;
186 /* try to delete a chunk of data at a given piece offset. the deletion is only
187 * performed if the piece is the most recenetly changed one and the whole
188 * affected range lies within it. the legnth of the piece, the span containing it
189 * and the whole text is adjusted accordingly */
190 static bool cache_delete(Text
*txt
, Piece
*p
, size_t off
, size_t len
) {
191 if (!cache_contains(txt
, p
))
193 Block
*blk
= array_get_ptr(&txt
->blocks
, array_length(&txt
->blocks
)-1);
195 size_t bufpos
= p
->data
+ off
- blk
->data
;
196 if (!addu(off
, len
, &end
) || end
> p
->len
|| !block_delete(blk
, bufpos
, len
))
199 txt
->current_revision
->change
->new.len
-= len
;
204 /* initialize a span and calculate its length */
205 static void span_init(Span
*span
, Piece
*start
, Piece
*end
) {
209 for (Piece
*p
= start
; p
; p
= p
->next
) {
217 /* swap out an old span and replace it with a new one.
219 * - if old is an empty span do not remove anything, just insert the new one
220 * - if new is an empty span do not insert anything, just remove the old one
222 * adjusts the document size accordingly.
224 static void span_swap(Text
*txt
, Span
*old
, Span
*new) {
225 if (old
->len
== 0 && new->len
== 0) {
227 } else if (old
->len
== 0) {
228 /* insert new span */
229 new->start
->prev
->next
= new->start
;
230 new->end
->next
->prev
= new->end
;
231 } else if (new->len
== 0) {
232 /* delete old span */
233 old
->start
->prev
->next
= old
->end
->next
;
234 old
->end
->next
->prev
= old
->start
->prev
;
236 /* replace old with new */
237 old
->start
->prev
->next
= new->start
;
238 old
->end
->next
->prev
= new->end
;
240 txt
->size
-= old
->len
;
241 txt
->size
+= new->len
;
244 /* Allocate a new revision and place it in the revision graph.
245 * All further changes will be associated with this revision. */
246 static Revision
*revision_alloc(Text
*txt
) {
247 Revision
*rev
= calloc(1, sizeof *rev
);
250 rev
->time
= time(NULL
);
251 txt
->current_revision
= rev
;
253 /* set sequence number */
254 if (!txt
->last_revision
)
257 rev
->seq
= txt
->last_revision
->seq
+ 1;
259 /* set earlier, later pointers */
260 if (txt
->last_revision
)
261 txt
->last_revision
->later
= rev
;
262 rev
->earlier
= txt
->last_revision
;
269 /* set prev, next pointers */
270 rev
->prev
= txt
->history
;
271 txt
->history
->next
= rev
;
276 static void revision_free(Revision
*rev
) {
279 for (Change
*next
, *c
= rev
->change
; c
; c
= next
) {
286 static Piece
*piece_alloc(Text
*txt
) {
287 Piece
*p
= calloc(1, sizeof *p
);
291 p
->global_next
= txt
->pieces
;
293 txt
->pieces
->global_prev
= p
;
298 static void piece_free(Piece
*p
) {
302 p
->global_prev
->global_next
= p
->global_next
;
304 p
->global_next
->global_prev
= p
->global_prev
;
305 if (p
->text
->pieces
== p
)
306 p
->text
->pieces
= p
->global_next
;
307 if (p
->text
->cache
== p
)
308 p
->text
->cache
= NULL
;
312 static void piece_init(Piece
*p
, Piece
*prev
, Piece
*next
, const char *data
, size_t len
) {
319 /* returns the piece holding the text at byte offset pos. if pos happens to
320 * be at a piece boundry i.e. the first byte of a piece then the previous piece
321 * to the left is returned with an offset of piece->len. this is convenient for
322 * modifications to the piece chain where both pieces (the returned one and the
323 * one following it) are needed, but unsuitable as a public interface.
325 * in particular if pos is zero, the begin sentinel piece is returned.
327 static Location
piece_get_intern(Text
*txt
, size_t pos
) {
329 for (Piece
*p
= &txt
->begin
; p
->next
; p
= p
->next
) {
330 if (cur
<= pos
&& pos
<= cur
+ p
->len
)
331 return (Location
){ .piece
= p
, .off
= pos
- cur
};
335 return (Location
){ 0 };
338 /* similiar to piece_get_intern but usable as a public API. returns the piece
339 * holding the text at byte offset pos. never returns a sentinel piece.
340 * it pos is the end of file (== text_size()) and the file is not empty then
341 * the last piece holding data is returned.
343 static Location
piece_get_extern(const Text
*txt
, size_t pos
) {
347 for (p
= txt
->begin
.next
; p
->next
; p
= p
->next
) {
348 if (cur
<= pos
&& pos
< cur
+ p
->len
)
349 return (Location
){ .piece
= p
, .off
= pos
- cur
};
354 return (Location
){ .piece
= p
->prev
, .off
= p
->prev
->len
};
356 return (Location
){ 0 };
359 /* allocate a new change, associate it with current revision or a newly
360 * allocated one if none exists. */
361 static Change
*change_alloc(Text
*txt
, size_t pos
) {
362 Revision
*rev
= txt
->current_revision
;
364 rev
= revision_alloc(txt
);
368 Change
*c
= calloc(1, sizeof *c
);
372 c
->next
= rev
->change
;
374 rev
->change
->prev
= c
;
379 static void change_free(Change
*c
) {
382 /* only free the new part of the span, the old one is still in use */
383 if (c
->new.start
!= c
->new.end
)
384 piece_free(c
->new.end
);
385 piece_free(c
->new.start
);
389 /* When inserting new data there are 2 cases to consider.
391 * - in the first the insertion point falls into the middle of an exisiting
392 * piece which is replaced by three new pieces:
394 * /-+ --> +---------------+ --> +-\
395 * | | | existing text | | |
396 * \-+ <-- +---------------+ <-- +-/
398 * Insertion point for "demo "
400 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
401 * | | | existing| |demo | |text | | |
402 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
404 * - the second case deals with an insertion point at a piece boundry:
406 * /-+ --> +---------------+ --> +-\
407 * | | | existing text | | |
408 * \-+ <-- +---------------+ <-- +-/
410 * Insertion point for "short"
412 * /-+ --> +-----+ --> +---------------+ --> +-\
413 * | | |short| | existing text | | |
414 * \-+ <-- +-----+ <-- +---------------+ <-- +-/
416 bool text_insert(Text
*txt
, size_t pos
, const char *data
, size_t len
) {
421 if (pos
< txt
->lines
.pos
)
422 lineno_cache_invalidate(&txt
->lines
);
424 Location loc
= piece_get_intern(txt
, pos
);
425 Piece
*p
= loc
.piece
;
428 size_t off
= loc
.off
;
429 if (cache_insert(txt
, p
, off
, data
, len
))
432 Change
*c
= change_alloc(txt
, pos
);
436 if (!(data
= block_store(txt
, data
, len
)))
442 /* insert between two existing pieces, hence there is nothing to
443 * remove, just add a new piece holding the extra text */
444 if (!(new = piece_alloc(txt
)))
446 piece_init(new, p
, p
->next
, data
, len
);
447 span_init(&c
->new, new, new);
448 span_init(&c
->old
, NULL
, NULL
);
450 /* insert into middle of an existing piece, therfore split the old
451 * piece. that is we have 3 new pieces one containing the content
452 * before the insertion point then one holding the newly inserted
453 * text and one holding the content after the insertion point.
455 Piece
*before
= piece_alloc(txt
);
456 new = piece_alloc(txt
);
457 Piece
*after
= piece_alloc(txt
);
458 if (!before
|| !new || !after
)
460 piece_init(before
, p
->prev
, new, p
->data
, off
);
461 piece_init(new, before
, after
, data
, len
);
462 piece_init(after
, new, p
->next
, p
->data
+ off
, p
->len
- off
);
464 span_init(&c
->new, before
, after
);
465 span_init(&c
->old
, p
, p
);
468 cache_piece(txt
, new);
469 span_swap(txt
, &c
->old
, &c
->new);
473 static size_t revision_undo(Text
*txt
, Revision
*rev
) {
475 for (Change
*c
= rev
->change
; c
; c
= c
->next
) {
476 span_swap(txt
, &c
->new, &c
->old
);
482 static size_t revision_redo(Text
*txt
, Revision
*rev
) {
484 Change
*c
= rev
->change
;
487 for ( ; c
; c
= c
->prev
) {
488 span_swap(txt
, &c
->old
, &c
->new);
490 if (c
->new.len
> c
->old
.len
)
491 pos
+= c
->new.len
- c
->old
.len
;
496 size_t text_undo(Text
*txt
) {
498 /* taking rev snapshot makes sure that txt->current_revision is reset */
500 Revision
*rev
= txt
->history
->prev
;
503 pos
= revision_undo(txt
, txt
->history
);
505 lineno_cache_invalidate(&txt
->lines
);
509 size_t text_redo(Text
*txt
) {
511 /* taking a snapshot makes sure that txt->current_revision is reset */
513 Revision
*rev
= txt
->history
->next
;
516 pos
= revision_redo(txt
, rev
);
518 lineno_cache_invalidate(&txt
->lines
);
522 static bool history_change_branch(Revision
*rev
) {
523 bool changed
= false;
525 if (rev
->prev
->next
!= rev
) {
526 rev
->prev
->next
= rev
;
534 static size_t history_traverse_to(Text
*txt
, Revision
*rev
) {
538 bool changed
= history_change_branch(rev
);
540 if (rev
->seq
== txt
->history
->seq
) {
541 return txt
->lines
.pos
;
542 } else if (rev
->seq
> txt
->history
->seq
) {
543 while (txt
->history
!= rev
)
544 pos
= text_redo(txt
);
546 } else if (rev
->seq
< txt
->history
->seq
) {
547 while (txt
->history
!= rev
)
548 pos
= text_undo(txt
);
552 while (txt
->history
->prev
&& txt
->history
->prev
->next
== txt
->history
)
554 pos
= text_undo(txt
);
555 while (txt
->history
!= rev
)
556 pos
= text_redo(txt
);
562 size_t text_earlier(Text
*txt
) {
563 return history_traverse_to(txt
, txt
->history
->earlier
);
566 size_t text_later(Text
*txt
) {
567 return history_traverse_to(txt
, txt
->history
->later
);
570 size_t text_restore(Text
*txt
, time_t time
) {
571 Revision
*rev
= txt
->history
;
572 while (time
< rev
->time
&& rev
->earlier
)
574 while (time
> rev
->time
&& rev
->later
)
576 time_t diff
= labs(rev
->time
- time
);
577 if (rev
->earlier
&& rev
->earlier
!= txt
->history
&& labs(rev
->earlier
->time
- time
) < diff
)
579 if (rev
->later
&& rev
->later
!= txt
->history
&& labs(rev
->later
->time
- time
) < diff
)
581 return history_traverse_to(txt
, rev
);
584 time_t text_state(const Text
*txt
) {
585 return txt
->history
->time
;
588 Text
*text_loadat_method(int dirfd
, const char *filename
, enum TextLoadMethod method
) {
589 Text
*txt
= calloc(1, sizeof *txt
);
592 Piece
*p
= piece_alloc(txt
);
596 array_init(&txt
->blocks
);
597 lineno_cache_invalidate(&txt
->lines
);
600 block
= block_load(dirfd
, filename
, method
, &txt
->info
);
603 if (block
&& !array_add_ptr(&txt
->blocks
, block
)) {
610 piece_init(p
, &txt
->begin
, &txt
->end
, "\0", 0);
612 piece_init(p
, &txt
->begin
, &txt
->end
, block
->data
, block
->len
);
614 piece_init(&txt
->begin
, NULL
, p
, NULL
, 0);
615 piece_init(&txt
->end
, p
, NULL
, NULL
, 0);
617 /* write an empty revision */
618 change_alloc(txt
, EPOS
);
620 txt
->saved_revision
= txt
->history
;
628 struct stat
text_stat(const Text
*txt
) {
632 void text_saved(Text
*txt
, struct stat
*meta
) {
635 txt
->saved_revision
= txt
->history
;
639 Block
*text_block_mmaped(Text
*txt
) {
640 Block
*block
= array_get_ptr(&txt
->blocks
, 0);
641 if (block
&& block
->type
== BLOCK_TYPE_MMAP_ORIG
&& block
->size
)
646 /* A delete operation can either start/stop midway through a piece or at
647 * a boundry. In the former case a new piece is created to represent the
648 * remaining text before/after the modification point.
650 * /-+ --> +---------+ --> +-----+ --> +-----+ --> +-\
651 * | | | existing| |demo | |text | | |
652 * \-+ <-- +---------+ <-- +-----+ <-- +-----+ <-- +-/
654 * |------ delete range -----|
656 * /-+ --> +----+ --> +--+ --> +-\
657 * | | | exi| |t | | |
658 * \-+ <-- +----+ <-- +--+ <-- +-/
660 bool text_delete(Text
*txt
, size_t pos
, size_t len
) {
664 if (!addu(pos
, len
, &pos_end
) || pos_end
> txt
->size
)
666 if (pos
< txt
->lines
.pos
)
667 lineno_cache_invalidate(&txt
->lines
);
669 Location loc
= piece_get_intern(txt
, pos
);
670 Piece
*p
= loc
.piece
;
673 size_t off
= loc
.off
;
674 if (cache_delete(txt
, p
, off
, len
))
676 Change
*c
= change_alloc(txt
, pos
);
680 bool midway_start
= false, midway_end
= false; /* split pieces? */
681 Piece
*before
, *after
; /* unmodified pieces before/after deletion point */
682 Piece
*start
, *end
; /* span which is removed */
683 size_t cur
; /* how much has already been deleted */
686 /* deletion starts at a piece boundry */
691 /* deletion starts midway through a piece */
695 before
= piece_alloc(txt
);
700 /* skip all pieces which fall into deletion range */
707 /* deletion stops at a piece boundry */
711 /* cur > len: deletion stops midway through a piece */
714 after
= piece_alloc(txt
);
717 piece_init(after
, before
, p
->next
, p
->data
+ p
->len
- (cur
- len
), cur
- len
);
721 /* we finally know which piece follows our newly allocated before piece */
722 piece_init(before
, start
->prev
, after
, start
->data
, off
);
725 Piece
*new_start
= NULL
, *new_end
= NULL
;
737 span_init(&c
->new, new_start
, new_end
);
738 span_init(&c
->old
, start
, end
);
739 span_swap(txt
, &c
->old
, &c
->new);
743 bool text_delete_range(Text
*txt
, const Filerange
*r
) {
744 if (!text_range_valid(r
))
746 return text_delete(txt
, r
->start
, text_range_size(r
));
749 /* preserve the current text content such that it can be restored by
750 * means of undo/redo operations */
751 bool text_snapshot(Text
*txt
) {
752 if (txt
->current_revision
)
753 txt
->last_revision
= txt
->current_revision
;
754 txt
->current_revision
= NULL
;
760 void text_free(Text
*txt
) {
765 Revision
*hist
= txt
->history
;
766 while (hist
&& hist
->prev
)
769 Revision
*later
= hist
->later
;
774 for (Piece
*next
, *p
= txt
->pieces
; p
; p
= next
) {
775 next
= p
->global_next
;
779 for (size_t i
= 0, len
= array_length(&txt
->blocks
); i
< len
; i
++)
780 block_free(array_get_ptr(&txt
->blocks
, i
));
781 array_release(&txt
->blocks
);
786 bool text_modified(const Text
*txt
) {
787 return txt
->saved_revision
!= txt
->history
;
790 bool text_mmaped(const Text
*txt
, const char *ptr
) {
791 uintptr_t addr
= (uintptr_t)ptr
;
792 for (size_t i
= 0, len
= array_length(&txt
->blocks
); i
< len
; i
++) {
793 Block
*blk
= array_get_ptr(&txt
->blocks
, i
);
794 if ((blk
->type
== BLOCK_TYPE_MMAP_ORIG
|| blk
->type
== BLOCK_TYPE_MMAP
) &&
795 (uintptr_t)(blk
->data
) <= addr
&& addr
< (uintptr_t)(blk
->data
+ blk
->size
))
801 static bool iterator_init(Iterator
*it
, size_t pos
, Piece
*p
, size_t off
) {
805 .start
= p
? p
->data
: NULL
,
806 .end
= p
&& p
->data
? p
->data
+ p
->len
: NULL
,
807 .text
= p
&& p
->data
? p
->data
+ off
: NULL
,
809 return text_iterator_valid(it
);
812 bool text_iterator_init(const Text
*txt
, Iterator
*it
, size_t pos
) {
813 Location loc
= piece_get_extern(txt
, pos
);
814 return iterator_init(it
, pos
, loc
.piece
, loc
.off
);
817 Iterator
text_iterator_get(const Text
*txt
, size_t pos
) {
819 text_iterator_init(txt
, &it
, pos
);
823 bool text_iterator_next(Iterator
*it
) {
824 size_t rem
= it
->end
- it
->text
;
825 return iterator_init(it
, it
->pos
+rem
, it
->piece
? it
->piece
->next
: NULL
, 0);
828 bool text_iterator_prev(Iterator
*it
) {
829 size_t off
= it
->text
- it
->start
;
830 size_t len
= it
->piece
&& it
->piece
->prev
? it
->piece
->prev
->len
: 0;
831 return iterator_init(it
, it
->pos
-off
, it
->piece
? it
->piece
->prev
: NULL
, len
);
834 const Text
*text_iterator_text(const Iterator
*it
) {
835 return it
->piece
? it
->piece
->text
: NULL
;
838 bool text_iterator_valid(const Iterator
*it
) {
839 /* filter out sentinel nodes */
840 return it
->piece
&& it
->piece
->text
;
843 bool text_iterator_has_next(const Iterator
*it
) {
844 return it
->piece
&& it
->piece
->next
;
847 bool text_iterator_has_prev(const Iterator
*it
) {
848 return it
->piece
&& it
->piece
->prev
;
851 size_t text_size(const Text
*txt
) {
855 /* count the number of new lines '\n' in range [pos, pos+len) */
856 static size_t lines_count(Text
*txt
, size_t pos
, size_t len
) {
858 for (Iterator it
= text_iterator_get(txt
, pos
);
859 text_iterator_valid(&it
);
860 text_iterator_next(&it
)) {
861 const char *start
= it
.text
;
862 while (len
> 0 && start
< it
.end
) {
863 size_t n
= MIN(len
, (size_t)(it
.end
- start
));
864 const char *end
= memchr(start
, '\n', n
);
870 len
-= end
- start
+ 1;
880 /* skip n lines forward and return position afterwards */
881 static size_t lines_skip_forward(Text
*txt
, size_t pos
, size_t lines
, size_t *lines_skipped
) {
882 size_t lines_old
= lines
;
883 for (Iterator it
= text_iterator_get(txt
, pos
);
884 text_iterator_valid(&it
);
885 text_iterator_next(&it
)) {
886 const char *start
= it
.text
;
887 while (lines
> 0 && start
< it
.end
) {
888 size_t n
= it
.end
- start
;
889 const char *end
= memchr(start
, '\n', n
);
894 pos
+= end
- start
+ 1;
903 *lines_skipped
= lines_old
- lines
;
907 static void lineno_cache_invalidate(LineCache
*cache
) {
912 size_t text_pos_by_lineno(Text
*txt
, size_t lineno
) {
913 size_t lines_skipped
;
914 LineCache
*cache
= &txt
->lines
;
917 if (lineno
> cache
->lineno
) {
918 cache
->pos
= lines_skip_forward(txt
, cache
->pos
, lineno
- cache
->lineno
, &lines_skipped
);
919 cache
->lineno
+= lines_skipped
;
920 } else if (lineno
< cache
->lineno
) {
922 // TODO does it make sense to scan memory backwards here?
923 size_t diff
= cache
->lineno
- lineno
;
925 lines_skip_backward(txt
, cache
->pos
, diff
);
928 cache
->pos
= lines_skip_forward(txt
, 0, lineno
- 1, &lines_skipped
);
929 cache
->lineno
= lines_skipped
+ 1;
931 return cache
->lineno
== lineno
? cache
->pos
: EPOS
;
934 size_t text_lineno_by_pos(Text
*txt
, size_t pos
) {
935 LineCache
*cache
= &txt
->lines
;
938 if (pos
< cache
->pos
) {
939 size_t diff
= cache
->pos
- pos
;
941 cache
->lineno
-= lines_count(txt
, pos
, diff
);
943 cache
->lineno
= lines_count(txt
, 0, pos
) + 1;
944 } else if (pos
> cache
->pos
) {
945 cache
->lineno
+= lines_count(txt
, cache
->pos
, pos
- cache
->pos
);
947 cache
->pos
= text_line_begin(txt
, pos
);
948 return cache
->lineno
;
951 Mark
text_mark_set(Text
*txt
, size_t pos
) {
952 if (pos
== txt
->size
)
953 return (Mark
)&txt
->end
;
954 Location loc
= piece_get_extern(txt
, pos
);
957 return (Mark
)(loc
.piece
->data
+ loc
.off
);
960 size_t text_mark_get(const Text
*txt
, Mark mark
) {
965 if (mark
== (Mark
)&txt
->end
)
968 for (Piece
*p
= txt
->begin
.next
; p
->next
; p
= p
->next
) {
969 Mark start
= (Mark
)(p
->data
);
970 Mark end
= start
+ p
->len
;
971 if (start
<= mark
&& mark
< end
)
972 return cur
+ (mark
- start
);