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