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