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