1 #include <math.h>
2 #include <unistd.h>
3 #include <wctype.h>
4 #include "mle.h"
5 
6 static int _bview_rectify_viewport_dim(bview_t *self, bline_t *bline, bint_t vpos, int dim_scope, int dim_size, bint_t *view_vpos);
7 static void _bview_init(bview_t *self, buffer_t *buffer);
8 static void _bview_init_resized(bview_t *self);
9 static kmap_t *_bview_get_init_kmap(editor_t *editor);
10 static void _bview_buffer_callback(buffer_t *buffer, baction_t *action, void *udata);
11 static int _bview_set_linenum_width(bview_t *self);
12 static void _bview_deinit(bview_t *self);
13 static void _bview_set_tab_width(bview_t *self, int tab_width);
14 static void _bview_fix_path(bview_t *self, char *path, int path_len, char **ret_path, int *ret_path_len, bint_t *ret_line_num);
15 static void _bview_expand_tilde(bview_t *self, char *path, int path_len, char **ret_path, int *ret_path_len);
16 static buffer_t *_bview_open_buffer(bview_t *self, char *opt_path, int opt_path_len);
17 static void _bview_draw_prompt(bview_t *self);
18 static void _bview_draw_status(bview_t *self);
19 static void _bview_draw_edit(bview_t *self, int x, int y, int w, int h);
20 static void _bview_draw_bline(bview_t *self, bline_t *bline, int rect_y, bline_t **optret_bline, int *optret_rect_y);
21 static void _bview_highlight_bracket_pair(bview_t *self, mark_t *mark);
22 
23 // Create a new bview
bview_new(editor_t * editor,char * opt_path,int opt_path_len,buffer_t * opt_buffer)24 bview_t *bview_new(editor_t *editor, char *opt_path, int opt_path_len, buffer_t *opt_buffer) {
25     bview_t *self;
26     buffer_t *buffer;
27 
28     // Allocate and init bview
29     self = calloc(1, sizeof(bview_t));
30     self->editor = editor;
31     self->path = strndup(opt_path, opt_path_len);
32     self->rect_caption.fg = TB_WHITE;
33     self->rect_caption.bg = TB_BLACK;
34     self->rect_lines.fg = TB_YELLOW;
35     self->rect_lines.bg = TB_BLACK;
36     self->rect_margin_left.fg = TB_RED;
37     self->rect_margin_right.fg = TB_RED;
38     self->rect_buffer.h = 10; // TODO hack to fix _bview_set_linenum_width before bview_resize
39     self->tab_width = editor->tab_width;
40     self->tab_to_space = editor->tab_to_space;
41     self->viewport_scope_x = editor->viewport_scope_x;
42     self->viewport_scope_y = editor->viewport_scope_y;
43     getcwd(self->init_cwd, PATH_MAX + 1);
44 
45     // Open buffer
46     if (opt_buffer) {
47         buffer = opt_buffer;
48     } else {
49         buffer = _bview_open_buffer(self, opt_path, opt_path_len);
50     }
51     _bview_init(self, buffer);
52 
53     return self;
54 }
55 
56 // Open a buffer in an existing bview
bview_open(bview_t * self,char * path,int path_len)57 int bview_open(bview_t *self, char *path, int path_len) {
58     buffer_t *buffer;
59     buffer = _bview_open_buffer(self, path, path_len);
60     if (self->path) free(self->path);
61     self->path = strndup(path, path_len);
62     _bview_init(self, buffer);
63     bview_resize(self, self->x, self->y, self->w, self->h);
64     return MLE_OK;
65 }
66 
67 // Free a bview
bview_destroy(bview_t * self)68 int bview_destroy(bview_t *self) {
69     _bview_deinit(self);
70     if (self->path) free(self->path);
71     // TODO ensure everything freed
72     free(self);
73     return MLE_OK;
74 }
75 
76 // Move and resize a bview to the given position and dimensions
bview_resize(bview_t * self,int x,int y,int w,int h)77 int bview_resize(bview_t *self, int x, int y, int w, int h) {
78     int aw, ah;
79 
80     self->x = x;
81     self->y = y;
82     self->w = w;
83     self->h = h;
84 
85     aw = w;
86     ah = h;
87 
88     if (self->split_child) {
89         if (self->split_is_vertical) {
90             aw = MLE_MAX(1, (int)((float)aw * self->split_factor));
91         } else {
92             ah = MLE_MAX(1, (int)((float)ah * self->split_factor));
93         }
94     }
95 
96     if (MLE_BVIEW_IS_EDIT(self)) {
97         self->rect_caption.x = x;
98         self->rect_caption.y = y;
99         self->rect_caption.w = aw;
100         self->rect_caption.h = 1;
101 
102         self->rect_lines.x = x;
103         self->rect_lines.y = y + 1;
104         self->rect_lines.w = self->linenum_width;
105         self->rect_lines.h = ah - 1;
106 
107         self->rect_margin_left.x = x + self->linenum_width;
108         self->rect_margin_left.y = y + 1;
109         self->rect_margin_left.w = 1;
110         self->rect_margin_left.h = ah - 1;
111 
112         self->rect_buffer.x = x + self->linenum_width + 1;
113         self->rect_buffer.y = y + 1;
114         self->rect_buffer.w = MLE_MAX(1, aw - (self->linenum_width + 1 + 1));
115         self->rect_buffer.h = ah - 1;
116 
117         self->rect_margin_right.x = x + (aw - 1);
118         self->rect_margin_right.y = y + 1;
119         self->rect_margin_right.w = 1;
120         self->rect_margin_right.h = ah - 1;
121     } else {
122         self->rect_buffer.x = x;
123         self->rect_buffer.y = y;
124         self->rect_buffer.w = aw;
125         self->rect_buffer.h = ah;
126     }
127 
128     if (self->split_child) {
129         bview_resize(
130             self->split_child,
131             x + (self->split_is_vertical ? aw : 0),
132             y + (self->split_is_vertical ? 0 : ah),
133             w - (self->split_is_vertical ? aw : 0),
134             h - (self->split_is_vertical ? 0 : ah)
135         );
136     }
137 
138     if (!self->is_resized) {
139         _bview_init_resized(self);
140         self->is_resized = 1;
141     }
142 
143     bview_rectify_viewport(self);
144 
145     return MLE_OK;
146 }
147 
148 // Return top-most split_parent of a bview
bview_get_split_root(bview_t * self)149 bview_t *bview_get_split_root(bview_t *self) {
150     bview_t *root;
151     root = self;
152     while (root->split_parent) {
153         root = root->split_parent;
154     }
155     return root;
156 }
157 
158 // Draw bview to screen
bview_draw(bview_t * self)159 int bview_draw(bview_t *self) {
160     if (MLE_BVIEW_IS_PROMPT(self)) {
161         _bview_draw_prompt(self);
162     } else if (MLE_BVIEW_IS_STATUS(self)) {
163         _bview_draw_status(self);
164     }
165     _bview_draw_edit(self, self->x, self->y, self->w, self->h);
166     return MLE_OK;
167 }
168 
169 // Set cursor to screen
bview_draw_cursor(bview_t * self,int set_real_cursor)170 int bview_draw_cursor(bview_t *self, int set_real_cursor) {
171     cursor_t *cursor;
172     mark_t *mark;
173     int screen_x;
174     int screen_y;
175     struct tb_cell *cell;
176     DL_FOREACH(self->cursors, cursor) {
177         mark = cursor->mark;
178         if (bview_get_screen_coords(self, mark, &screen_x, &screen_y, &cell) != MLE_OK) {
179             // Out of bounds
180             continue;
181         }
182         if (set_real_cursor && cursor == self->active_cursor) {
183             // Set terminal cursor
184             tb_set_cursor(screen_x, screen_y);
185         } else {
186             // Set fake cursor
187             tb_change_cell(screen_x, screen_y, cell->ch, cell->fg, cell->bg | (cursor->is_asleep ? TB_RED : TB_CYAN)); // TODO configurable
188         }
189         if (self->editor->highlight_bracket_pairs) {
190             _bview_highlight_bracket_pair(self, mark);
191         }
192     }
193     return MLE_OK;
194 }
195 
196 // Push a kmap
bview_push_kmap(bview_t * bview,kmap_t * kmap)197 int bview_push_kmap(bview_t *bview, kmap_t *kmap) {
198     kmap_node_t *node;
199     node = calloc(1, sizeof(kmap_node_t));
200     node->kmap = kmap;
201     node->bview = bview;
202     DL_APPEND(bview->kmap_stack, node);
203     bview->kmap_tail = node;
204     return MLE_OK;
205 }
206 
207 // Pop a kmap
bview_pop_kmap(bview_t * bview,kmap_t ** optret_kmap)208 int bview_pop_kmap(bview_t *bview, kmap_t **optret_kmap) {
209     kmap_node_t *node_to_pop;
210     node_to_pop = bview->kmap_tail;
211     if (!node_to_pop) {
212         return MLE_ERR;
213     }
214     if (optret_kmap) {
215         *optret_kmap = node_to_pop->kmap;
216     }
217     bview->kmap_tail = node_to_pop->prev != node_to_pop ? node_to_pop->prev : NULL;
218     DL_DELETE(bview->kmap_stack, node_to_pop);
219     free(node_to_pop);
220     return MLE_OK;
221 }
222 
223 // Split a bview
bview_split(bview_t * self,int is_vertical,float factor,bview_t ** optret_bview)224 int bview_split(bview_t *self, int is_vertical, float factor, bview_t **optret_bview) {
225     bview_t *child;
226 
227     if (self->split_child) {
228         MLE_RETURN_ERR(self->editor, "bview %p is already split", (void*)self);
229     } else if (!MLE_BVIEW_IS_EDIT(self)) {
230         MLE_RETURN_ERR(self->editor, "bview %p is not an edit bview", (void*)self);
231     }
232 
233     // Make child
234     editor_open_bview(self->editor, self, self->type, NULL, 0, 1, 0, 1, self->buffer, &child);
235     child->split_parent = self;
236     self->split_child = child;
237     self->split_factor = factor;
238     self->split_is_vertical = is_vertical;
239 
240     // Move cursor to same position
241     mark_move_to(child->active_cursor->mark, self->active_cursor->mark->bline->line_index, self->active_cursor->mark->col);
242     bview_center_viewport_y(child);
243 
244     // Resize self
245     bview_resize(self, self->x, self->y, self->w, self->h);
246 
247     if (optret_bview) {
248         *optret_bview = child;
249     }
250     return MLE_OK;
251 }
252 
253 // Return number of active cursors
bview_get_active_cursor_count(bview_t * self)254 int bview_get_active_cursor_count(bview_t *self) {
255     int count;
256     cursor_t *cursor;
257     count = 0;
258     DL_FOREACH(self->cursors, cursor) {
259         if (!cursor->is_asleep) {
260             count += 1;
261         }
262     }
263     return count;
264 }
265 
266 // Add a cursor to a bview
bview_add_cursor(bview_t * self,bline_t * opt_bline,bint_t opt_col,cursor_t ** optret_cursor)267 int bview_add_cursor(bview_t *self, bline_t *opt_bline, bint_t opt_col, cursor_t **optret_cursor) {
268     cursor_t *cursor;
269     cursor = calloc(1, sizeof(cursor_t));
270     cursor->bview = self;
271     if (!opt_bline) opt_bline = self->buffer->first_line;
272     if (opt_col < 0) opt_col = 0;
273     cursor->mark = buffer_add_mark(self->buffer, opt_bline, opt_col);
274     DL_APPEND(self->cursors, cursor);
275     if (!self->active_cursor) {
276         self->active_cursor = cursor;
277     }
278     if (optret_cursor) {
279         *optret_cursor = cursor;
280     }
281     return MLE_OK;
282 }
283 
284 // Add sleeping cursor
bview_add_cursor_asleep(bview_t * self,bline_t * opt_bline,bint_t opt_col,cursor_t ** optret_cursor)285 int bview_add_cursor_asleep(bview_t *self, bline_t *opt_bline, bint_t opt_col, cursor_t **optret_cursor) {
286     cursor_t *cursor;
287     bview_add_cursor(self, opt_bline, opt_col, &cursor);
288     cursor->is_asleep = 1;
289     if (optret_cursor) *optret_cursor = cursor;
290     return MLE_OK;
291 }
292 
293 // Wake all sleeping cursors
bview_wake_sleeping_cursors(bview_t * self)294 int bview_wake_sleeping_cursors(bview_t *self) {
295     cursor_t *cursor;
296     DL_FOREACH(self->cursors, cursor) {
297         if (cursor->is_asleep) {
298             cursor->is_asleep = 0;
299         }
300     }
301     return MLE_OK;
302 }
303 
304 // Remove all cursors except one
bview_remove_cursors_except(bview_t * self,cursor_t * one)305 int bview_remove_cursors_except(bview_t *self, cursor_t *one) {
306     cursor_t *cursor;
307     cursor_t *cursor_tmp;
308     DL_FOREACH_SAFE(self->cursors, cursor, cursor_tmp) {
309         if (cursor != one) {
310             bview_remove_cursor(self, cursor);
311         }
312     }
313     return MLE_OK;
314 }
315 
316 // Remove a cursor from a bview
bview_remove_cursor(bview_t * self,cursor_t * cursor)317 int bview_remove_cursor(bview_t *self, cursor_t *cursor) {
318     cursor_t *el;
319     cursor_t *tmp;
320     DL_FOREACH_SAFE(self->cursors, el, tmp) {
321         if (el == cursor) {
322             self->active_cursor = el->prev && el->prev != el ? el->prev : el->next;
323             DL_DELETE(self->cursors, el);
324             if (el->sel_rule) {
325                 buffer_remove_srule(el->bview->buffer, el->sel_rule);
326                 srule_destroy(el->sel_rule);
327                 el->sel_rule = NULL;
328             }
329             if (el->cut_buffer) free(el->cut_buffer);
330             free(el);
331             return MLE_OK;
332         }
333     }
334     return MLE_ERR;
335 }
336 
337 // Set viewport y safely
bview_set_viewport_y(bview_t * self,bint_t y,int do_rectify)338 int bview_set_viewport_y(bview_t *self, bint_t y, int do_rectify) {
339     if (y < 0) {
340         y = 0;
341     } else if (y >= self->buffer->line_count) {
342         y = self->buffer->line_count - 1;
343     }
344     self->viewport_y = y;
345     if (do_rectify) bview_rectify_viewport(self);
346     buffer_get_bline(self->buffer, self->viewport_y, &self->viewport_bline);
347     return MLE_OK;
348 }
349 
350 // Center the viewport vertically
bview_center_viewport_y(bview_t * self)351 int bview_center_viewport_y(bview_t *self) {
352     bint_t center;
353     center = self->active_cursor->mark->bline->line_index - self->rect_buffer.h/2;
354     if (center < 0) center = 0;
355     return bview_set_viewport_y(self, center, 1);
356 }
357 
358 // Zero the viewport vertically
bview_zero_viewport_y(bview_t * self)359 int bview_zero_viewport_y(bview_t *self) {
360     return bview_set_viewport_y(self, self->active_cursor->mark->bline->line_index, 1);
361 }
362 
363 // Maximize the viewport vertically
bview_max_viewport_y(bview_t * self)364 int bview_max_viewport_y(bview_t *self) {
365     bint_t max;
366     max = self->active_cursor->mark->bline->line_index - self->rect_buffer.h;
367     if (max < 0) max = 0;
368     return bview_set_viewport_y(self, max, 1);
369 }
370 
371 // Rectify the viewport
bview_rectify_viewport(bview_t * self)372 int bview_rectify_viewport(bview_t *self) {
373     mark_t *mark;
374     mark = self->active_cursor->mark;
375 
376     // Rectify each dimension of the viewport
377     MLBUF_BLINE_ENSURE_CHARS(mark->bline);
378     _bview_rectify_viewport_dim(self, mark->bline, MLE_MARK_COL_TO_VCOL(mark), self->viewport_scope_x, self->rect_buffer.w, &self->viewport_x_vcol);
379     bline_get_col_from_vcol(mark->bline, self->viewport_x_vcol, &(self->viewport_x));
380 
381     if (_bview_rectify_viewport_dim(self, mark->bline, mark->bline->line_index, self->viewport_scope_y, self->rect_buffer.h, &self->viewport_y)) {
382         // TODO viewport_y_vrow (soft-wrapped lines, code folding, etc)
383         // Refresh viewport_bline
384         buffer_get_bline(self->buffer, self->viewport_y, &self->viewport_bline);
385     }
386 
387     return MLE_OK;
388 }
389 
390 // Add a listener
bview_add_listener(bview_t * self,bview_listener_cb_t callback,void * udata)391 int bview_add_listener(bview_t *self, bview_listener_cb_t callback, void *udata) {
392     bview_listener_t *listener;
393     listener = calloc(1, sizeof(bview_listener_t));
394     listener->callback = callback;
395     listener->udata = udata;
396     DL_APPEND(self->listeners, listener);
397     return MLE_OK;
398 }
399 
400 // Remove and free a listener
bview_destroy_listener(bview_t * self,bview_listener_t * listener)401 int bview_destroy_listener(bview_t *self, bview_listener_t *listener) {
402     DL_DELETE(self->listeners, listener);
403     free(listener);
404     return MLE_OK;
405 }
406 
407 // Rectify a viewport dimension. Return 1 if changed, else 0.
_bview_rectify_viewport_dim(bview_t * self,bline_t * bline,bint_t vpos,int dim_scope,int dim_size,bint_t * view_vpos)408 static int _bview_rectify_viewport_dim(bview_t *self, bline_t *bline, bint_t vpos, int dim_scope, int dim_size, bint_t *view_vpos) {
409     int rc;
410     bint_t vpos_start;
411     bint_t vpos_stop;
412     (void)self;
413     (void)bline;
414 
415     // Find bounds
416     if (dim_scope < 0) {
417         // Keep cursor at least `dim_scope` cells away from edge
418         // Remember dim_scope is negative here
419         dim_scope = MLE_MAX(dim_scope, ((dim_size / 2) * -1));
420         vpos_start = *view_vpos - dim_scope; // N in from left edge
421         vpos_stop = (*view_vpos + dim_size) + dim_scope; // N in from right edge
422     } else {
423         // Keep cursor within `dim_scope/2` cells of midpoint
424         dim_scope = MLE_MIN(dim_scope, dim_size);
425         vpos_start = (*view_vpos + (dim_size / 2)) - (int)floorf((float)dim_scope * 0.5); // -N/2 from midpoint
426         vpos_stop = (*view_vpos + (dim_size / 2)) + (int)ceilf((float)dim_scope * 0.5); // +N/2 from midpoint
427     }
428 
429     // Rectify
430     rc = 1;
431     if (vpos < vpos_start) {
432         *view_vpos -= MLE_MIN(*view_vpos, vpos_start - vpos);
433     } else if (vpos >= vpos_stop) {
434         *view_vpos += ((vpos - vpos_stop) + 1);
435     } else {
436         rc = 0;
437     }
438 
439     return rc;
440 }
441 
442 // Init a bview with a buffer
_bview_init(bview_t * self,buffer_t * buffer)443 static void _bview_init(bview_t *self, buffer_t *buffer) {
444     cursor_t *cursor_tmp;
445     kmap_t *kmap_init;
446 
447     _bview_deinit(self);
448 
449     // Reference buffer
450     self->buffer = buffer;
451     self->buffer->ref_count += 1;
452     _bview_set_linenum_width(self);
453 
454     // Push normal mode
455     kmap_init = _bview_get_init_kmap(self->editor);
456     if (kmap_init != self->editor->kmap_normal) {
457         // Make kmap_normal the bottom if init kmap isn't kmap_normal
458         bview_push_kmap(self, self->editor->kmap_normal);
459     }
460     bview_push_kmap(self, kmap_init);
461 
462     // Set syntax
463     bview_set_syntax(self, NULL);
464 
465     // Add a cursor
466     bview_add_cursor(self, self->buffer->first_line, 0, &cursor_tmp);
467 }
468 
469 // Invoked once after a bview has been resized for the first time
_bview_init_resized(bview_t * self)470 static void _bview_init_resized(bview_t *self) {
471     // Move cursor to startup line if present
472     if (self->startup_linenum > 0) {
473         mark_move_to(self->active_cursor->mark, self->startup_linenum, 0);
474         bview_center_viewport_y(self);
475     }
476 }
477 
478 // Return initial kmap to use
_bview_get_init_kmap(editor_t * editor)479 static kmap_t *_bview_get_init_kmap(editor_t *editor) {
480     if (!editor->kmap_init) {
481         if (editor->kmap_init_name) {
482             HASH_FIND_STR(editor->kmap_map, editor->kmap_init_name, editor->kmap_init);
483         }
484         if (!editor->kmap_init) {
485             editor->kmap_init = editor->kmap_normal;
486         }
487     }
488     return editor->kmap_init;
489 }
490 
491 // Called by mlbuf after edits
_bview_buffer_callback(buffer_t * buffer,baction_t * action,void * udata)492 static void _bview_buffer_callback(buffer_t *buffer, baction_t *action, void *udata) {
493     editor_t *editor;
494     bview_t *self;
495     bview_t *active;
496     bview_listener_t *listener;
497 
498     self = (bview_t*)udata;
499     editor = self->editor;
500     active = editor->active;
501 
502     // Rectify viewport if edit was on active bview
503     if (active->buffer == buffer) {
504         bview_rectify_viewport(active);
505     }
506 
507     if (action && action->line_delta != 0) {
508         bview_t *bview;
509         bview_t *tmp1;
510         bview_t *tmp2;
511         CDL_FOREACH_SAFE2(editor->all_bviews, bview, tmp1, tmp2, all_prev, all_next) {
512             if (bview->buffer == buffer) {
513                 // Adjust linenum_width
514                 if (_bview_set_linenum_width(bview)) {
515                     bview_resize(bview, bview->x, bview->y, bview->w, bview->h);
516                 }
517                 // Adjust viewport_bline
518                 buffer_get_bline(bview->buffer, bview->viewport_y, &bview->viewport_bline);
519             }
520         }
521     }
522 
523     // Call bview listeners
524     DL_FOREACH(self->listeners, listener) {
525         listener->callback(self, action, listener->udata);
526     }
527 
528     // Notify event observers
529     editor_notify_observers(editor, "buffer:baction", (void*)action);
530 }
531 
532 // Set linenum_width and return 1 if changed
_bview_set_linenum_width(bview_t * self)533 static int _bview_set_linenum_width(bview_t *self) {
534     int orig;
535     orig = self->linenum_width;
536     self->abs_linenum_width = MLE_MAX(1, (int)(floor(log10((double)self->buffer->line_count))) + 1);
537     if (self->editor->linenum_type != MLE_LINENUM_TYPE_ABS) {
538         self->rel_linenum_width = MLE_MAX(
539             self->editor->linenum_type == MLE_LINENUM_TYPE_BOTH ? 1 : self->abs_linenum_width,
540             (int)(floor(log10((double)self->rect_buffer.h))) + 1
541         );
542     } else {
543         self->rel_linenum_width = 0;
544     }
545     if (self->editor->linenum_type == MLE_LINENUM_TYPE_ABS) {
546         self->linenum_width = self->abs_linenum_width;
547     } else if (self->editor->linenum_type == MLE_LINENUM_TYPE_REL) {
548         self->linenum_width = self->abs_linenum_width > self->rel_linenum_width ? self->abs_linenum_width : self->rel_linenum_width;
549     } else if (self->editor->linenum_type == MLE_LINENUM_TYPE_BOTH) {
550         self->linenum_width = self->abs_linenum_width + 1 + self->rel_linenum_width;
551     }
552     return orig == self->linenum_width ? 0 : 1;
553 }
554 
555 // Deinit a bview
_bview_deinit(bview_t * self)556 static void _bview_deinit(bview_t *self) {
557     bview_listener_t *listener;
558     bview_listener_t *listener_tmp;
559 
560     // Remove all kmaps
561     while (self->kmap_tail) {
562         bview_pop_kmap(self, NULL);
563     }
564 
565     // Remove all syntax rules
566     if (self->syntax) {
567         srule_node_t *srule_node;
568         buffer_set_styles_enabled(self->buffer, 0);
569         DL_FOREACH(self->syntax->srules, srule_node) {
570             buffer_remove_srule(self->buffer, srule_node->srule);
571         }
572         buffer_set_styles_enabled(self->buffer, 1);
573     }
574 
575     // Remove all cursors
576     while (self->active_cursor) {
577         bview_remove_cursor(self, self->active_cursor);
578     }
579 
580     // Destroy async proc
581     if (self->aproc) {
582         aproc_destroy(self->aproc, 1);
583         self->aproc = NULL;
584     }
585 
586     // Remove all listeners
587     DL_FOREACH_SAFE(self->listeners, listener, listener_tmp) {
588         bview_destroy_listener(self, listener);
589     }
590 
591     // Dereference/free buffer
592     if (self->buffer) {
593         self->buffer->ref_count -= 1;
594         if (self->buffer->ref_count < 1) {
595             buffer_destroy(self->buffer);
596         }
597     }
598 
599     // Free last_search
600     if (self->last_search) {
601         free(self->last_search);
602     }
603 }
604 
605 // Set syntax on bview buffer
bview_set_syntax(bview_t * self,char * opt_syntax)606 int bview_set_syntax(bview_t *self, char *opt_syntax) {
607     syntax_t *syntax;
608     syntax_t *syntax_tmp;
609     syntax_t *use_syntax;
610     srule_node_t *srule_node;
611 
612     // Only set syntax on edit bviews
613     if (!MLE_BVIEW_IS_EDIT(self)) {
614         return MLE_ERR;
615     }
616 
617     use_syntax = NULL;
618     if (opt_syntax) {
619         // Set by opt_syntax
620         HASH_FIND_STR(self->editor->syntax_map, opt_syntax, use_syntax);
621     } else if (self->editor->is_in_init && self->editor->syntax_override) {
622         // Set by override at init
623         HASH_FIND_STR(self->editor->syntax_map, self->editor->syntax_override, use_syntax);
624     } else if (self->buffer->path) {
625         // Set by path
626         HASH_ITER(hh, self->editor->syntax_map, syntax, syntax_tmp) {
627             if (util_pcre_match(syntax->path_pattern, self->buffer->path, strlen(self->buffer->path), NULL, NULL)) {
628                 use_syntax = syntax;
629                 break;
630             }
631         }
632     }
633 
634     buffer_set_styles_enabled(self->buffer, 0);
635 
636     // Remove current syntax
637     if (self->syntax) {
638         DL_FOREACH(self->syntax->srules, srule_node) {
639             buffer_remove_srule(self->buffer, srule_node->srule);
640             self->syntax = NULL;
641         }
642     }
643 
644     // Set syntax if found
645     if (use_syntax) {
646         DL_FOREACH(use_syntax->srules, srule_node) {
647             buffer_add_srule(self->buffer, srule_node->srule);
648         }
649         self->syntax = use_syntax;
650     }
651 
652     // Set tab settings
653     self->tab_to_space = (use_syntax && use_syntax->tab_to_space >= 0)
654         ? use_syntax->tab_to_space
655         : self->editor->tab_to_space;
656     _bview_set_tab_width(self, (use_syntax && use_syntax->tab_width >= 1)
657         ? use_syntax->tab_width
658         : self->editor->tab_width
659     );
660 
661     buffer_set_styles_enabled(self->buffer, 1);
662 
663     return use_syntax ? MLE_OK : MLE_ERR;
664 }
665 
_bview_set_tab_width(bview_t * self,int tab_width)666 static void _bview_set_tab_width(bview_t *self, int tab_width) {
667     self->tab_width = tab_width;
668     if (self->buffer && self->buffer->tab_width != self->tab_width) {
669         buffer_set_tab_width(self->buffer, self->tab_width);
670     }
671 }
672 
673 // Attempt to replace leading ~/ with $HOME
_bview_expand_tilde(bview_t * self,char * path,int path_len,char ** ret_path,int * ret_path_len)674 static void _bview_expand_tilde(bview_t *self, char *path, int path_len, char **ret_path, int *ret_path_len) {
675     char *homedir;
676     char *newpath;
677     (void)self;
678     if (!util_is_file("~", NULL, NULL)
679         && strncmp(path, "~/", 2) == 0
680         && (homedir = getenv("HOME")) != NULL
681     ) {
682         newpath = malloc(strlen(homedir) + 1 + (path_len - 2) + 1);
683         sprintf(newpath, "%s/%.*s", homedir, path_len-2, path+2);
684         *ret_path = newpath;
685         *ret_path_len = strlen(*ret_path);
686         return;
687     }
688     *ret_path = strndup(path, path_len);
689     *ret_path_len = strlen(*ret_path);
690 }
691 
692 // Attempt to fix path by stripping away git-style diff prefixes ([ab/]) and/or
693 // by extracting a trailing line number after a colon (:)
_bview_fix_path(bview_t * self,char * path,int path_len,char ** ret_path,int * ret_path_len,bint_t * ret_line_num)694 static void _bview_fix_path(bview_t *self, char *path, int path_len, char **ret_path, int *ret_path_len, bint_t *ret_line_num) {
695     char *tmp;
696     int tmp_len;
697     char *colon;
698     int is_valid;
699     int fix_nudge;
700     int fix_len;
701     bint_t line_num;
702     (void)self;
703 
704     fix_nudge = 0;
705     fix_len = path_len;
706     line_num = 0;
707 
708     // Path already valid?
709     if (util_is_file(path, NULL, NULL) || util_is_dir(path)) {
710         goto _bview_fix_path_ret;
711     }
712 
713     // Path valid if we strip "[ab]/" prefix?
714     if (path_len >= 3
715         && (strncmp(path, "a/", 2) == 0 || strncmp(path, "b/", 2) == 0)
716         && (util_is_file(path+2, NULL, NULL) || util_is_dir(path+2))
717     ) {
718         fix_nudge = 2;
719         fix_len -= 2;
720         goto _bview_fix_path_ret;
721     }
722 
723     // Path valid if we extract line num after colon?
724     if ((colon = strrchr(path, ':')) != NULL) {
725         tmp_len = colon - path;
726         tmp = strndup(path, tmp_len);
727         is_valid = util_is_file(tmp, NULL, NULL) ? 1 : 0;
728         free(tmp);
729         if (is_valid) {
730             fix_len = tmp_len;
731             line_num = strtoul(colon + 1, NULL, 10);
732             goto _bview_fix_path_ret;
733         }
734     }
735 
736     // Path valid if we strip "[ab]/" prefix and extract line num?
737     if (path_len >= 3
738         && (strncmp(path, "a/", 2) == 0 || strncmp(path, "b/", 2) == 0)
739         && (colon = strrchr(path, ':')) != NULL
740     ) {
741         tmp_len = (colon - path) - 2;
742         tmp = strndup(path+2, tmp_len);
743         is_valid = util_is_file(tmp, NULL, NULL) ? 1 : 0;
744         free(tmp);
745         if (is_valid) {
746             fix_nudge = 2;
747             fix_len = tmp_len;
748             line_num = strtoul(colon + 1, NULL, 10);
749             goto _bview_fix_path_ret;
750         }
751     }
752 
753 _bview_fix_path_ret:
754     *ret_path = strndup(path + fix_nudge, fix_len);
755     *ret_path_len = strlen(*ret_path);
756     *ret_line_num = line_num > 0 ? line_num - 1 : 0;
757 }
758 
759 // Open a buffer with an optional path to load, otherwise empty
_bview_open_buffer(bview_t * self,char * opt_path,int opt_path_len)760 static buffer_t *_bview_open_buffer(bview_t *self, char *opt_path, int opt_path_len) {
761     buffer_t *buffer;
762     int has_path;
763     char *fix_path;
764     char *exp_path;
765     int fix_path_len;
766     int exp_path_len;
767     bint_t startup_line_num;
768 
769     buffer = NULL;
770     has_path = opt_path && opt_path_len > 0 ? 1 : 0;
771 
772     if (has_path) {
773         _bview_expand_tilde(self, opt_path, opt_path_len, &exp_path, &exp_path_len);
774         _bview_fix_path(self, exp_path, exp_path_len, &fix_path, &fix_path_len, &startup_line_num);
775         buffer = buffer_new_open(fix_path);
776         if (buffer) self->startup_linenum = startup_line_num;
777         free(fix_path);
778         free(exp_path);
779     }
780     if (!buffer) {
781         buffer = buffer_new();
782         if (has_path) {
783             buffer->path = strndup(opt_path, opt_path_len);
784         }
785     }
786     buffer_set_callback(buffer, _bview_buffer_callback, self);
787     _bview_set_tab_width(self, self->tab_width);
788     return buffer;
789 }
790 
_bview_draw_prompt(bview_t * self)791 static void _bview_draw_prompt(bview_t *self) {
792     _bview_draw_bline(self, self->buffer->first_line, 0, NULL, NULL);
793 }
794 
_bview_draw_status(bview_t * self)795 static void _bview_draw_status(bview_t *self) {
796     editor_t *editor;
797     bview_t *active;
798     bview_t *active_edit;
799     mark_t *mark;
800 
801     editor = self->editor;
802     active = editor->active;
803     active_edit = editor->active_edit;
804     mark = active_edit->active_cursor->mark;
805 
806     // Prompt
807     if (active == editor->prompt) {
808         tb_printf(editor->rect_status, 0, 0, TB_GREEN | TB_BOLD, TB_BLACK, "%-*.*s", editor->rect_status.w, editor->rect_status.w, self->editor->prompt->prompt_str);
809         goto _bview_draw_status_end;
810     }
811 
812     // Macro indicator
813     int i_macro_fg, i_macro_bg;
814     char *i_macro;
815     if (editor->is_recording_macro) {
816         i_macro_fg = TB_RED | TB_BOLD;
817         i_macro_bg = TB_BLACK;
818         i_macro = "r";
819     } else if (editor->macro_apply) {
820         i_macro_fg = TB_GREEN | TB_BOLD;
821         i_macro_bg = TB_BLACK;
822         i_macro = "p";
823     } else {
824         i_macro_fg = 0;
825         i_macro_bg = 0;
826         i_macro = ".";
827     }
828 
829     // Anchor indicator
830     int i_anchor_fg, i_anchor_bg;
831     bint_t anchor_len, anchor_nlines, anchor_tmp;
832     char *i_anchor;
833     cursor_t *cursor;
834     if (active_edit->active_cursor->is_anchored) {
835         i_anchor_fg = TB_WHITE | TB_BOLD;
836         i_anchor_bg = TB_BLACK;
837         i_anchor = "a";
838         cursor = active_edit->active_cursor;
839         mark_get_offset(cursor->anchor, &anchor_tmp);
840         mark_get_offset(cursor->mark, &anchor_len);
841         anchor_len -= anchor_tmp;
842         anchor_nlines = cursor->anchor->bline->line_index - cursor->mark->bline->line_index;
843         if (anchor_nlines < 0) anchor_nlines *= -1;
844         anchor_nlines += 1;
845     } else {
846         i_anchor_fg = 0;
847         i_anchor_bg = 0;
848         i_anchor = ".";
849         anchor_len = 0;
850         anchor_nlines = 0;
851     }
852 
853     // Async indicator
854     int i_async_fg, i_async_bg;
855     char *i_async;
856     if (editor->aprocs) {
857         i_async_fg = TB_YELLOW | TB_BOLD;
858         i_async_bg = TB_BLACK;
859         i_async = "x";
860     } else {
861         i_async_fg = 0;
862         i_async_bg = 0;
863         i_async = ".";
864     }
865 
866     // Need-more-input icon
867     int i_needinput_fg;
868     int i_needinput_bg;
869     char *i_needinput;
870     if (editor->loop_ctx->need_more_input) {
871         i_needinput_fg = TB_BLUE | TB_BOLD;
872         i_needinput_bg = TB_BLACK;
873         i_needinput = "n";
874     } else {
875         i_needinput_fg = 0;
876         i_needinput_bg = 0;
877         i_needinput = ".";
878     }
879 
880     // Bview num TODO pre-compute this
881     bview_t *bview_tmp;
882     int bview_count = 0;
883     int bview_num = 0;
884     CDL_FOREACH2(editor->all_bviews, bview_tmp, all_next) {
885         if (MLE_BVIEW_IS_EDIT(bview_tmp)) {
886             bview_count += 1;
887             if (bview_tmp == active_edit) bview_num = bview_count;
888         }
889     }
890 
891     // Render status line
892     MLBUF_BLINE_ENSURE_CHARS(mark->bline);
893     tb_printf(editor->rect_status, 0, 0, 0, 0, "%*.*s", editor->rect_status.w, editor->rect_status.w, " ");
894     tb_printf_attr(editor->rect_status, 0, 0,
895         "@%d,%d;%s@%d,%d;"                                // mle_normal    mode
896         "[@%d,%d;%s@%d,%d;%s@%d,%d;%s@%d,%d;%s@%d,%d;]  " // [....]        need_input,anchor,macro,async
897         "buf:@%d,%d;%d@%d,%d;/@%d,%d;%d@%d,%d;  "         // buf:1/2       bview num
898         "<@%d,%d;%s@%d,%d;>  "                            // <php>         syntax
899         "line:@%d,%d;%llu@%d,%d;/@%d,%d;%llu@%d,%d;  "    // line:1/100    line
900         "col:@%d,%d;%llu@%d,%d;/@%d,%d;%llu@%d,%d;  "     // col:0/80      col
901         "%s@%d,%d;%lld@%d,%d;,@%d,%d;%llu@%d,%d;  ",      // sel:10,1      sel len, nlines
902         TB_MAGENTA | TB_BOLD, 0, active->kmap_tail->kmap->name, 0, 0,
903         i_needinput_fg, i_needinput_bg, i_needinput,
904         i_anchor_fg, i_anchor_bg, i_anchor,
905         i_macro_fg, i_macro_bg, i_macro,
906         i_async_fg, i_async_bg, i_async, 0, 0,
907         TB_BLUE | TB_BOLD, 0, bview_num, 0, 0, TB_BLUE, 0, bview_count, 0, 0,
908         TB_CYAN | TB_BOLD, 0, active_edit->syntax ? active_edit->syntax->name : "none", 0, 0,
909         TB_YELLOW | TB_BOLD, 0, mark->bline->line_index + 1, 0, 0, TB_YELLOW, 0, active_edit->buffer->line_count, 0, 0,
910         TB_YELLOW | TB_BOLD, 0, mark->col, 0, 0, TB_YELLOW, 0, mark->bline->char_count, 0, 0,
911         anchor_nlines == 0 ? "" : "sel:",
912         anchor_nlines == 0 ? TB_BLACK : TB_YELLOW | TB_BOLD, 0, anchor_len,
913         anchor_nlines == 0 ? TB_BLACK : 0, 0,
914         anchor_nlines == 0 ? TB_BLACK : TB_YELLOW, 0, anchor_nlines, 0, 0
915     );
916 
917     // Overlay errstr if present
918 _bview_draw_status_end:
919     if (editor->errstr[0] != '\0') {
920         int errstrlen = strlen(editor->errstr) + 5; // Add 5 for "err! "
921         tb_printf(editor->rect_status, editor->rect_status.w - errstrlen, 0, TB_WHITE | TB_BOLD, TB_RED, "err! %s", editor->errstr);
922         editor->errstr[0] = '\0'; // Clear errstr
923     } else if (editor->infostr[0] != '\0') {
924         int infostrlen = strlen(editor->infostr);
925         tb_printf(editor->rect_status, editor->rect_status.w - infostrlen, 0, TB_WHITE, 0, "%s", editor->infostr);
926         editor->infostr[0] = '\0'; // Clear errstr
927     }
928 }
929 
_bview_draw_edit(bview_t * self,int x,int y,int w,int h)930 static void _bview_draw_edit(bview_t *self, int x, int y, int w, int h) {
931     int split_w;
932     int split_h;
933     int min_w;
934     int min_h;
935     int rect_y;
936     int fg_attr;
937     int bg_attr;
938     bline_t *bline;
939 
940     // Handle split
941     if (self->split_child) {
942         // Calc split dimensions
943         if (self->split_is_vertical) {
944             split_w = w - (int)((float)w * self->split_factor);
945             split_h = h;
946         } else {
947             split_w = w;
948             split_h = h - (int)((float)h * self->split_factor);
949         }
950 
951         // Draw child
952         _bview_draw_edit(self->split_child, x + (w - split_w), y + (h - split_h), split_w, split_h);
953 
954         // Continue drawing self minus split dimensions
955         w -= (w - split_w);
956         h -= (h - split_h);
957     }
958 
959     // Calc min dimensions
960     min_w = self->linenum_width + 3;
961     min_h = 2;
962 
963     // Ensure renderable
964     if (w < min_w || h < min_h
965         || x + w > self->editor->w
966         || y + h > self->editor->h
967     ) {
968         return;
969     }
970 
971     // Render caption
972     fg_attr = self->editor->active_edit == self ? TB_BOLD : 0;
973     bg_attr = self->editor->active_edit == self ? TB_BLUE : 0;
974     tb_printf(self->rect_caption, 0, 0, fg_attr, bg_attr, "%*.*s", self->rect_caption.w, self->rect_caption.w, " ");
975     if (self->buffer->path) {
976         tb_printf(self->rect_caption, 0, 0, fg_attr, bg_attr, "%*.s%s %c",
977             self->linenum_width, " ",
978             self->buffer->path, self->buffer->is_unsaved ? '*' : ' ');
979     } else {
980         tb_printf(self->rect_caption, 0, 0, fg_attr, bg_attr, "%*.s<buffer-%p> %c",
981             self->linenum_width, " ",
982             self->buffer, self->buffer->is_unsaved ? '*' : ' ');
983     }
984 
985     // Render lines and margins
986     if (!self->viewport_bline) {
987         buffer_get_bline(self->buffer, MLE_MAX(0, self->viewport_y), &self->viewport_bline);
988     }
989     bline = self->viewport_bline;
990     for (rect_y = 0; rect_y < self->rect_buffer.h; rect_y++) {
991         if (self->viewport_y + rect_y < 0 || self->viewport_y + rect_y >= self->buffer->line_count || !bline) { // "|| !bline" See TODOs below
992             // Draw pre/post blank
993             tb_printf(self->rect_lines, 0, rect_y, 0, 0, "%*c", self->linenum_width, '~');
994             tb_printf(self->rect_margin_left, 0, rect_y, 0, 0, "%c", ' ');
995             tb_printf(self->rect_margin_right, 0, rect_y, 0, 0, "%c", ' ');
996             tb_printf(self->rect_buffer, 0, rect_y, 0, 0, "%-*.*s", self->rect_buffer.w, self->rect_buffer.w, " ");
997         } else {
998             // Draw bline at self->rect_buffer self->viewport_y + rect_y
999             // TODO How can bline be NULL here?
1000             // TODO How can self->viewport_y != self->viewport_bline->line_index ?
1001             _bview_draw_bline(self, bline, rect_y, &bline, &rect_y);
1002             bline = bline->next;
1003         }
1004     }
1005 }
1006 
_bview_draw_bline(bview_t * self,bline_t * bline,int rect_y,bline_t ** optret_bline,int * optret_rect_y)1007 static void _bview_draw_bline(bview_t *self, bline_t *bline, int rect_y, bline_t **optret_bline, int *optret_rect_y) {
1008     int rect_x;
1009     bint_t char_col;
1010     int fg;
1011     int bg;
1012     uint32_t ch;
1013     int char_w;
1014     bint_t viewport_x;
1015     bint_t viewport_x_vcol;
1016     int i;
1017     int is_cursor_line;
1018     int is_soft_wrap;
1019     int orig_rect_y;
1020 
1021     MLBUF_BLINE_ENSURE_CHARS(bline);
1022 
1023     // Set is_cursor_line
1024     is_cursor_line = self->active_cursor->mark->bline == bline ? 1 : 0;
1025 
1026     // Soft wrap only for current line
1027     is_soft_wrap = self->editor->soft_wrap && is_cursor_line && MLE_BVIEW_IS_EDIT(self) ? 1 : 0;
1028 
1029     // Use viewport_x only for current line when not soft wrapping
1030     viewport_x = 0;
1031     viewport_x_vcol = 0;
1032     if (is_cursor_line && !is_soft_wrap) {
1033         viewport_x = self->viewport_x;
1034         viewport_x_vcol = self->viewport_x_vcol;
1035     }
1036 
1037     // Draw linenums and margins
1038     if (MLE_BVIEW_IS_EDIT(self)) {
1039         int linenum_fg = is_cursor_line ? TB_BOLD : 0;
1040         if (self->editor->linenum_type == MLE_LINENUM_TYPE_ABS
1041             || self->editor->linenum_type == MLE_LINENUM_TYPE_BOTH
1042             || (self->editor->linenum_type == MLE_LINENUM_TYPE_REL && is_cursor_line)
1043         ) {
1044             tb_printf(self->rect_lines, 0, rect_y, linenum_fg, 0, "%*d", self->abs_linenum_width, (int)(bline->line_index + 1) % (int)pow(10, self->linenum_width));
1045             if (self->editor->linenum_type == MLE_LINENUM_TYPE_BOTH) {
1046                 tb_printf(self->rect_lines, self->abs_linenum_width, rect_y, linenum_fg, 0, " %*d", self->rel_linenum_width, (int)labs(bline->line_index - self->active_cursor->mark->bline->line_index));
1047             }
1048         } else if (self->editor->linenum_type == MLE_LINENUM_TYPE_REL) {
1049             tb_printf(self->rect_lines, 0, rect_y, linenum_fg, 0, "%*d", self->rel_linenum_width, (int)labs(bline->line_index - self->active_cursor->mark->bline->line_index));
1050         }
1051         tb_printf(self->rect_margin_left, 0, rect_y, 0, 0, "%c", viewport_x > 0 && bline->char_count > 0 ? '^' : ' ');
1052         if (!is_soft_wrap && bline->char_vwidth - viewport_x_vcol > self->rect_buffer.w) {
1053             tb_printf(self->rect_margin_right, 0, rect_y, 0, 0, "%c", '$');
1054         }
1055     }
1056 
1057     // Render 0 thru rect_buffer.w cell by cell
1058     orig_rect_y = rect_y;
1059     rect_x = 0;
1060     char_col = viewport_x;
1061     while (1) {
1062         char_w = 1;
1063         if (char_col < bline->char_count) {
1064             ch = bline->chars[char_col].ch;
1065             fg = bline->chars[char_col].style.fg;
1066             bg = bline->chars[char_col].style.bg;
1067             char_w = char_col == bline->char_count - 1
1068                 ? bline->char_vwidth - bline->chars[char_col].vcol
1069                 : bline->chars[char_col + 1].vcol - bline->chars[char_col].vcol;
1070             if (ch == '\t') {
1071                 ch = ' ';
1072             } else if (!iswprint(ch)) {
1073                 ch = '?';
1074             }
1075             if (self->editor->color_col == char_col && MLE_BVIEW_IS_EDIT(self)) {
1076                 bg |= TB_RED;
1077             }
1078         } else {
1079             break;
1080         }
1081         if (MLE_BVIEW_IS_MENU(self) && is_cursor_line) {
1082             bg |= TB_REVERSE;
1083         }
1084         for (i = 0; i < char_w && rect_x < self->rect_buffer.w; i++) {
1085             tb_change_cell(self->rect_buffer.x + rect_x + i, self->rect_buffer.y + rect_y, ch, fg, bg);
1086         }
1087         if (is_soft_wrap && rect_x+1 >= self->rect_buffer.w && rect_y+1 < self->rect_buffer.h) {
1088             rect_x = 0;
1089             rect_y += 1;
1090             for (i = 0; i < self->linenum_width; i++) {
1091                 tb_printf(self->rect_lines, i, rect_y, 0, 0, "%c", '.');
1092             }
1093         } else {
1094             rect_x += char_w;
1095         }
1096         char_col += 1;
1097     }
1098     for (i = orig_rect_y; i < rect_y && bline->next; i++) {
1099         bline = bline->next;
1100     }
1101     if (optret_bline) *optret_bline = bline;
1102     if (optret_rect_y) *optret_rect_y = rect_y;
1103 }
1104 
1105 // Highlight matching bracket pair under mark
_bview_highlight_bracket_pair(bview_t * self,mark_t * mark)1106 static void _bview_highlight_bracket_pair(bview_t *self, mark_t *mark) {
1107     bline_t *line;
1108     bint_t brkt;
1109     bint_t col;
1110     mark_t pair;
1111     int screen_x;
1112     int screen_y;
1113     struct tb_cell *cell;
1114 
1115     MLBUF_BLINE_ENSURE_CHARS(mark->bline);
1116 
1117     if (mark_is_at_eol(mark) || !util_get_bracket_pair(mark->bline->chars[mark->col].ch, NULL)) {
1118         // Not a bracket
1119         return;
1120     }
1121     if (mark_find_bracket_pair(mark, MLE_BRACKET_PAIR_MAX_SEARCH, &line, &col, &brkt) != MLBUF_OK) {
1122         // No pair found
1123         return;
1124     }
1125     if (mark->bline == line && (mark->col == col - 1 || mark->col == col + 1)) {
1126         // One char away, do not highlight (looks confusing in UI)
1127         return;
1128     }
1129 
1130     pair.bline = line;
1131     pair.col = col;
1132     if (bview_get_screen_coords(self, &pair, &screen_x, &screen_y, &cell) != MLE_OK) {
1133         // Out of bounds
1134         return;
1135     }
1136     tb_change_cell(screen_x, screen_y, cell->ch, cell->fg | TB_UNDERLINE, cell->bg); // TODO configurable
1137 }
1138 
1139 // Find screen coordinates for a mark
bview_get_screen_coords(bview_t * self,mark_t * mark,int * ret_x,int * ret_y,struct tb_cell ** optret_cell)1140 int bview_get_screen_coords(bview_t *self, mark_t *mark, int *ret_x, int *ret_y, struct tb_cell **optret_cell) {
1141     int screen_x;
1142     int screen_y;
1143     int is_soft_wrapped;
1144 
1145     MLBUF_BLINE_ENSURE_CHARS(mark->bline);
1146 
1147     is_soft_wrapped = self->editor->soft_wrap
1148         && self->active_cursor->mark->bline == mark->bline
1149         && MLE_BVIEW_IS_EDIT(self) ? 1 : 0;
1150 
1151     if (is_soft_wrapped) {
1152         screen_x = self->rect_buffer.x + MLE_MARK_COL_TO_VCOL(mark) % self->rect_buffer.w;
1153         screen_y = self->rect_buffer.y + (mark->bline->line_index - self->viewport_bline->line_index) + (MLE_MARK_COL_TO_VCOL(mark) / self->rect_buffer.w);
1154     } else {
1155         screen_x = self->rect_buffer.x + MLE_MARK_COL_TO_VCOL(mark) - MLE_COL_TO_VCOL(mark->bline, self->viewport_x, mark->bline->char_vwidth);
1156         screen_y = self->rect_buffer.y + (mark->bline->line_index - self->viewport_bline->line_index);
1157     }
1158     if (screen_x < self->rect_buffer.x || screen_x >= self->rect_buffer.x + self->rect_buffer.w
1159        || screen_y < self->rect_buffer.y || screen_y >= self->rect_buffer.y + self->rect_buffer.h
1160     ) {
1161         // Out of bounds
1162         return MLE_ERR;
1163     }
1164     *ret_x = screen_x;
1165     *ret_y = screen_y;
1166     if (optret_cell) {
1167         *optret_cell = tb_cell_buffer() + (ptrdiff_t)(tb_width() * screen_y + screen_x);
1168     }
1169     return MLE_OK;
1170 }
1171