Line | Branch | Exec | Source |
---|---|---|---|
1 | #include <string.h> | ||
2 | #include "edit.h" | ||
3 | #include "block.h" | ||
4 | #include "buffer.h" | ||
5 | #include "syntax/highlight.h" | ||
6 | #include "util/debug.h" | ||
7 | #include "util/list.h" | ||
8 | #include "util/xmalloc.h" | ||
9 | |||
10 | enum { | ||
11 | BLOCK_EDIT_SIZE = 512 | ||
12 | }; | ||
13 | |||
14 | 291 | static void sanity_check_blocks(const View *view, bool check_newlines) | |
15 | { | ||
16 | 291 | if (DEBUG < 1) { | |
17 | return; | ||
18 | } | ||
19 | |||
20 | 291 | const Buffer *buffer = view->buffer; | |
21 | 291 | BUG_ON(list_empty(&buffer->blocks)); | |
22 | 291 | BUG_ON(view->cursor.offset > view->cursor.blk->size); | |
23 | |||
24 | 291 | const Block *blk = BLOCK(buffer->blocks.next); | |
25 |
2/2✓ Branch 0 taken 6 times.
✓ Branch 1 taken 285 times.
|
291 | if (blk->size == 0) { |
26 | // The only time a zero-sized block is valid is when it's the | ||
27 | // first and only block | ||
28 | 6 | BUG_ON(buffer->blocks.next->next != &buffer->blocks); | |
29 | 6 | BUG_ON(view->cursor.blk != blk); | |
30 | return; | ||
31 | } | ||
32 | |||
33 | bool cursor_seen = false; | ||
34 |
2/2✓ Branch 0 taken 289 times.
✓ Branch 1 taken 285 times.
|
574 | block_for_each(blk, &buffer->blocks) { |
35 | 289 | const size_t size = blk->size; | |
36 | 289 | BUG_ON(size == 0); | |
37 | 289 | BUG_ON(size > blk->alloc); | |
38 |
2/2✓ Branch 0 taken 285 times.
✓ Branch 1 taken 4 times.
|
289 | if (blk == view->cursor.blk) { |
39 | 285 | cursor_seen = true; | |
40 | } | ||
41 |
1/2✓ Branch 0 taken 289 times.
✗ Branch 1 not taken.
|
289 | if (check_newlines) { |
42 | // Non-empty blocks must ALWAYS end with a newline, since | ||
43 | // lines MAY NOT straddle multiple blocks | ||
44 | 289 | BUG_ON(blk->data[size - 1] != '\n'); | |
45 | } | ||
46 | 289 | if (DEBUG > 2) { | |
47 | 289 | BUG_ON(count_nl(blk->data, size) != blk->nl); | |
48 | } | ||
49 | } | ||
50 | |||
51 | 285 | BUG_ON(!cursor_seen); | |
52 | } | ||
53 | |||
54 | 326 | static size_t copy_count_nl(char *dst, const char *src, size_t len) | |
55 | { | ||
56 | 326 | size_t nl = 0; | |
57 |
2/2✓ Branch 0 taken 19375 times.
✓ Branch 1 taken 326 times.
|
19701 | for (size_t i = 0; i < len; i++) { |
58 | 19375 | dst[i] = src[i]; | |
59 |
2/2✓ Branch 0 taken 152 times.
✓ Branch 1 taken 19223 times.
|
19375 | if (src[i] == '\n') { |
60 | 152 | nl++; | |
61 | } | ||
62 | } | ||
63 | 326 | return nl; | |
64 | } | ||
65 | |||
66 | 191 | static size_t insert_to_current(BlockIter *cursor, const char *buf, size_t len) | |
67 | { | ||
68 | 191 | Block *blk = cursor->blk; | |
69 | 191 | size_t offset = cursor->offset; | |
70 | 191 | size_t size = blk->size + len; | |
71 | 191 | block_grow(blk, size); | |
72 | 191 | memmove(blk->data + offset + len, blk->data + offset, blk->size - offset); | |
73 | 191 | size_t nl = copy_count_nl(blk->data + offset, buf, len); | |
74 | 191 | blk->nl += nl; | |
75 | 191 | blk->size = size; | |
76 | 191 | return nl; | |
77 | } | ||
78 | |||
79 | /* | ||
80 | * Combine current block and new data into smaller blocks: | ||
81 | * | ||
82 | * • Block _must_ contain whole lines | ||
83 | * • Block _must_ contain at least one line | ||
84 | * • Preferred maximum size of block is BLOCK_EDIT_SIZE | ||
85 | * • Size of any block can be larger than BLOCK_EDIT_SIZE only if there's | ||
86 | * a very long line | ||
87 | */ | ||
88 | 2 | static size_t split_and_insert(BlockIter *cursor, const char *buf, size_t len) | |
89 | { | ||
90 | 2 | Block *blk = cursor->blk; | |
91 | 2 | ListHead *prev_node = blk->node.prev; | |
92 | 2 | const char *buf1 = blk->data; | |
93 | 2 | const char *buf2 = buf; | |
94 | 2 | const char *buf3 = blk->data + cursor->offset; | |
95 | 2 | size_t size1 = cursor->offset; | |
96 | 2 | size_t size2 = len; | |
97 | 2 | size_t size3 = blk->size - size1; | |
98 | 2 | size_t total = size1 + size2 + size3; | |
99 | 2 | size_t start = 0; // Beginning of new block | |
100 | 2 | size_t size = 0; // Size of new block | |
101 | 2 | size_t pos = 0; // Current position | |
102 | 2 | size_t nl_added = 0; | |
103 | |||
104 |
2/2✓ Branch 0 taken 28 times.
✓ Branch 1 taken 2 times.
|
30 | while (start < total) { |
105 | // Size of new block if next line would be added | ||
106 | 28 | size_t new_size = 0; | |
107 | 28 | size_t copied = 0; | |
108 | |||
109 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 23 times.
|
28 | if (pos < size1) { |
110 | 5 | const char *nl = memchr(buf1 + pos, '\n', size1 - pos); | |
111 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | if (nl) { |
112 | 4 | new_size = nl - buf1 + 1 - start; | |
113 | } | ||
114 | } | ||
115 | |||
116 |
3/4✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 20 times.
✓ Branch 3 taken 4 times.
|
28 | if (!new_size && pos < size1 + size2) { |
117 | 20 | size_t offset = 0; | |
118 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 2 times.
|
20 | if (pos > size1) { |
119 | 18 | offset = pos - size1; | |
120 | } | ||
121 | |||
122 | 20 | const char *nl = memchr(buf2 + offset, '\n', size2 - offset); | |
123 |
1/2✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
|
20 | if (nl) { |
124 | 20 | new_size = size1 + nl - buf2 + 1 - start; | |
125 | } | ||
126 | } | ||
127 | |||
128 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 24 times.
|
28 | if (!new_size && pos < total) { |
129 | 4 | size_t offset = 0; | |
130 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | if (pos > size1 + size2) { |
131 | 2 | offset = pos - size1 - size2; | |
132 | } | ||
133 | |||
134 | 4 | const char *nl = memchr(buf3 + offset, '\n', size3 - offset); | |
135 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | if (nl) { |
136 | 4 | new_size = size1 + size2 + nl - buf3 + 1 - start; | |
137 | } else { | ||
138 | ✗ | new_size = total - start; | |
139 | } | ||
140 | } | ||
141 | |||
142 |
2/2✓ Branch 0 taken 25 times.
✓ Branch 1 taken 3 times.
|
28 | if (new_size <= BLOCK_EDIT_SIZE) { |
143 | // Fits | ||
144 | 25 | size = new_size; | |
145 | 25 | pos = start + new_size; | |
146 |
2/2✓ Branch 0 taken 23 times.
✓ Branch 1 taken 2 times.
|
25 | if (pos < total) { |
147 | 23 | continue; | |
148 | } | ||
149 | } else { | ||
150 | // Does not fit | ||
151 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
|
3 | if (!size) { |
152 | // One block containing one very long line | ||
153 | ✗ | size = new_size; | |
154 | ✗ | pos = start + new_size; | |
155 | } | ||
156 | } | ||
157 | |||
158 | 2 | BUG_ON(!size); | |
159 | 5 | Block *new = block_new(size); | |
160 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 4 times.
|
5 | if (start < size1) { |
161 | 1 | size_t avail = size1 - start; | |
162 | 1 | size_t count = MIN(size, avail); | |
163 | 1 | new->nl += copy_count_nl(new->data, buf1 + start, count); | |
164 | 1 | copied += count; | |
165 | 1 | start += count; | |
166 | } | ||
167 |
3/4✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 1 times.
|
5 | if (start >= size1 && start < size1 + size2) { |
168 | 4 | size_t offset = start - size1; | |
169 | 4 | size_t avail = size2 - offset; | |
170 | 4 | size_t count = MIN(size - copied, avail); | |
171 | 4 | new->nl += copy_count_nl(new->data + copied, buf2 + offset, count); | |
172 | 4 | copied += count; | |
173 | 4 | start += count; | |
174 | } | ||
175 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
|
5 | if (start >= size1 + size2) { |
176 | 3 | size_t offset = start - size1 - size2; | |
177 | 3 | size_t avail = size3 - offset; | |
178 | 3 | size_t count = size - copied; | |
179 | 3 | BUG_ON(count > avail); | |
180 | 3 | new->nl += copy_count_nl(new->data + copied, buf3 + offset, count); | |
181 | 3 | copied += count; | |
182 | 3 | start += count; | |
183 | } | ||
184 | |||
185 | 5 | new->size = size; | |
186 | 5 | BUG_ON(copied != size); | |
187 | 5 | list_insert_before(&new->node, &blk->node); | |
188 | |||
189 | 5 | nl_added += new->nl; | |
190 | 5 | size = 0; | |
191 | } | ||
192 | |||
193 | 2 | cursor->blk = BLOCK(prev_node->next); | |
194 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
|
2 | while (cursor->offset > cursor->blk->size) { |
195 | ✗ | cursor->offset -= cursor->blk->size; | |
196 | ✗ | cursor->blk = BLOCK(cursor->blk->node.next); | |
197 | } | ||
198 | |||
199 | 2 | nl_added -= blk->nl; | |
200 | 2 | block_free(blk); | |
201 | 2 | return nl_added; | |
202 | } | ||
203 | |||
204 | 193 | static size_t insert_bytes(BlockIter *cursor, const char *buf, size_t len) | |
205 | { | ||
206 | // Blocks must contain whole lines. | ||
207 | // Last char of buf might not be newline. | ||
208 | 193 | block_iter_normalize(cursor); | |
209 | |||
210 | 193 | Block *blk = cursor->blk; | |
211 | 193 | size_t new_size = blk->size + len; | |
212 |
4/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 187 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
|
193 | if (new_size <= blk->alloc || new_size <= BLOCK_EDIT_SIZE) { |
213 | 190 | return insert_to_current(cursor, buf, len); | |
214 | } | ||
215 | |||
216 |
4/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
|
3 | if (blk->nl <= 1 && !memchr(buf, '\n', len)) { |
217 | // Can't split this possibly very long line. | ||
218 | // insert_to_current() is much faster than split_and_insert(). | ||
219 | 1 | return insert_to_current(cursor, buf, len); | |
220 | } | ||
221 | 2 | return split_and_insert(cursor, buf, len); | |
222 | } | ||
223 | |||
224 | 193 | void do_insert(View *view, const char *buf, size_t len) | |
225 | { | ||
226 | 193 | Buffer *buffer = view->buffer; | |
227 | 193 | size_t nl = insert_bytes(&view->cursor, buf, len); | |
228 | 193 | buffer->nl += nl; | |
229 | 193 | sanity_check_blocks(view, true); | |
230 | |||
231 | 193 | view_update_cursor_y(view); | |
232 |
2/2✓ Branch 0 taken 120 times.
✓ Branch 1 taken 73 times.
|
193 | buffer_mark_lines_changed(buffer, view->cy, nl ? LONG_MAX : view->cy); |
233 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 192 times.
|
193 | if (buffer->syntax) { |
234 | 1 | hl_insert(&buffer->line_start_states, view->cy, nl); | |
235 | } | ||
236 | 193 | } | |
237 | |||
238 | 6 | static bool only_block(const Buffer *buffer, const Block *blk) | |
239 | { | ||
240 |
2/4✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
6 | return blk->node.prev == &buffer->blocks && blk->node.next == &buffer->blocks; |
241 | } | ||
242 | |||
243 | 69 | char *do_delete(View *view, size_t len, bool sanity_check_newlines) | |
244 | { | ||
245 |
1/2✓ Branch 0 taken 69 times.
✗ Branch 1 not taken.
|
69 | if (len == 0) { |
246 | return NULL; | ||
247 | } | ||
248 | |||
249 | 69 | ListHead *saved_prev_node = NULL; | |
250 | 69 | Block *blk = view->cursor.blk; | |
251 | 69 | size_t offset = view->cursor.offset; | |
252 |
2/2✓ Branch 0 taken 25 times.
✓ Branch 1 taken 44 times.
|
69 | if (!offset) { |
253 | // The block where the cursor is can become empty and thus | ||
254 | // may be deleted | ||
255 | 25 | saved_prev_node = blk->node.prev; | |
256 | } | ||
257 | |||
258 | 69 | Buffer *buffer = view->buffer; | |
259 | 69 | char *deleted = xmalloc(len); | |
260 | 69 | size_t pos = 0; | |
261 | 69 | size_t deleted_nl = 0; | |
262 | |||
263 | 69 | while (pos < len) { | |
264 | 69 | ListHead *next = blk->node.next; | |
265 | 69 | size_t avail = blk->size - offset; | |
266 | 69 | size_t count = MIN(len - pos, avail); | |
267 | 69 | char *ptr = blk->data + offset; | |
268 | 69 | size_t nl = copy_count_nl(deleted + pos, ptr, count); | |
269 |
2/2✓ Branch 0 taken 56 times.
✓ Branch 1 taken 13 times.
|
69 | if (count < avail) { |
270 | 56 | memmove(ptr, ptr + count, avail - count); | |
271 | } | ||
272 | |||
273 | 69 | deleted_nl += nl; | |
274 | 69 | buffer->nl -= nl; | |
275 | 69 | blk->nl -= nl; | |
276 | 69 | blk->size -= count; | |
277 |
3/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 63 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6 times.
|
69 | if (!blk->size && !only_block(buffer, blk)) { |
278 | ✗ | block_free(blk); | |
279 | } | ||
280 | |||
281 | 69 | offset = 0; | |
282 | 69 | pos += count; | |
283 | 69 | blk = BLOCK(next); | |
284 | 207 | BUG_ON(pos < len && next == &buffer->blocks); | |
285 | } | ||
286 | |||
287 |
2/2✓ Branch 0 taken 25 times.
✓ Branch 1 taken 44 times.
|
69 | if (saved_prev_node) { |
288 | // Cursor was at beginning of a block that was possibly deleted | ||
289 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
|
25 | if (saved_prev_node->next == &buffer->blocks) { |
290 | ✗ | view->cursor.blk = BLOCK(saved_prev_node); | |
291 | ✗ | view->cursor.offset = view->cursor.blk->size; | |
292 | } else { | ||
293 | 25 | view->cursor.blk = BLOCK(saved_prev_node->next); | |
294 | } | ||
295 | } | ||
296 | |||
297 | 69 | blk = view->cursor.blk; | |
298 | |||
299 | 69 | if ( | |
300 |
2/2✓ Branch 0 taken 63 times.
✓ Branch 1 taken 6 times.
|
69 | blk->size |
301 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 63 times.
|
63 | && blk->data[blk->size - 1] != '\n' |
302 | ✗ | && blk->node.next != &buffer->blocks | |
303 | ) { | ||
304 | ✗ | Block *next = BLOCK(blk->node.next); | |
305 | ✗ | size_t size = blk->size + next->size; | |
306 | ✗ | block_grow(blk, size); | |
307 | ✗ | memcpy(blk->data + blk->size, next->data, next->size); | |
308 | ✗ | blk->size = size; | |
309 | ✗ | blk->nl += next->nl; | |
310 | ✗ | block_free(next); | |
311 | } | ||
312 | |||
313 | 69 | sanity_check_blocks(view, sanity_check_newlines); | |
314 | |||
315 | 69 | view_update_cursor_y(view); | |
316 |
2/2✓ Branch 0 taken 44 times.
✓ Branch 1 taken 25 times.
|
69 | buffer_mark_lines_changed(buffer, view->cy, deleted_nl ? LONG_MAX : view->cy); |
317 | |||
318 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 69 times.
|
69 | if (buffer->syntax) { |
319 | ✗ | hl_delete(&buffer->line_start_states, view->cy, deleted_nl); | |
320 | } | ||
321 | |||
322 | return deleted; | ||
323 | } | ||
324 | |||
325 | 31 | char *do_replace(View *view, size_t del, const char *buf, size_t ins) | |
326 | { | ||
327 | 31 | BUG_ON(del == 0); | |
328 | 31 | BUG_ON(ins == 0); | |
329 | 31 | block_iter_normalize(&view->cursor); | |
330 | |||
331 | 31 | Block *blk = view->cursor.blk; | |
332 | 31 | size_t offset = view->cursor.offset; | |
333 | 31 | size_t avail = blk->size - offset; | |
334 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 29 times.
|
31 | if (del >= avail) { |
335 | 2 | goto slow; | |
336 | } | ||
337 | |||
338 | 29 | size_t new_size = blk->size + ins - del; | |
339 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
|
29 | if (new_size > BLOCK_EDIT_SIZE) { |
340 | // Should split | ||
341 | ✗ | if (blk->nl > 1 || memchr(buf, '\n', ins)) { | |
342 | // Most likely can be split | ||
343 | ✗ | goto slow; | |
344 | } | ||
345 | } | ||
346 | |||
347 | 29 | block_grow(blk, new_size); | |
348 | |||
349 | // Modification is limited to one block | ||
350 | 29 | Buffer *buffer = view->buffer; | |
351 | 29 | char *ptr = blk->data + offset; | |
352 | 29 | char *deleted = xmalloc(del); | |
353 | 29 | size_t del_nl = copy_count_nl(deleted, ptr, del); | |
354 | 29 | blk->nl -= del_nl; | |
355 | 29 | buffer->nl -= del_nl; | |
356 | |||
357 |
2/2✓ Branch 0 taken 16 times.
✓ Branch 1 taken 13 times.
|
29 | if (del != ins) { |
358 | 16 | memmove(ptr + ins, ptr + del, avail - del); | |
359 | } | ||
360 | |||
361 | 29 | size_t ins_nl = copy_count_nl(ptr, buf, ins); | |
362 | 29 | blk->nl += ins_nl; | |
363 | 29 | buffer->nl += ins_nl; | |
364 | 29 | blk->size = new_size; | |
365 | 29 | sanity_check_blocks(view, true); | |
366 | 29 | view_update_cursor_y(view); | |
367 | |||
368 | // If the number of inserted and removed bytes are the same, some | ||
369 | // line(s) changed but the lines after them didn't move up or down | ||
370 |
2/2✓ Branch 0 taken 18 times.
✓ Branch 1 taken 11 times.
|
29 | long max = (del_nl == ins_nl) ? view->cy + del_nl : LONG_MAX; |
371 | 29 | buffer_mark_lines_changed(buffer, view->cy, max); | |
372 | |||
373 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 29 times.
|
29 | if (buffer->syntax) { |
374 | ✗ | hl_delete(&buffer->line_start_states, view->cy, del_nl); | |
375 | ✗ | hl_insert(&buffer->line_start_states, view->cy, ins_nl); | |
376 | } | ||
377 | |||
378 | return deleted; | ||
379 | |||
380 | 2 | slow: | |
381 | // The "sanity_check_newlines" argument of do_delete() is false here | ||
382 | // because it may be removing a terminating newline that do_insert() | ||
383 | // is going to insert again at a different position: | ||
384 | 2 | deleted = do_delete(view, del, false); | |
385 | 2 | BUG_ON(!deleted); // do_delete() only returns NULL if len is 0 | |
386 | 2 | do_insert(view, buf, ins); | |
387 | 2 | return deleted; | |
388 | } | ||
389 |