1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <time.h>
5 #include <sys/mman.h>
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <unistd.h>
9 #include <fcntl.h>
10 #include <utlist.h>
11 #include <inttypes.h>
12 #include "mlbuf.h"
13 
14 static int _buffer_open_mmap(buffer_t *self, int fd, size_t size);
15 static int _buffer_open_read(buffer_t *self, int fd, size_t size);
16 static int _buffer_bline_unslab(bline_t *self);
17 static void _buffer_stat(buffer_t *self);
18 static int _buffer_baction_do(buffer_t *self, bline_t *bline, baction_t *action, int is_redo, bint_t *opt_repeat_offset);
19 static int _buffer_update(buffer_t *self, baction_t *action);
20 static int _buffer_truncate_undo_stack(buffer_t *self, baction_t *action_from);
21 static int _buffer_add_to_undo_stack(buffer_t *self, baction_t *action);
22 static int _buffer_apply_styles_singles(bline_t *start_line, bint_t min_nlines);
23 static int _buffer_apply_styles_multis(bline_t *start_line, bint_t min_nlines, int srule_type);
24 static int _buffer_bline_apply_style_single(srule_t *srule, bline_t *bline);
25 static int _buffer_bline_apply_style_multi(srule_t *srule, bline_t *bline, srule_t **open_rule, bint_t *look_offset);
26 static bline_t *_buffer_bline_new(buffer_t *self);
27 static int _buffer_bline_free(bline_t *bline, bline_t *maybe_mark_line, bint_t col_delta);
28 static bline_t *_buffer_bline_break(bline_t *bline, bint_t col);
29 static void _buffer_find_end_pos(bline_t *start_line, bint_t start_col, bint_t num_chars, bline_t **ret_end_line, bint_t *ret_end_col, bint_t *ret_safe_num_chars);
30 static void _buffer_bline_replace(bline_t *bline, bint_t start_col, char *data, bint_t data_len, str_t *del_data);
31 static bint_t _buffer_bline_insert(bline_t *bline, bint_t col, char *data, bint_t data_len, int move_marks);
32 static bint_t _buffer_bline_delete(bline_t *bline, bint_t col, bint_t num_chars);
33 static bint_t _buffer_bline_col_to_index(bline_t *bline, bint_t col);
34 static bint_t _buffer_bline_index_to_col(bline_t *bline, bint_t index);
35 static int _buffer_munmap(buffer_t *self);
36 static int _srule_multi_find(srule_t *rule, int find_end, bline_t *bline, bint_t start_offset, bint_t *ret_start, bint_t *ret_stop);
37 static int _srule_multi_find_start(srule_t *rule, bline_t *bline, bint_t start_offset, bint_t *ret_start, bint_t *ret_stop);
38 static int _srule_multi_find_end(srule_t *rule, bline_t *bline, bint_t start_offset, bint_t *ret_stop);
39 static int _baction_destroy(baction_t *action);
40 
41 // Make a new buffer and return it
buffer_new()42 buffer_t *buffer_new() {
43     buffer_t *buffer;
44     bline_t *bline;
45     buffer = calloc(1, sizeof(buffer_t));
46     buffer->tab_width = 4;
47     bline = _buffer_bline_new(buffer);
48     buffer->first_line = bline;
49     buffer->last_line = bline;
50     buffer->line_count = 1;
51     buffer->mmap_fd = -1;
52     return buffer;
53 }
54 
55 // Wrapper for buffer_new + buffer_open
buffer_new_open(char * path)56 buffer_t *buffer_new_open(char *path) {
57     buffer_t *self;
58     int rc;
59     self = buffer_new();
60     if ((rc = buffer_open(self, path)) != MLBUF_OK) {
61         buffer_destroy(self);
62         return NULL;
63     }
64     return self;
65 }
66 
67 // Read buffer from path
buffer_open(buffer_t * self,char * path)68 int buffer_open(buffer_t *self, char *path) {
69     int rc;
70     struct stat st;
71     int fd;
72     fd = -1;
73 
74     rc = MLBUF_OK;
75     do {
76         // Exit early if path is empty
77         if (!path || strlen(path) < 1) {
78             rc = MLBUF_ERR;
79             break;
80         }
81 
82         // Open file for reading
83         if ((fd = open(path, O_RDONLY)) < 0) {
84             rc = MLBUF_ERR;
85             break;
86         }
87 
88         // Stat file
89         if (fstat(fd, &st) < 0) {
90             rc = MLBUF_ERR;
91             break;
92         }
93 
94         // Read or mmap file into buffer
95         self->is_in_open = 1;
96         if (st.st_size >= MLBUF_LARGE_FILE_SIZE) {
97             if (_buffer_open_mmap(self, fd, st.st_size) != MLBUF_OK) {
98                 // mmap failed so fallback to normal read
99                 if (_buffer_open_read(self, fd, st.st_size) != MLBUF_OK) {
100                     rc = MLBUF_ERR;
101                     break;
102                 }
103             }
104         } else {
105             if (_buffer_open_read(self, fd, st.st_size) != MLBUF_OK) {
106                 rc = MLBUF_ERR;
107                 break;
108             }
109         }
110         self->is_in_open = 0;
111     } while(0);
112 
113     if (fd >= 0) close(fd);
114 
115     if (rc == MLBUF_ERR) return rc;
116 
117     // Set path
118     if (self->path) free(self->path);
119     self->path = strdup(path);
120     self->is_unsaved = 0;
121 
122     // Remember stat
123     _buffer_stat(self);
124 
125     return MLBUF_OK;
126 }
127 
128 // Write buffer to path
buffer_save(buffer_t * self)129 int buffer_save(buffer_t *self) {
130     return buffer_save_as(self, self->path, NULL);
131 }
132 
133 // Write buffer to specified path
buffer_save_as(buffer_t * self,char * path,bint_t * optret_nbytes)134 int buffer_save_as(buffer_t *self, char *path, bint_t *optret_nbytes) {
135     FILE *fp;
136     size_t nbytes;
137 
138     if (optret_nbytes) *optret_nbytes = 0;
139 
140     // Exit early if path is empty
141     if (!path || strlen(path) < 1) {
142         return MLBUF_ERR;
143     }
144 
145     // Open file for writing
146     if (!(fp = fopen(path, "wb"))) {
147         return MLBUF_ERR;
148     }
149 
150     // Write data to file
151     nbytes = 0;
152     buffer_write_to_file(self, fp, &nbytes);
153     fclose(fp);
154     if (optret_nbytes) *optret_nbytes = (bint_t)nbytes;
155     if ((bint_t)nbytes != self->byte_count) return MLBUF_ERR;
156 
157     // Set path
158     if (self->path != path) {
159         if (self->path) free(self->path);
160         self->path = strdup(path);
161     }
162     self->is_unsaved = 0;
163 
164     // Remember stat
165     _buffer_stat(self);
166 
167     return MLBUF_OK;
168 }
169 
170 // Write buffer data to FILE*
buffer_write_to_file(buffer_t * self,FILE * fp,size_t * optret_nbytes)171 int buffer_write_to_file(buffer_t *self, FILE *fp, size_t *optret_nbytes) {
172     return buffer_write_to_fd(self, fileno(fp), optret_nbytes);
173 }
174 
175 // Write buffer data to file descriptor
buffer_write_to_fd(buffer_t * self,int fd,size_t * optret_nbytes)176 int buffer_write_to_fd(buffer_t *self, int fd, size_t *optret_nbytes) {
177     bline_t *bline;
178     size_t nbytes;
179     ssize_t write_rc;
180     nbytes = 0;
181     #define MLBUF_BUFFER_WRITE_CHECK(pd, pl) do { \
182         write_rc = write(fd, (pd), (pl)); \
183         if (write_rc < (pl)) { \
184             return MLBUF_ERR; \
185         } \
186         nbytes += write_rc; \
187     } while(0)
188     for (bline = self->first_line; bline; bline = bline->next) {
189         if (bline->data_len > 0) MLBUF_BUFFER_WRITE_CHECK(bline->data, bline->data_len);
190         if (bline->next)         MLBUF_BUFFER_WRITE_CHECK("\n", 1);
191     }
192     if (optret_nbytes) *optret_nbytes = nbytes;
193     return MLBUF_OK;
194 }
195 
196 // Free a buffer
buffer_destroy(buffer_t * self)197 int buffer_destroy(buffer_t *self) {
198     bline_t *line;
199     bline_t *line_tmp;
200     baction_t *action;
201     baction_t *action_tmp;
202     char c;
203     for (line = self->last_line; line; ) {
204         line_tmp = line->prev;
205         _buffer_bline_free(line, NULL, 0);
206         line = line_tmp;
207     }
208     if (self->data) free(self->data);
209     if (self->path) free(self->path);
210     DL_FOREACH_SAFE(self->actions, action, action_tmp) {
211         DL_DELETE(self->actions, action);
212         _baction_destroy(action);
213     }
214     for (c = 'a'; c <= 'z'; c++) buffer_register_clear(self, c);
215     _buffer_munmap(self);
216     if (self->slabbed_blines) free(self->slabbed_blines);
217     if (self->slabbed_chars) free(self->slabbed_chars);
218     free(self);
219     return MLBUF_OK;
220 }
221 
222 // Add a mark to this buffer and return it
buffer_add_mark(buffer_t * self,bline_t * maybe_line,bint_t maybe_col)223 mark_t *buffer_add_mark(buffer_t *self, bline_t *maybe_line, bint_t maybe_col) {
224     return buffer_add_mark_ex(self, '\0', maybe_line, maybe_col);
225 }
226 
227 // If letter is [a-z], add a lettered mark and return it.
228 // If letter is \0, add a non-lettered mark and return it.
229 // Otherwise do nothing and return NULL.
buffer_add_mark_ex(buffer_t * self,char letter,bline_t * maybe_line,bint_t maybe_col)230 mark_t *buffer_add_mark_ex(buffer_t *self, char letter, bline_t *maybe_line, bint_t maybe_col) {
231     mark_t *mark;
232     mark_t *mark_tmp;
233     if (!((letter >= 'a' && letter <= 'z') || letter == '\0')) {
234         return NULL;
235     }
236     mark = calloc(1, sizeof(mark_t));
237     mark->letter = letter;
238     MLBUF_MAKE_GT_EQ0(maybe_col);
239     if (maybe_line != NULL) {
240         MLBUF_BLINE_ENSURE_CHARS(maybe_line);
241         mark->bline = maybe_line;
242         mark->col = maybe_col < 0 ? 0 : MLBUF_MIN(maybe_line->char_count, maybe_col);
243     } else {
244         mark->bline = self->first_line;
245         mark->col = 0;
246     }
247     DL_APPEND(mark->bline->marks, mark);
248     if (mark->letter) {
249         if ((mark_tmp = MLBUF_LETT_MARK(self, mark->letter)) != NULL) {
250             buffer_destroy_mark(self, mark_tmp);
251         }
252         MLBUF_LETT_MARK(self, mark->letter) = mark;
253     }
254     return mark;
255 }
256 
257 // Return lettered mark or NULL if it does not exist
buffer_get_lettered_mark(buffer_t * self,char letter,mark_t ** ret_mark)258 int buffer_get_lettered_mark(buffer_t *self, char letter, mark_t **ret_mark) {
259     MLBUF_ENSURE_AZ(letter);
260     *ret_mark = MLBUF_LETT_MARK(self, letter);
261     return MLBUF_OK;
262 }
263 
264 // Remove mark from buffer and free it, removing any range srules that use it
buffer_destroy_mark(buffer_t * self,mark_t * mark)265 int buffer_destroy_mark(buffer_t *self, mark_t *mark) {
266     srule_node_t *node;
267     srule_node_t *node_tmp;
268     DL_DELETE(mark->bline->marks, mark);
269     if (mark->letter) MLBUF_LETT_MARK(self, mark->letter) = NULL;
270     DL_FOREACH_SAFE(self->multi_srules, node, node_tmp) {
271         if (node->srule->type == MLBUF_SRULE_TYPE_RANGE
272             && (node->srule->range_a == mark
273             ||  node->srule->range_b == mark)
274         ) {
275             buffer_remove_srule(self, node->srule);
276         }
277     }
278     free(mark);
279     return MLBUF_OK;
280 }
281 
282 // Get buffer contents and length
buffer_get(buffer_t * self,char ** ret_data,bint_t * ret_data_len)283 int buffer_get(buffer_t *self, char **ret_data, bint_t *ret_data_len) {
284     bline_t *bline;
285     char *data_cursor;
286     bint_t alloc_size;
287     if (self->is_data_dirty) {
288         // Refresh self->data
289         alloc_size = self->byte_count + 2;
290         self->data = self->data != NULL
291             ? realloc(self->data, alloc_size)
292             : malloc(alloc_size);
293         data_cursor = self->data;
294         for (bline = self->first_line; bline != NULL; bline = bline->next) {
295             if (bline->data_len > 0) {
296                 memcpy(data_cursor, bline->data, bline->data_len);
297                 data_cursor += bline->data_len;
298                 self->data_len += bline->data_len;
299             }
300             if (bline->next) {
301                 *data_cursor = '\n';
302                 data_cursor += 1;
303             }
304         }
305         *data_cursor = '\0';
306         self->data_len = (bint_t)(data_cursor - self->data);
307         self->is_data_dirty = 0;
308     }
309     *ret_data = self->data;
310     *ret_data_len = self->data_len;
311     return MLBUF_OK;
312 }
313 
buffer_clear(buffer_t * self)314 int buffer_clear(buffer_t *self) {
315     return buffer_delete(self, 0, self->byte_count);
316 }
317 
318 // Set buffer contents
buffer_set(buffer_t * self,char * data,bint_t data_len)319 int buffer_set(buffer_t *self, char *data, bint_t data_len) {
320     int rc;
321     MLBUF_MAKE_GT_EQ0(data_len);
322     if ((rc = buffer_clear(self)) != MLBUF_OK) {
323         return rc;
324     }
325     rc = buffer_insert(self, 0, data, data_len, NULL);
326     if (self->actions) _buffer_truncate_undo_stack(self, self->actions);
327     return rc;
328 }
329 
330 // Set buffer contents more efficiently
buffer_set_mmapped(buffer_t * self,char * data,bint_t data_len)331 int buffer_set_mmapped(buffer_t *self, char *data, bint_t data_len) {
332     bint_t nlines;
333     bint_t line_num;
334     bline_t *blines;
335     bint_t data_remaining_len;
336     char *data_cursor;
337     char *data_newline;
338     bint_t line_len;
339 
340     if (buffer_clear(self) != MLBUF_OK) {
341         return MLBUF_ERR;
342     }
343 
344     // Count number of lines
345     nlines = 1;
346     data_cursor = data;
347     data_remaining_len = data_len;
348     while (data_remaining_len > 0 && (data_newline = memchr(data_cursor, '\n', data_remaining_len)) != NULL) {
349         data_remaining_len -= (bint_t)(data_newline - data_cursor) + 1;
350         data_cursor = data_newline + 1;
351         nlines += 1;
352     }
353 
354     // Allocate blines and chars. These are freed in buffer_destroy.
355     self->slabbed_chars = calloc(data_len, sizeof(bline_char_t));
356     self->slabbed_blines = malloc(nlines * sizeof(bline_t));
357     blines = self->slabbed_blines;
358 
359     // Populate blines
360     line_num = 0;
361     data_cursor = data;
362     data_remaining_len = data_len;
363     while (1) {
364         data_newline = data_remaining_len > 0
365             ? memchr(data_cursor, '\n', data_remaining_len)
366             : NULL;
367         line_len = data_newline ?
368             (bint_t)(data_newline - data_cursor)
369             : data_remaining_len;
370         blines[line_num] = (bline_t){
371             .buffer = self,
372             .data = data_cursor,
373             .data_len = line_len,
374             .data_cap = line_len,
375             .line_index = line_num,
376             .char_count = line_len,
377             .char_vwidth = line_len,
378             .chars = (self->slabbed_chars + (data_len - data_remaining_len)),
379             .chars_cap = line_len,
380             .marks = NULL,
381             .bol_rule = NULL,
382             .eol_rule = NULL,
383             .is_chars_dirty = 1,
384             .is_slabbed = 1,
385             .is_data_slabbed = 1,
386             .next = NULL,
387             .prev = NULL
388         };
389         if (line_num > 0) {
390             blines[line_num-1].next = blines + line_num;
391             blines[line_num].prev = blines + (line_num-1);
392         }
393         if (data_newline) {
394             data_remaining_len -= line_len + 1;
395             data_cursor = data_newline + 1;
396             line_num += 1;
397         } else {
398             break;
399         }
400     }
401 
402     if (self->first_line) _buffer_bline_free(self->first_line, NULL, 0);
403     self->first_line = blines;
404     self->last_line = blines + line_num;
405     self->byte_count = data_len;
406     self->line_count = line_num + 1;
407     self->is_data_dirty = 1;
408     return MLBUF_OK;
409 }
410 
411 // Insert data into buffer given a buffer offset
buffer_insert(buffer_t * self,bint_t offset,char * data,bint_t data_len,bint_t * optret_num_chars)412 int buffer_insert(buffer_t *self, bint_t offset, char *data, bint_t data_len, bint_t *optret_num_chars) {
413     int rc;
414     bline_t *start_line;
415     bint_t start_col;
416     MLBUF_MAKE_GT_EQ0(offset);
417 
418     // Find start line and col
419     if ((rc = buffer_get_bline_col(self, offset, &start_line, &start_col)) != MLBUF_OK) {
420         return rc;
421     }
422 
423     return buffer_insert_w_bline(self, start_line, start_col, data, data_len, optret_num_chars);
424 }
425 
426 // Insert data into buffer given a bline/col
buffer_insert_w_bline(buffer_t * self,bline_t * start_line,bint_t start_col,char * data,bint_t data_len,bint_t * optret_num_chars)427 int buffer_insert_w_bline(buffer_t *self, bline_t *start_line, bint_t start_col, char *data, bint_t data_len, bint_t *optret_num_chars) {
428     bline_t *cur_line;
429     bint_t cur_col;
430     bline_t *new_line;
431     char *data_cursor;
432     char *data_newline;
433     bint_t data_remaining_len;
434     bint_t insert_len;
435     bint_t num_lines_added;
436     char *ins_data;
437     bint_t ins_data_len;
438     bint_t ins_data_nchars;
439     baction_t *action;
440     MLBUF_MAKE_GT_EQ0(data_len);
441 
442     // Exit early if no data
443     if (data_len < 1) {
444         return MLBUF_OK;
445     }
446 
447     // Insert lines
448     data_cursor = data;
449     data_remaining_len = data_len;
450     cur_line = start_line;
451     cur_col = start_col;
452     num_lines_added = 0;
453     while (data_remaining_len > 0 && (data_newline = memchr(data_cursor, '\n', data_remaining_len)) != NULL) {
454         insert_len = (bint_t)(data_newline - data_cursor);
455         new_line = _buffer_bline_break(cur_line, cur_col);
456         num_lines_added += 1;
457         if (insert_len > 0) {
458             _buffer_bline_insert(cur_line, cur_col, data_cursor, insert_len, 1);
459         }
460         data_remaining_len -= (data_newline - data_cursor) + 1;
461         data_cursor = data_newline + 1;
462         cur_line = new_line;
463         cur_col = 0;
464     }
465     if (data_remaining_len > 0) {
466         cur_col += _buffer_bline_insert(cur_line, cur_col, data_cursor, data_remaining_len, 1);
467     }
468 
469     // Get inserted data
470     buffer_substr(self, start_line, start_col, cur_line, cur_col, &ins_data, &ins_data_len, &ins_data_nchars);
471 
472     // Add baction
473     action = calloc(1, sizeof(baction_t));
474     action->type = MLBUF_BACTION_TYPE_INSERT;
475     action->buffer = self;
476     action->start_line = start_line;
477     action->start_line_index = start_line->line_index;
478     action->start_col = start_col;
479     action->maybe_end_line = cur_line;
480     action->maybe_end_line_index = action->start_line_index + num_lines_added;
481     action->maybe_end_col = cur_col;
482     action->byte_delta = (bint_t)ins_data_len;
483     action->char_delta = (bint_t)ins_data_nchars;
484     action->line_delta = (bint_t)num_lines_added;
485     action->data = ins_data;
486     action->data_len = ins_data_len;
487     _buffer_update(self, action);
488     if (optret_num_chars) *optret_num_chars = ins_data_nchars;
489 
490     return MLBUF_OK;
491 }
492 
493 // Delete data from buffer given an offset
buffer_delete(buffer_t * self,bint_t offset,bint_t num_chars)494 int buffer_delete(buffer_t *self, bint_t offset, bint_t num_chars) {
495     bline_t *start_line;
496     bint_t start_col;
497     MLBUF_MAKE_GT_EQ0(offset);
498     buffer_get_bline_col(self, offset, &start_line, &start_col);
499     return buffer_delete_w_bline(self, start_line, start_col, num_chars);
500 }
501 
502 // Delete data from buffer given a bline/col
buffer_delete_w_bline(buffer_t * self,bline_t * start_line,bint_t start_col,bint_t num_chars)503 int buffer_delete_w_bline(buffer_t *self, bline_t *start_line, bint_t start_col, bint_t num_chars) {
504     bline_t *end_line;
505     bint_t end_col;
506     bline_t *tmp_line;
507     bline_t *swap_line;
508     bline_t *next_line;
509     bint_t tmp_len;
510     char *del_data;
511     bint_t del_data_len;
512     bint_t del_data_nchars;
513     bint_t num_lines_removed;
514     bint_t safe_num_chars;
515     bint_t orig_char_count;
516     baction_t *action;
517     MLBUF_MAKE_GT_EQ0(num_chars);
518 
519     // Find end line and col
520     _buffer_find_end_pos(start_line, start_col, num_chars, &end_line, &end_col, &num_chars);
521 
522     // Exit early if there is nothing to delete
523     MLBUF_BLINE_ENSURE_CHARS(self->last_line);
524     if (start_line == end_line && start_col >= end_col) {
525         return MLBUF_OK;
526     } else if (start_line == self->last_line && start_col == self->last_line->char_count) {
527         return MLBUF_OK;
528     }
529 
530     // Get deleted data
531     buffer_substr(self, start_line, start_col, end_line, end_col, &del_data, &del_data_len, &del_data_nchars);
532 
533     // Delete suffix starting at start_line:start_col
534     MLBUF_BLINE_ENSURE_CHARS(start_line);
535     safe_num_chars = MLBUF_MIN(num_chars, start_line->char_count - start_col);
536     if (safe_num_chars > 0) {
537         _buffer_bline_delete(start_line, start_col, safe_num_chars);
538     }
539 
540     // Copy remaining portion of end_line to start_line:start_col
541     MLBUF_BLINE_ENSURE_CHARS(start_line);
542     orig_char_count = start_line->char_count;
543     if (start_line != end_line && (tmp_len = end_line->data_len - _buffer_bline_col_to_index(end_line, end_col)) > 0) {
544         _buffer_bline_insert(
545             start_line,
546             start_col,
547             end_line->data + (end_line->data_len - tmp_len),
548             tmp_len,
549             0
550         );
551     }
552 
553     // Remove lines after start_line thru end_line
554     // Relocate marks to end of start_line
555     num_lines_removed = 0;
556     swap_line = end_line->next;
557     next_line = NULL;
558     tmp_line = start_line->next;
559     while (tmp_line != NULL && tmp_line != swap_line) {
560         next_line = tmp_line->next;
561         _buffer_bline_free(tmp_line, start_line, orig_char_count - end_col);
562         num_lines_removed += 1;
563         tmp_line = next_line;
564     }
565     start_line->next = swap_line;
566     if (swap_line) swap_line->prev = start_line;
567 
568     // Add baction
569     action = calloc(1, sizeof(baction_t));
570     action->type = MLBUF_BACTION_TYPE_DELETE;
571     action->buffer = self;
572     action->start_line = start_line;
573     action->start_line_index = start_line->line_index;
574     action->start_col = start_col;
575     action->byte_delta = -1 * (bint_t)del_data_len;
576     action->char_delta = -1 * (bint_t)del_data_nchars;
577     action->line_delta = -1 * (bint_t)num_lines_removed;
578     action->data = del_data;
579     action->data_len = del_data_len;
580     _buffer_update(self, action);
581 
582     return MLBUF_OK;
583 }
584 
585 // Replace num_chars in buffer at offset with data
buffer_replace(buffer_t * self,bint_t offset,bint_t num_chars,char * data,bint_t data_len)586 int buffer_replace(buffer_t *self, bint_t offset, bint_t num_chars, char *data, bint_t data_len) {
587     int rc;
588     bline_t *start_line;
589     bint_t start_col;
590     MLBUF_MAKE_GT_EQ0(offset);
591 
592     // Find start line and col
593     if ((rc = buffer_get_bline_col(self, offset, &start_line, &start_col)) != MLBUF_OK) {
594         return rc;
595     }
596 
597     return buffer_replace_w_bline(self, start_line, start_col, num_chars, data, data_len);
598 }
599 
600 // Replace num_chars from start_line:start_col with data
buffer_replace_w_bline(buffer_t * self,bline_t * start_line,bint_t start_col,bint_t del_chars,char * data,bint_t data_len)601 int buffer_replace_w_bline(buffer_t *self, bline_t *start_line, bint_t start_col, bint_t del_chars, char *data, bint_t data_len) {
602     bline_t *cur_line;
603     bint_t cur_col;
604     bint_t insert_rem;
605     bint_t delete_rem;
606     bint_t data_linelen;
607     bint_t nchars_ins;
608     bint_t nlines;
609     char *data_cursor;
610     char *data_newline;
611     baction_t *action;
612     str_t del_data = {0};
613 
614     // Replace data on common lines
615     insert_rem = data_len;
616     delete_rem = del_chars;
617     cur_line = start_line;
618     cur_col = start_col;
619     data_cursor = data;
620     nchars_ins = 0;
621     nlines = 0;
622     MLBUF_BLINE_ENSURE_CHARS(cur_line);
623     while (insert_rem > 0 && delete_rem > (cur_line->char_count - cur_col)) {
624         data_newline = memchr(data_cursor, '\n', insert_rem);
625         data_linelen = data_newline ? (bint_t)(data_newline - data_cursor) : insert_rem;
626         delete_rem -= cur_line->char_count - cur_col;
627         _buffer_bline_replace(cur_line, cur_col, data_cursor, data_linelen, &del_data);
628         nchars_ins += cur_line->char_count - cur_col;
629         insert_rem -= data_linelen;
630         data_cursor += data_linelen;
631         cur_col = cur_line->char_count;
632         if (data_newline && insert_rem >= 2 && delete_rem >= 2 && cur_line->next) {
633             data_cursor += 1;
634             insert_rem -= 1;
635             delete_rem -= 1;
636             cur_line = cur_line->next;
637             cur_col = 0;
638             nchars_ins += 1;
639             str_append_len(&del_data, "\n", 1);
640         } else {
641             break;
642         }
643         nlines += 1;
644         MLBUF_BLINE_ENSURE_CHARS(cur_line);
645     }
646 
647     // Add delete baction
648     if (del_data.len > 0) {
649         action = calloc(1, sizeof(baction_t));
650         action->type = MLBUF_BACTION_TYPE_DELETE;
651         action->buffer = self;
652         action->start_line = start_line;
653         action->start_line_index = start_line->line_index;
654         action->start_col = start_col;
655         action->byte_delta = -1 * (bint_t)del_data.len;
656         action->char_delta = -1 * (del_chars - delete_rem);
657         action->line_delta = -1 * nlines;
658         action->data = del_data.data;
659         action->data_len = (bint_t)del_data.len;
660         _buffer_update(self, action);
661     }
662 
663     // Add insert baction
664     if (data_len - insert_rem > 0) {
665         action = calloc(1, sizeof(baction_t));
666         action->type = MLBUF_BACTION_TYPE_INSERT;
667         action->buffer = self;
668         action->start_line = start_line;
669         action->start_line_index = start_line->line_index;
670         action->start_col = start_col;
671         action->maybe_end_line = cur_line;
672         action->maybe_end_line_index = action->start_line_index + nlines;
673         action->maybe_end_col = cur_col;
674         action->byte_delta = data_len - insert_rem;
675         action->char_delta = nchars_ins;
676         action->line_delta = nlines;
677         action->data = strndup(data, action->byte_delta);
678         action->data_len = action->byte_delta;
679         _buffer_update(self, action);
680     }
681 
682     // Delete left over data
683     if (delete_rem > 0) {
684         buffer_delete_w_bline(self, cur_line, cur_col, delete_rem);
685     }
686 
687     // Insert left over data
688     if (insert_rem > 0) {
689         buffer_insert_w_bline(self, cur_line, cur_col, data_cursor, insert_rem, NULL);
690     }
691 
692     return MLBUF_OK;
693 }
694 
695 // Return a line given a line_index
buffer_get_bline(buffer_t * self,bint_t line_index,bline_t ** ret_bline)696 int buffer_get_bline(buffer_t *self, bint_t line_index, bline_t **ret_bline) {
697     bline_t *tmp_line;
698     MLBUF_MAKE_GT_EQ0(line_index);
699     for (tmp_line = self->first_line; tmp_line; tmp_line = tmp_line->next) {
700         if (tmp_line->line_index == line_index) {
701             *ret_bline = tmp_line;
702             return MLBUF_OK;
703         }
704     }
705     *ret_bline = self->last_line;
706     return MLBUF_ERR;
707 }
708 
709 // Return a line and col for the given offset
buffer_get_bline_col(buffer_t * self,bint_t offset,bline_t ** ret_bline,bint_t * ret_col)710 int buffer_get_bline_col(buffer_t *self, bint_t offset, bline_t **ret_bline, bint_t *ret_col) {
711     bline_t *tmp_line;
712     bline_t *good_line = NULL;
713     bint_t remaining_chars;
714     MLBUF_MAKE_GT_EQ0(offset);
715 
716     remaining_chars = offset;
717     for (tmp_line = self->first_line; tmp_line != NULL; tmp_line = tmp_line->next) {
718         MLBUF_BLINE_ENSURE_CHARS(tmp_line);
719         if (tmp_line->char_count >= remaining_chars) {
720             *ret_bline = tmp_line;
721             *ret_col = remaining_chars;
722             return MLBUF_OK;
723         } else {
724             remaining_chars -= (tmp_line->char_count + 1); // Plus 1 for newline
725         }
726         good_line = tmp_line;
727     }
728 
729     if (!good_line) good_line = self->first_line;
730     *ret_bline = good_line;
731     *ret_col = good_line->char_count;
732     return MLBUF_OK;
733 }
734 
735 // Return an offset given a line and col
buffer_get_offset(buffer_t * self,bline_t * bline,bint_t col,bint_t * ret_offset)736 int buffer_get_offset(buffer_t *self, bline_t *bline, bint_t col, bint_t *ret_offset) {
737     bline_t *tmp_line;
738     bint_t offset;
739     MLBUF_MAKE_GT_EQ0(col);
740 
741     offset = 0;
742     for (tmp_line = self->first_line; tmp_line != bline->next; tmp_line = tmp_line->next) {
743         MLBUF_BLINE_ENSURE_CHARS(tmp_line);
744         if (tmp_line == bline) {
745             offset += MLBUF_MIN(tmp_line->char_count, col);
746             break;
747         } else {
748             offset += tmp_line->char_count + 1; // Plus 1 for newline
749         }
750     }
751 
752     *ret_offset = offset;
753     return MLBUF_OK;
754 }
755 
756 // Add a style rule to the buffer
buffer_add_srule(buffer_t * self,srule_t * srule)757 int buffer_add_srule(buffer_t *self, srule_t *srule) {
758     srule_node_t *node;
759     node = calloc(1, sizeof(srule_node_t));
760     node->srule = srule;
761     if (srule->type == MLBUF_SRULE_TYPE_SINGLE) {
762         DL_APPEND(self->single_srules, node);
763     } else {
764         DL_APPEND(self->multi_srules, node);
765     }
766     if (srule->type == MLBUF_SRULE_TYPE_RANGE) {
767         srule->range_a->range_srule = srule;
768         srule->range_b->range_srule = srule;
769     }
770     return buffer_apply_styles(self, self->first_line, self->line_count - 1);
771 }
772 
773 // Remove a style rule from the buffer
buffer_remove_srule(buffer_t * self,srule_t * srule)774 int buffer_remove_srule(buffer_t *self, srule_t *srule) {
775     int found;
776     srule_node_t **head;
777     srule_node_t *node;
778     srule_node_t *node_tmp;
779     if (srule->type == MLBUF_SRULE_TYPE_SINGLE) {
780         head = &self->single_srules;
781     } else {
782         head = &self->multi_srules;
783     }
784     found = 0;
785     DL_FOREACH_SAFE(*head, node, node_tmp) {
786         if (node->srule != srule) continue;
787         if (srule->type == MLBUF_SRULE_TYPE_RANGE) {
788             srule->range_a->range_srule = NULL;
789             srule->range_b->range_srule = NULL;
790         }
791         DL_DELETE(*head, node);
792         free(node);
793         found = 1;
794         break;
795     }
796     if (!found) return MLBUF_ERR;
797     return buffer_apply_styles(self, self->first_line, self->line_count - 1);
798 }
799 
800 // Set callback to cb. Pass in NULL to unset callback.
buffer_set_callback(buffer_t * self,buffer_callback_t cb,void * udata)801 int buffer_set_callback(buffer_t *self, buffer_callback_t cb, void *udata) {
802     if (cb) {
803         self->callback = cb;
804         self->callback_udata = udata;
805     } else {
806         self->callback = NULL;
807         self->callback_udata = NULL;
808     }
809     return MLBUF_OK;
810 }
811 
812 // Set tab_width and recalculate all line char vwidths
buffer_set_tab_width(buffer_t * self,int tab_width)813 int buffer_set_tab_width(buffer_t *self, int tab_width) {
814     bline_t *tmp_line;
815     if (tab_width < 1) {
816         return MLBUF_ERR;
817     }
818     self->tab_width = tab_width;
819     for (tmp_line = self->first_line; tmp_line; tmp_line = tmp_line->next) {
820         bline_count_chars(tmp_line);
821     }
822     return MLBUF_OK;
823 }
824 
825 // Return data from start_line:start_col thru end_line:end_col
buffer_substr(buffer_t * self,bline_t * start_line,bint_t start_col,bline_t * end_line,bint_t end_col,char ** ret_data,bint_t * ret_data_len,bint_t * ret_data_nchars)826 int buffer_substr(buffer_t *self, bline_t *start_line, bint_t start_col, bline_t *end_line, bint_t end_col, char **ret_data, bint_t *ret_data_len, bint_t *ret_data_nchars) {
827     char *data;
828     bint_t data_len;
829     bint_t data_size;
830     bline_t *tmp_line;
831     bint_t copy_len;
832     bint_t copy_index;
833     bint_t add_len;
834     bint_t nchars;
835     MLBUF_MAKE_GT_EQ0(start_col);
836     MLBUF_MAKE_GT_EQ0(end_col);
837 
838     data = calloc(2, sizeof(char));
839     data_len = 0;
840     data_size = 2;
841     nchars = 0;
842 
843     for (tmp_line = start_line; tmp_line != end_line->next; tmp_line = tmp_line->next) {
844         // Get copy_index + copy_len
845         // Also increment nchars
846         if (start_line == end_line) {
847             copy_index = _buffer_bline_col_to_index(start_line, start_col);
848             copy_len = _buffer_bline_col_to_index(start_line, end_col) - copy_index;
849             nchars += end_col - start_col;
850         } else if (tmp_line == start_line) {
851             copy_index = _buffer_bline_col_to_index(start_line, start_col);
852             copy_len = tmp_line->data_len - copy_index;
853             MLBUF_BLINE_ENSURE_CHARS(start_line);
854             nchars += start_line->char_count - start_col;
855         } else if (tmp_line == end_line) {
856             copy_index = 0;
857             copy_len = _buffer_bline_col_to_index(end_line, end_col);
858             nchars += end_col;
859         } else {
860             copy_index = 0;
861             copy_len = tmp_line->data_len;
862             MLBUF_BLINE_ENSURE_CHARS(tmp_line);
863             nchars += tmp_line->char_count;
864         }
865 
866         // Add 1 for newline if not on end_line
867         add_len = copy_len + (tmp_line != end_line ? 1 : 0); // Plus 1 for newline
868         nchars += (tmp_line != end_line ? 1 : 0);
869 
870         // Copy add_len bytes from copy_index into data
871         if (add_len > 0) {
872             if (data_len + add_len + 1 > data_size) {
873                 data_size = data_len + add_len + 1; // Plus 1 for nullchar
874                 data = realloc(data, data_size);
875             }
876             if (copy_len > 0) {
877                 memcpy(data + data_len, tmp_line->data + copy_index, copy_len);
878                 data_len += copy_len;
879             }
880             if (tmp_line != end_line) {
881                 *(data + data_len) = '\n';
882                 data_len += 1;
883             }
884         }
885     }
886 
887     *(data + data_len) = '\0';
888     *ret_data = data;
889     *ret_data_len = data_len;
890     *ret_data_nchars = nchars;
891 
892     return MLBUF_OK;
893 }
894 
895 // Undo an action
buffer_undo(buffer_t * self)896 int buffer_undo(buffer_t *self) {
897     baction_t *action_to_undo;
898     bline_t *bline;
899     int rc;
900 
901     // Find action to undo
902     if (self->action_undone) {
903         if (self->action_undone == self->actions) {
904             return MLBUF_ERR;
905         } else if (!self->action_undone->prev) {
906             return MLBUF_ERR;
907         }
908         action_to_undo = self->action_undone->prev;
909     } else if (self->action_tail) {
910         action_to_undo = self->action_tail;
911     } else {
912         return MLBUF_ERR;
913     }
914 
915     // Get line to perform undo on
916     bline = NULL;
917     buffer_get_bline(self, action_to_undo->start_line_index, &bline);
918     MLBUF_BLINE_ENSURE_CHARS(bline);
919     if (!bline) {
920         return MLBUF_ERR;
921     } else if (action_to_undo->start_col > bline->char_count) {
922         return MLBUF_ERR;
923     }
924 
925     // Perform action
926     rc = _buffer_baction_do(self, bline, action_to_undo, 0, NULL);
927 
928     // Update action_undone
929     if (rc == MLBUF_OK) {
930         self->action_undone = action_to_undo;
931     }
932     return rc;
933 }
934 
935 // Redo an undone action
buffer_redo(buffer_t * self)936 int buffer_redo(buffer_t *self) {
937     baction_t *action_to_redo;
938     bline_t *bline;
939     int rc;
940 
941     // Find action to undo
942     if (!self->action_undone) {
943         return MLBUF_ERR;
944     }
945     action_to_redo = self->action_undone;
946 
947     // Get line to perform undo on
948     bline = NULL;
949     buffer_get_bline(self, action_to_redo->start_line_index, &bline);
950     MLBUF_BLINE_ENSURE_CHARS(bline);
951     if (!bline) {
952         return MLBUF_ERR;
953     } else if (action_to_redo->start_col > bline->char_count) {
954         return MLBUF_ERR;
955     }
956 
957     // Perform action
958     rc = _buffer_baction_do(self, bline, action_to_redo, 1, NULL);
959 
960     // Update action_undone
961     if (rc == MLBUF_OK) {
962         self->action_undone = self->action_undone->next;
963     }
964     return rc;
965 }
966 
967 // Toggle is_style_disabled
buffer_set_styles_enabled(buffer_t * self,int is_enabled)968 int buffer_set_styles_enabled(buffer_t *self, int is_enabled) {
969     if (!self->is_style_disabled && !is_enabled) {
970         self->is_style_disabled = 1;
971     } else if (self->is_style_disabled && is_enabled) {
972         self->is_style_disabled = 0;
973         buffer_apply_styles(self, self->first_line, self->line_count);
974     }
975     return MLBUF_OK;
976 }
977 
978 // Apply styles from start_line
buffer_apply_styles(buffer_t * self,bline_t * start_line,bint_t line_delta)979 int buffer_apply_styles(buffer_t *self, bline_t *start_line, bint_t line_delta) {
980     bint_t min_nlines;
981     srule_node_t *srule_node;
982     int count_tmp;
983     int srule_count;
984 
985     if (self->is_style_disabled) {
986         return MLBUF_OK;
987     }
988 
989     // min_nlines, minimum number of lines to style
990     //     line_delta  < 0: 2 (start_line + 1)
991     //     line_delta == 0: 1 (start_line)
992     //     line_delta  > 0: 1 + line_delta (start_line + added lines)
993     min_nlines = 1 + (line_delta < 0 ? 1 : line_delta);
994 
995     // Count current srules
996     srule_count = 0;
997     DL_COUNT(self->single_srules, srule_node, count_tmp);
998     srule_count += count_tmp;
999     DL_COUNT(self->multi_srules, srule_node, count_tmp);
1000     srule_count += count_tmp;
1001 
1002     // Apply rules if there are any, or if the number of rules changed
1003     if (srule_count > 0 || self->num_applied_srules != srule_count) {
1004         _buffer_apply_styles_singles(start_line, min_nlines);
1005         _buffer_apply_styles_multis(start_line, min_nlines, MLBUF_SRULE_TYPE_MULTI);
1006         _buffer_apply_styles_multis(start_line, min_nlines, MLBUF_SRULE_TYPE_RANGE);
1007         self->num_applied_srules = srule_count;
1008     }
1009 
1010     return MLBUF_OK;
1011 }
1012 
1013 // Set register
buffer_register_set(buffer_t * self,char reg,char * data,size_t data_len)1014 int buffer_register_set(buffer_t *self, char reg, char *data, size_t data_len) {
1015     MLBUF_ENSURE_AZ(reg);
1016     str_set_len(MLBUF_REG_PTR(self, reg), data, data_len);
1017     return MLBUF_OK;
1018 }
1019 
1020 // Append to register
buffer_register_append(buffer_t * self,char reg,char * data,size_t data_len)1021 int buffer_register_append(buffer_t *self, char reg, char *data, size_t data_len) {
1022     MLBUF_ENSURE_AZ(reg);
1023     str_append_len(MLBUF_REG_PTR(self, reg), data, data_len);
1024     return MLBUF_OK;
1025 }
1026 
1027 // Prepend to register
buffer_register_prepend(buffer_t * self,char reg,char * data,size_t data_len)1028 int buffer_register_prepend(buffer_t *self, char reg, char *data, size_t data_len) {
1029     MLBUF_ENSURE_AZ(reg);
1030     str_prepend_len(MLBUF_REG_PTR(self, reg), data, data_len);
1031     return MLBUF_OK;
1032 }
1033 
1034 // Clear register
buffer_register_clear(buffer_t * self,char reg)1035 int buffer_register_clear(buffer_t *self, char reg) {
1036     MLBUF_ENSURE_AZ(reg);
1037     str_free(MLBUF_REG_PTR(self, reg));
1038     return MLBUF_OK;
1039 }
1040 
1041 // Get register, optionally allocating duplicate
buffer_register_get(buffer_t * self,char reg,int dup,char ** ret_data,size_t * ret_data_len)1042 int buffer_register_get(buffer_t *self, char reg, int dup, char **ret_data, size_t *ret_data_len) {
1043     str_t *sreg;
1044     MLBUF_ENSURE_AZ(reg);
1045     sreg = MLBUF_REG_PTR(self, reg);
1046     if (dup) {
1047         *ret_data = strndup(sreg->data, sreg->len);
1048         *ret_data_len = strlen(*ret_data);
1049     } else {
1050         *ret_data = sreg->len > 0 ? sreg->data : "";
1051         *ret_data_len = sreg->len;
1052     }
1053     return MLBUF_OK;
1054 }
1055 
_buffer_open_mmap(buffer_t * self,int fd,size_t size)1056 static int _buffer_open_mmap(buffer_t *self, int fd, size_t size) {
1057     char tmppath[16];
1058     int tmpfd;
1059     char readbuf[1024];
1060     ssize_t nread;
1061     char *mmap_buf;
1062 
1063     // Copy fd to tmp file
1064     sprintf(tmppath, "%s", "/tmp/mle-XXXXXX");
1065     tmpfd = mkstemp(tmppath);
1066     if (tmpfd < 0) {
1067         return MLBUF_ERR;
1068     }
1069     unlink(tmppath);
1070     while (1) {
1071         nread = read(fd, &readbuf, 1024);
1072         if (nread == 0) {
1073             break;
1074         } else if (nread < 0) {
1075             close(tmpfd);
1076             return MLBUF_ERR;
1077         }
1078         if (write(tmpfd, readbuf, nread) != nread) {
1079             close(tmpfd);
1080             return MLBUF_ERR;
1081         }
1082     }
1083 
1084     // Now mmap tmp file
1085     mmap_buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, tmpfd, 0);
1086     if (mmap_buf == MAP_FAILED) {
1087         return MLBUF_ERR;
1088     } else if (buffer_set_mmapped(self, mmap_buf, (bint_t)size) != MLBUF_OK) {
1089         return MLBUF_ERR;
1090     }
1091 
1092     _buffer_munmap(self);
1093     self->mmap = mmap_buf;
1094     self->mmap_len = size;
1095     self->mmap_fd = tmpfd;
1096     return MLBUF_OK;
1097 }
1098 
_buffer_open_read(buffer_t * self,int fd,size_t size)1099 static int _buffer_open_read(buffer_t *self, int fd, size_t size) {
1100     int rc;
1101     char *buf;
1102     if (size <= PTRDIFF_MAX) {
1103         buf = malloc(size);
1104     } else {
1105         return MLBUF_ERR;
1106     }
1107     rc = MLBUF_OK;
1108     if (size != (size_t)read(fd, buf, size)) {
1109         rc = MLBUF_ERR;
1110     } else if (buffer_set(self, buf, (bint_t)size) != MLBUF_OK) {
1111         rc = MLBUF_ERR;
1112     }
1113     free(buf);
1114     return rc;
1115 }
1116 
_buffer_bline_unslab(bline_t * self)1117 static int _buffer_bline_unslab(bline_t *self) {
1118     char *data;
1119     bline_char_t *chars;
1120     if (!self->is_data_slabbed) {
1121         return MLBUF_ERR;
1122     }
1123     data = malloc(self->data_len);
1124     chars = malloc(self->data_len * sizeof(bline_char_t));
1125     memcpy(data, self->data, self->data_len);
1126     memcpy(chars, self->chars, self->data_len * sizeof(bline_char_t));
1127     self->data = data;
1128     self->data_cap = self->data_len;
1129     self->chars = chars;
1130     self->chars_cap = self->data_len;
1131     self->is_data_slabbed = 0;
1132     return bline_count_chars(self);
1133 }
1134 
_buffer_stat(buffer_t * self)1135 static void _buffer_stat(buffer_t *self) {
1136     if (!self->path) {
1137         return;
1138     }
1139     stat(self->path, &self->st); // TODO err?
1140 }
1141 
_buffer_baction_do(buffer_t * self,bline_t * bline,baction_t * action,int is_redo,bint_t * opt_repeat_offset)1142 static int _buffer_baction_do(buffer_t *self, bline_t *bline, baction_t *action, int is_redo, bint_t *opt_repeat_offset) {
1143     int rc;
1144     bint_t col;
1145     bint_t offset;
1146     self->_is_in_undo = 1;
1147     col = opt_repeat_offset ? *opt_repeat_offset : action->start_col;
1148     buffer_get_offset(self, bline, col, &offset);
1149     if ((action->type == MLBUF_BACTION_TYPE_DELETE && is_redo)
1150         || (action->type == MLBUF_BACTION_TYPE_INSERT && !is_redo)
1151     ) {
1152         rc = buffer_delete(self, offset, (bint_t)((is_redo ? -1 : 1) * action->char_delta));
1153     } else {
1154         rc = buffer_insert(self, offset, action->data, action->data_len, NULL);
1155     }
1156     self->_is_in_undo = 0;
1157     return rc;
1158 }
1159 
_buffer_update(buffer_t * self,baction_t * action)1160 static int _buffer_update(buffer_t *self, baction_t *action) {
1161     bline_t *tmp_line;
1162     bline_t *last_line;
1163     bint_t new_line_index;
1164 
1165     // Adjust counts
1166     self->byte_count += action->byte_delta;
1167     self->line_count += action->line_delta;
1168     self->is_data_dirty = 1;
1169 
1170     // Set unsaved
1171     self->is_unsaved = 1;
1172 
1173     // Renumber lines
1174     if (action->line_delta != 0) {
1175         last_line = NULL;
1176         new_line_index = action->start_line->line_index;
1177         for (tmp_line = action->start_line->next; tmp_line != NULL; tmp_line = tmp_line->next) {
1178             tmp_line->line_index = ++new_line_index;
1179             last_line = tmp_line;
1180         }
1181         self->last_line = last_line ? last_line : action->start_line;
1182     }
1183 
1184     // Restyle from start_line
1185     buffer_apply_styles(self, action->start_line, action->line_delta);
1186 
1187     // Raise event on listener
1188     if (self->callback && !self->is_in_callback) {
1189         self->is_in_callback = 1;
1190         self->callback(self, action, self->callback_udata);
1191         self->is_in_callback = 0;
1192     }
1193 
1194     // Handle undo stack
1195     if (self->_is_in_undo) {
1196         _baction_destroy(action);
1197     } else {
1198         _buffer_add_to_undo_stack(self, action);
1199     }
1200 
1201     return MLBUF_OK;
1202 }
1203 
_buffer_truncate_undo_stack(buffer_t * self,baction_t * action_from)1204 static int _buffer_truncate_undo_stack(buffer_t *self, baction_t *action_from) {
1205     baction_t *action_target;
1206     baction_t *action_tmp;
1207     int do_delete;
1208     self->action_tail = action_from->prev != action_from ? action_from->prev : NULL;
1209     do_delete = 0;
1210     DL_FOREACH_SAFE(self->actions, action_target, action_tmp) {
1211         if (!do_delete && action_target == action_from) {
1212             do_delete = 1;
1213         }
1214         if (do_delete) {
1215             DL_DELETE(self->actions, action_target);
1216             _baction_destroy(action_target);
1217         }
1218     }
1219     return MLBUF_OK;
1220 }
1221 
_buffer_add_to_undo_stack(buffer_t * self,baction_t * action)1222 static int _buffer_add_to_undo_stack(buffer_t *self, baction_t *action) {
1223     if (self->action_undone) {
1224         // We are recording an action after an undo has been performed, so we
1225         // need to chop off the tail of the baction list before recording the
1226         // new one.
1227         // TODO could implement multilevel undo here instead
1228         _buffer_truncate_undo_stack(self, self->action_undone);
1229         self->action_undone = NULL;
1230     }
1231 
1232     // Append action to list
1233     DL_APPEND(self->actions, action);
1234     self->action_tail = action;
1235     return MLBUF_OK;
1236 }
1237 
_buffer_apply_styles_singles(bline_t * start_line,bint_t min_nlines)1238 static int _buffer_apply_styles_singles(bline_t *start_line, bint_t min_nlines) {
1239     bline_t *cur_line;
1240     srule_node_t *srule_node;
1241     bint_t styled_nlines;
1242     bint_t i;
1243 
1244     // Apply styles starting at start_line
1245     cur_line = start_line;
1246     styled_nlines = 0;
1247     while (cur_line && styled_nlines < min_nlines) {
1248         // Reset styles of cur_line
1249         for (i = 0; i < cur_line->data_cap; i++) {
1250             cur_line->chars[i].style = (sblock_t){0, 0};
1251         }
1252 
1253         // Apply single-line styles to cur_line
1254         if (cur_line->data_len > 0) {
1255             DL_FOREACH(start_line->buffer->single_srules, srule_node) {
1256                 _buffer_bline_apply_style_single(srule_node->srule, cur_line);
1257             }
1258         }
1259 
1260         // Done styling cur_line; increment styled_nlines
1261         styled_nlines += 1;
1262 
1263         // Continue to next line
1264         cur_line = cur_line->next;
1265     } // end while (cur_line)
1266 
1267     return MLBUF_OK;
1268 }
1269 
_buffer_apply_styles_multis(bline_t * start_line,bint_t min_nlines,int srule_type)1270 static int _buffer_apply_styles_multis(bline_t *start_line, bint_t min_nlines, int srule_type) {
1271     bline_t *cur_line;
1272     srule_node_t *srule_node;
1273     srule_t *open_rule;
1274     bint_t styled_nlines;
1275     bint_t multi_look_offset;
1276     int open_rule_ended;
1277     int already_open;
1278 
1279     // Apply styles starting at start_line
1280     cur_line = start_line;
1281     open_rule = NULL;
1282     styled_nlines = 0;
1283     open_rule_ended = 0;
1284     multi_look_offset = 0;
1285     while (cur_line) {
1286         if (cur_line->prev && cur_line->prev->eol_rule && !open_rule && !open_rule_ended) {
1287             // Resume open_rule from previous line
1288             open_rule = cur_line->prev->eol_rule;
1289         }
1290         if (!open_rule_ended) {
1291             multi_look_offset = 0;
1292         }
1293         if (open_rule) {
1294             // Apply open_rule to cur_line
1295             already_open = cur_line->eol_rule == open_rule ? 1 : 0;
1296             _buffer_bline_apply_style_multi(open_rule, cur_line, &open_rule, &multi_look_offset);
1297             if (open_rule) {
1298                 // open_rule is still open
1299                 if (styled_nlines > min_nlines && already_open) {
1300                     // We are past min_nlines and styles have not changed; done
1301                     break;
1302                 }
1303             } else {
1304                 // open_rule ended on this line; resume normal styling on same line
1305                 open_rule_ended = 1;
1306                 continue;
1307             }
1308         } else {
1309             if (srule_type == MLBUF_SRULE_TYPE_MULTI) {
1310                 // Re-apply single line rules if a multi-line rule was resolved
1311                 if (cur_line->prev && cur_line->bol_rule != cur_line->prev->eol_rule) {
1312                     _buffer_apply_styles_singles(cur_line, 1);
1313                 }
1314                 // Reset bol_rule and eol_rule
1315                 if (!open_rule_ended) cur_line->bol_rule = NULL;
1316                 cur_line->eol_rule = NULL;
1317             }
1318             // Apply multi-line styles to cur_line
1319             DL_FOREACH(start_line->buffer->multi_srules, srule_node) {
1320                 if (srule_node->srule->type != srule_type) continue;
1321                 _buffer_bline_apply_style_multi(srule_node->srule, cur_line, &open_rule, &multi_look_offset);
1322                 multi_look_offset = 0;
1323                 if (open_rule) break; // We have an open_rule; break
1324             }
1325         }
1326 
1327         // Done styling cur_line; increment styled_nlines
1328         styled_nlines += 1;
1329 
1330         // If there is no open_rule and we are past min_nlines, we are done.
1331         if (!open_rule && (!cur_line->next || !cur_line->next->bol_rule) && styled_nlines > min_nlines) {
1332             break;
1333         }
1334 
1335         // Reset open_rule_ended flag
1336         if (open_rule_ended) open_rule_ended = 0;
1337 
1338         // Continue to next line
1339         cur_line = cur_line->next;
1340     } // end while (cur_line)
1341 
1342     return MLBUF_OK;
1343 }
1344 
_buffer_bline_apply_style_single(srule_t * srule,bline_t * bline)1345 static int _buffer_bline_apply_style_single(srule_t *srule, bline_t *bline) {
1346     int rc;
1347     int substrs[3];
1348     bint_t start;
1349     bint_t stop;
1350     bint_t look_offset;
1351     look_offset = 0;
1352 
1353     MLBUF_BLINE_ENSURE_CHARS(bline);
1354     while (look_offset < bline->data_len) {
1355         if ((rc = pcre_exec(srule->cre, srule->crex, bline->data, bline->data_len, look_offset, 0, substrs, 3)) >= 0) {
1356             if (substrs[1] < 0) {
1357                 // substrs[0..1] can be -1 sometimes, See http://pcre.org/pcre.txt
1358                 break;
1359             }
1360             start = _buffer_bline_index_to_col(bline, substrs[0]);
1361             stop = _buffer_bline_index_to_col(bline, substrs[1]);
1362             for (; start < stop; start++) {
1363                 bline->chars[start].style = srule->style;
1364             }
1365             look_offset = MLBUF_MAX(substrs[1], look_offset + 1);
1366         } else {
1367             break;
1368         }
1369     }
1370     return MLBUF_OK;
1371 }
1372 
_buffer_bline_apply_style_multi(srule_t * srule,bline_t * bline,srule_t ** open_rule,bint_t * look_offset)1373 static int _buffer_bline_apply_style_multi(srule_t *srule, bline_t *bline, srule_t **open_rule, bint_t *look_offset) {
1374     bint_t start;
1375     bint_t start_stop;
1376     bint_t end;
1377     int found_start;
1378     int found_end;
1379 
1380     if (srule->type == MLBUF_SRULE_TYPE_RANGE
1381         && mark_is_eq(srule->range_a, srule->range_b)
1382     ) {
1383         // Empty range rule
1384         return MLBUF_OK;
1385     }
1386 
1387     MLBUF_BLINE_ENSURE_CHARS(bline);
1388 
1389     do {
1390         found_start = 0;
1391         found_end = 0;
1392         if (*open_rule == NULL) {
1393             // Look for start and end of rule
1394             if ((found_start = _srule_multi_find_start(srule, bline, *look_offset, &start, &start_stop))) {
1395                 *look_offset = start_stop;
1396                 found_end = *look_offset < bline->char_count
1397                     ? _srule_multi_find_end(srule, bline, *look_offset, &end)
1398                     : 0;
1399                 if (found_end) *look_offset = end;
1400             } else {
1401                 return MLBUF_OK; // No match; bail
1402             }
1403         } else {
1404             // Look for end of rule
1405             start = 0;
1406             bline->bol_rule = srule;
1407             found_end = _srule_multi_find_end(srule, bline, *look_offset, &end);
1408         }
1409 
1410         // Set start, end, bol_rule, and eol_rule
1411         if (!found_end) {
1412             end = bline->char_count; // Style until eol
1413             bline->eol_rule = srule; // Set eol_rule
1414             *open_rule = srule;
1415         } else if (*open_rule != NULL) {
1416             *open_rule = NULL;
1417         }
1418 
1419         // Write styles
1420         for (; start < end; start++) {
1421             bline->chars[start].style = srule->style;
1422         }
1423 
1424         // Range rules can only match once
1425         if (srule->type == MLBUF_SRULE_TYPE_RANGE) {
1426             break;
1427         }
1428     } while (found_start && found_end && *look_offset < bline->char_count);
1429 
1430     return MLBUF_OK;
1431 }
1432 
_buffer_bline_new(buffer_t * self)1433 static bline_t *_buffer_bline_new(buffer_t *self) {
1434     bline_t *bline;
1435     bline = calloc(1, sizeof(bline_t));
1436     bline->buffer = self;
1437     return bline;
1438 }
1439 
_buffer_bline_free(bline_t * bline,bline_t * maybe_mark_line,bint_t col_delta)1440 static int _buffer_bline_free(bline_t *bline, bline_t *maybe_mark_line, bint_t col_delta) {
1441     mark_t *mark;
1442     mark_t *mark_tmp;
1443     if (!bline->is_data_slabbed) {
1444         if (bline->data) free(bline->data);
1445         if (bline->chars) free(bline->chars);
1446     }
1447     if (bline->marks) {
1448         DL_FOREACH_SAFE(bline->marks, mark, mark_tmp) {
1449             if (maybe_mark_line) {
1450                 _mark_mark_move_inner(mark, maybe_mark_line, mark->col + col_delta, 1, 0);
1451             } else {
1452                 buffer_destroy_mark(bline->buffer, mark);
1453             }
1454         }
1455     }
1456     if (!bline->is_slabbed) {
1457         free(bline);
1458     }
1459     return MLBUF_OK;
1460 }
1461 
_buffer_bline_break(bline_t * bline,bint_t col)1462 static bline_t *_buffer_bline_break(bline_t *bline, bint_t col) {
1463     bint_t index;
1464     bint_t len;
1465     bline_t *new_line;
1466     bline_t *tmp_line;
1467     mark_t *mark;
1468     mark_t *mark_tmp;
1469 
1470     // Unslab if needed
1471     if (bline->is_data_slabbed) _buffer_bline_unslab(bline);
1472 
1473     // Make new_line
1474     new_line = _buffer_bline_new(bline->buffer);
1475 
1476     // Find byte index to break on
1477     index = _buffer_bline_col_to_index(bline, col);
1478     len = bline->data_len - index;
1479 
1480     if (len > 0) {
1481         // Move data to new line
1482         new_line->data = malloc(len);
1483         memcpy(new_line->data, bline->data + index, len);
1484         new_line->data_len = len;
1485         new_line->data_cap = len;
1486         bline_count_chars(new_line); // Update char widths
1487 
1488         // Truncate orig line
1489         bline->data_len -= len;
1490         bline_count_chars(bline); // Update char widths
1491     }
1492 
1493     // Insert new_line in linked list
1494     tmp_line = bline->next;
1495     bline->next = new_line;
1496     new_line->next = tmp_line;
1497     new_line->prev = bline;
1498     if (tmp_line) tmp_line->prev = new_line;
1499 
1500     // Move marks at or past col to new_line
1501     DL_FOREACH_SAFE(bline->marks, mark, mark_tmp) {
1502         if (mark->col >= col) {
1503             _mark_mark_move_inner(mark, new_line, mark->col - col, 1, 0);
1504         }
1505     }
1506 
1507     return new_line;
1508 }
1509 
1510 // Given start_line:start_col + num_chars, find end_line:end_col
_buffer_find_end_pos(bline_t * start_line,bint_t start_col,bint_t num_chars,bline_t ** ret_end_line,bint_t * ret_end_col,bint_t * ret_safe_num_chars)1511 static void _buffer_find_end_pos(bline_t *start_line, bint_t start_col, bint_t num_chars, bline_t **ret_end_line, bint_t *ret_end_col, bint_t *ret_safe_num_chars) {
1512     bline_t *end_line;
1513     bint_t end_col;
1514     bint_t num_chars_rem;
1515     end_line = start_line;
1516     end_col = start_col;
1517     num_chars_rem = num_chars;
1518     while (num_chars_rem > 0) {
1519         MLBUF_BLINE_ENSURE_CHARS(end_line);
1520         if (end_line->char_count - end_col >= num_chars_rem) {
1521             end_col += num_chars_rem;
1522             num_chars_rem = 0;
1523         } else {
1524             num_chars_rem -= (end_line->char_count - end_col) + 1;
1525             if (end_line->next) {
1526                 end_line = end_line->next;
1527                 end_col = 0;
1528             } else {
1529                 end_col = end_line->char_count;
1530                 break;
1531             }
1532         }
1533     }
1534     num_chars -= num_chars_rem;
1535     *ret_end_line = end_line;
1536     *ret_end_col = end_col;
1537     *ret_safe_num_chars = num_chars;
1538 }
1539 
_buffer_bline_replace(bline_t * bline,bint_t start_col,char * data,bint_t data_len,str_t * del_data)1540 static void _buffer_bline_replace(bline_t *bline, bint_t start_col, char *data, bint_t data_len, str_t *del_data) {
1541     bint_t start_index;
1542     mark_t *mark;
1543 
1544     // Unslab if needed
1545     if (bline->is_data_slabbed) _buffer_bline_unslab(bline);
1546 
1547     // Realloc if needed
1548     MLBUF_BLINE_ENSURE_CHARS(bline);
1549     if (start_col <= 0) {
1550         start_index = 0;
1551     } else if (start_col >= bline->char_count) {
1552         start_index = bline->data_len;
1553     } else {
1554         start_index = bline->chars[start_col].index;
1555     }
1556     if (start_index + data_len >= bline->data_cap) {
1557         bline->data = realloc(bline->data, start_index + data_len);
1558         bline->data_cap = start_index + data_len;
1559     }
1560 
1561     // Store del_data
1562     str_append_len(del_data, bline->data + start_index, bline->data_len - start_index);
1563 
1564     // Copy data into slot and update chars
1565     memmove(bline->data + start_index, data, (size_t)data_len);
1566     bline->data_len = start_index + data_len;
1567     bline_count_chars(bline);
1568 
1569     // Fix marks
1570     DL_FOREACH(bline->marks, mark) {
1571         if (mark->col > bline->char_count) {
1572             mark->col = bline->char_count;
1573         }
1574     }
1575 }
1576 
_buffer_bline_insert(bline_t * bline,bint_t col,char * data,bint_t data_len,int move_marks)1577 static bint_t _buffer_bline_insert(bline_t *bline, bint_t col, char *data, bint_t data_len, int move_marks) {
1578     bint_t index;
1579     mark_t *mark;
1580     mark_t *mark_tmp;
1581     bint_t orig_char_count;
1582     bint_t num_chars_added;
1583 
1584     // Unslab if needed
1585     if (bline->is_data_slabbed) _buffer_bline_unslab(bline);
1586 
1587     // Get orig char_count
1588     MLBUF_BLINE_ENSURE_CHARS(bline);
1589     orig_char_count = bline->char_count;
1590 
1591     // Ensure space for data
1592     if (!bline->data) {
1593         bline->data = malloc(data_len);
1594         bline->data_cap = data_len;
1595     } else if (bline->data_len + data_len > bline->data_cap) {
1596         bline->data = realloc(bline->data, bline->data_len + data_len);
1597         bline->data_cap = bline->data_len + data_len;
1598     }
1599 
1600     // Find insert point
1601     index = _buffer_bline_col_to_index(bline, col);
1602 
1603     // Make room for insert data
1604     if (index < bline->data_len) {
1605         memmove(bline->data + index + data_len, bline->data + index, bline->data_len - index);
1606     }
1607     bline->data_len += data_len;
1608 
1609     // Insert data
1610     memcpy(bline->data + index, data, data_len);
1611 
1612     // Update chars
1613     bline_count_chars(bline);
1614     num_chars_added = bline->char_count - orig_char_count;
1615 
1616     // Move marks after col right by num_chars_added
1617     if (move_marks) {
1618         DL_FOREACH_SAFE(bline->marks, mark, mark_tmp) {
1619             if (mark_is_after_col_minus_lefties(mark, col)) {
1620                 mark->col += num_chars_added;
1621             }
1622         }
1623     }
1624 
1625     return num_chars_added;
1626 }
1627 
_buffer_bline_delete(bline_t * bline,bint_t col,bint_t num_chars)1628 static bint_t _buffer_bline_delete(bline_t *bline, bint_t col, bint_t num_chars) {
1629     bint_t safe_num_chars;
1630     bint_t index;
1631     bint_t index_end;
1632     bint_t move_len;
1633     mark_t *mark;
1634     mark_t *mark_tmp;
1635     bint_t orig_char_count;
1636     bint_t num_chars_deleted;
1637 
1638     // Unslab if needed
1639     if (bline->is_data_slabbed) _buffer_bline_unslab(bline);
1640 
1641     // Get orig char_count
1642     MLBUF_BLINE_ENSURE_CHARS(bline);
1643     orig_char_count = bline->char_count;
1644 
1645     // Clamp num_chars
1646     safe_num_chars = MLBUF_MIN(bline->char_count - col, num_chars);
1647     if (safe_num_chars != num_chars) {
1648         MLBUF_DEBUG_PRINTF("num_chars=%" PRIdMAX " does not match safe_num_chars=%" PRIdMAX "\n", num_chars, safe_num_chars);
1649     }
1650 
1651     // Nothing to do if safe_num_chars is 0
1652     if (safe_num_chars < 1) {
1653         MLBUF_DEBUG_PRINTF("safe_num_chars=%" PRIdMAX " lt 1\n", safe_num_chars);
1654         return MLBUF_OK;
1655     }
1656 
1657     // Find delete bounds
1658     index = _buffer_bline_col_to_index(bline, col);
1659     index_end = _buffer_bline_col_to_index(bline, col + safe_num_chars);
1660     move_len = (bint_t)(bline->data_len - index_end);
1661 
1662     // Shift data
1663     if (move_len > 0) {
1664         memmove(bline->data + index, bline->data + index_end, move_len);
1665     }
1666     bline->data_len -= index_end - index;
1667 
1668     // Update chars
1669     bline_count_chars(bline);
1670     num_chars_deleted = orig_char_count - bline->char_count;
1671 
1672     // Move marks after col left by num_chars_deleted
1673     DL_FOREACH_SAFE(bline->marks, mark, mark_tmp) {
1674         if (mark->col > col) {
1675             mark->col = MLBUF_MAX(0, mark->col - num_chars_deleted);
1676         }
1677     }
1678 
1679     return num_chars_deleted;
1680 }
1681 
_buffer_bline_col_to_index(bline_t * bline,bint_t col)1682 static bint_t _buffer_bline_col_to_index(bline_t *bline, bint_t col) {
1683     bint_t index;
1684     MLBUF_BLINE_ENSURE_CHARS(bline);
1685     if (!bline->chars) {
1686         return 0;
1687     }
1688     if (col >= bline->char_count) {
1689         index = bline->data_len;
1690     } else {
1691         index = bline->chars[col].index;
1692     }
1693     return index;
1694 }
1695 
_buffer_bline_index_to_col(bline_t * bline,bint_t index)1696 static bint_t _buffer_bline_index_to_col(bline_t *bline, bint_t index) {
1697     MLBUF_BLINE_ENSURE_CHARS(bline);
1698     if (index < 1) {
1699         return 0;
1700     } else if (index >= bline->data_len) {
1701         return bline->char_count;
1702     }
1703     return bline->chars[index].index_to_vcol;
1704 }
1705 
1706 // Close self->fd and self->mmap if needed
_buffer_munmap(buffer_t * self)1707 static int _buffer_munmap(buffer_t *self) {
1708     if (self->mmap) {
1709         munmap(self->mmap, self->mmap_len);
1710         close(self->mmap_fd);
1711         self->mmap = NULL;
1712         self->mmap_len = 0;
1713         self->mmap_fd = -1;
1714     }
1715     return MLBUF_OK;
1716 }
1717 
1718 // Make a new single-line style rule
srule_new_single(char * re,bint_t re_len,int caseless,uint16_t fg,uint16_t bg)1719 srule_t *srule_new_single(char *re, bint_t re_len, int caseless, uint16_t fg, uint16_t bg) {
1720     srule_t *rule;
1721     const char *re_error;
1722     int re_erroffset;
1723     rule = calloc(1, sizeof(srule_t));
1724     rule->type = MLBUF_SRULE_TYPE_SINGLE;
1725     rule->style.fg = fg;
1726     rule->style.bg = bg;
1727     rule->re = malloc((re_len + 1) * sizeof(char));
1728     snprintf(rule->re, re_len + 1, "%.*s", (int)re_len, re);
1729     rule->cre = pcre_compile((const char*)rule->re, PCRE_NO_AUTO_CAPTURE | (caseless ? PCRE_CASELESS : 0), &re_error, &re_erroffset, NULL);
1730     if (!rule->cre) {
1731         // TODO log error
1732         srule_destroy(rule);
1733         return NULL;
1734     }
1735     rule->crex = pcre_study(rule->cre, PCRE_STUDY_JIT_COMPILE, &re_error);
1736     return rule;
1737 }
1738 
1739 // Make a new multi-line style rule
srule_new_multi(char * re,bint_t re_len,char * re_end,bint_t re_end_len,uint16_t fg,uint16_t bg)1740 srule_t *srule_new_multi(char *re, bint_t re_len, char *re_end, bint_t re_end_len, uint16_t fg, uint16_t bg) {
1741     srule_t *rule;
1742     const char *re_error;
1743     int re_erroffset;
1744     rule = calloc(1, sizeof(srule_t));
1745     rule->type = MLBUF_SRULE_TYPE_MULTI;
1746     rule->style.fg = fg;
1747     rule->style.bg = bg;
1748     rule->re = malloc((re_len + 1) * sizeof(char));
1749     rule->re_end = malloc((re_end_len + 1) * sizeof(char));
1750     snprintf(rule->re, re_len + 1, "%.*s", (int)re_len, re);
1751     snprintf(rule->re_end, re_end_len + 1, "%.*s", (int)re_end_len, re_end);
1752     rule->cre = pcre_compile((const char*)rule->re, PCRE_NO_AUTO_CAPTURE, &re_error, &re_erroffset, NULL);
1753     rule->cre_end = pcre_compile((const char*)rule->re_end, PCRE_NO_AUTO_CAPTURE, &re_error, &re_erroffset, NULL);
1754     if (!rule->cre || !rule->cre_end) {
1755         // TODO log error
1756         srule_destroy(rule);
1757         return NULL;
1758     }
1759     rule->crex = pcre_study(rule->cre, PCRE_STUDY_JIT_COMPILE, &re_error);
1760     rule->crex_end = pcre_study(rule->cre_end, PCRE_STUDY_JIT_COMPILE, &re_error);
1761     return rule;
1762 }
1763 
1764 // Make a new range style rule
srule_new_range(mark_t * range_a,mark_t * range_b,uint16_t fg,uint16_t bg)1765 srule_t *srule_new_range(mark_t *range_a, mark_t *range_b, uint16_t fg, uint16_t bg) {
1766     srule_t *rule;
1767     rule = calloc(1, sizeof(srule_t));
1768     rule->type = MLBUF_SRULE_TYPE_RANGE;
1769     rule->style.fg = fg;
1770     rule->style.bg = bg;
1771     rule->range_a = range_a;
1772     rule->range_b = range_b;
1773     return rule;
1774 }
1775 
1776 // Free an srule
srule_destroy(srule_t * srule)1777 int srule_destroy(srule_t *srule) {
1778     if (srule->re) free(srule->re);
1779     if (srule->re_end) free(srule->re_end);
1780     if (srule->cre) pcre_free(srule->cre);
1781     if (srule->cre_end) pcre_free(srule->cre_end);
1782     if (srule->crex) pcre_free_study_ex(srule->crex);
1783     if (srule->crex_end) pcre_free_study_ex(srule->crex_end);
1784     free(srule);
1785     return MLBUF_OK;
1786 }
1787 
_srule_multi_find(srule_t * rule,int find_end,bline_t * bline,bint_t start_offset,bint_t * ret_start,bint_t * ret_stop)1788 static int _srule_multi_find(srule_t *rule, int find_end, bline_t *bline, bint_t start_offset, bint_t *ret_start, bint_t *ret_stop) {
1789     int rc;
1790     pcre *cre;
1791     pcre_extra *crex;
1792     int substrs[3];
1793     bint_t start_index;
1794     mark_t *mark;
1795 
1796     if (rule->type == MLBUF_SRULE_TYPE_RANGE) {
1797         mark = mark_is_gt(rule->range_a, rule->range_b)
1798             ? (find_end ? rule->range_a : rule->range_b)
1799             : (find_end ? rule->range_b : rule->range_a);
1800         if (mark->bline == bline && mark->col >= start_offset) {
1801             *ret_start = mark->col;
1802             *ret_stop = mark->col;
1803             return 1;
1804         }
1805         return 0;
1806     }
1807 
1808     // MLBUF_SRULE_TYPE_MULTI
1809     cre = find_end ? rule->cre_end : rule->cre;
1810     crex = find_end ? rule->crex_end : rule->crex;
1811     start_index = _buffer_bline_col_to_index(bline, start_offset);
1812     if ((rc = pcre_exec(cre, crex, bline->data, bline->data_len, start_index, 0, substrs, 3)) >= 0) {
1813         *ret_start = _buffer_bline_index_to_col(bline, substrs[0]);
1814         *ret_stop = _buffer_bline_index_to_col(bline, substrs[1]);
1815         return 1;
1816     }
1817     return 0;
1818 }
1819 
_srule_multi_find_start(srule_t * rule,bline_t * bline,bint_t start_offset,bint_t * ret_start,bint_t * ret_stop)1820 static int _srule_multi_find_start(srule_t *rule, bline_t *bline, bint_t start_offset, bint_t *ret_start, bint_t *ret_stop) {
1821     return _srule_multi_find(rule, 0, bline, start_offset, ret_start, ret_stop);
1822 }
1823 
_srule_multi_find_end(srule_t * rule,bline_t * bline,bint_t start_offset,bint_t * ret_stop)1824 static int _srule_multi_find_end(srule_t *rule, bline_t *bline, bint_t start_offset, bint_t *ret_stop) {
1825     bint_t ignore;
1826     return _srule_multi_find(rule, 1, bline, start_offset, &ignore, ret_stop);
1827 }
1828 
_baction_destroy(baction_t * action)1829 static int _baction_destroy(baction_t *action) {
1830     if (action->data) free(action->data);
1831     free(action);
1832     return MLBUF_OK;
1833 }
1834