vis
a vi-like editor based on Plan 9's structural regular expressions
git clone https://9o.is/git/vis.git
view.c
(36650B)
1 #include <ctype.h>
2 #include <errno.h>
3 #include <limits.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <wchar.h>
8
9 #include "vis-core.h"
10 #include "view.h"
11 #include "text.h"
12 #include "text-motions.h"
13 #include "text-util.h"
14 #include "util.h"
15
16 /* A selection is made up of two marks named cursor and anchor.
17 * While the anchor remains fixed the cursor mark follows cursor motions.
18 * For a selection (indicated by []), the marks (^) are placed as follows:
19 *
20 * [some text] [!]
21 * ^ ^ ^
22 * ^
23 *
24 * That is the marks point to the *start* of the first and last character
25 * of the selection. In particular for a single character selection (as
26 * depicted on the right above) both marks point to the same location.
27 *
28 * The view_selections_{get,set} functions take care of adding/removing
29 * the necessary offset for the last character.
30 */
31
32 static const char *symbols_none[] = {
33 [SYNTAX_SYMBOL_SPACE] = " ",
34 [SYNTAX_SYMBOL_TAB] = " ",
35 [SYNTAX_SYMBOL_TAB_FILL] = " ",
36 [SYNTAX_SYMBOL_EOL] = " ",
37 [SYNTAX_SYMBOL_EOF] = " ",
38 };
39
40 static const char *symbols_default[] = {
41 [SYNTAX_SYMBOL_SPACE] = "·", /* Middle Dot U+00B7 */
42 [SYNTAX_SYMBOL_TAB] = "›", /* Single Right-Pointing Angle Quotation Mark U+203A */
43 [SYNTAX_SYMBOL_TAB_FILL] = " ",
44 [SYNTAX_SYMBOL_EOL] = "↵", /* Downwards Arrow with Corner Leftwards U+21B5 */
45 [SYNTAX_SYMBOL_EOF] = "~",
46 };
47
48 static Cell cell_blank = { .width = 0, .len = 0, .data = " ", };
49
50 /* move visible viewport n-lines up/down, redraws the view but does not change
51 * cursor position which becomes invalid and should be corrected by calling
52 * view_cursors_to. the return value indicates whether the visible area changed.
53 */
54 static bool view_viewport_up(View *view, int n);
55 static bool view_viewport_down(View *view, int n);
56
57 static void view_clear(View *view);
58 static bool view_add_cell(View *view, const Cell *cell);
59 static bool view_addch(View *view, Cell *cell);
60 static void selection_free(Selection*);
61 /* set/move current cursor position to a given (line, column) pair */
62 static size_t cursor_set(Selection*, Line *line, int col);
63
64 void window_status_update(Vis *vis, Win *win) {
65 char left_parts[4][255] = { "", "", "", "" };
66 char right_parts[4][32] = { "", "", "", "" };
67 char left[sizeof(left_parts)+LENGTH(left_parts)*8];
68 char right[sizeof(right_parts)+LENGTH(right_parts)*8];
69 char status[sizeof(left)+sizeof(right)+1];
70 size_t left_count = 0;
71 size_t right_count = 0;
72
73 View *view = &win->view;
74 File *file = win->file;
75 Text *txt = file->text;
76 int width = win->width;
77 enum UiOption options = win->options;
78 bool focused = vis->win == win;
79 const char *filename = file_name_get(file);
80 const char *mode = vis->mode->status;
81
82 if (focused && mode)
83 strcpy(left_parts[left_count++], mode);
84
85 snprintf(left_parts[left_count++], sizeof(left_parts[0]), "%s%s%s",
86 filename ? filename : "[No Name]",
87 text_modified(txt) ? " [+]" : "",
88 vis_macro_recording(vis) ? " @": "");
89
90 int count = vis->action.count;
91 const char *keys = buffer_content0(&vis->input_queue);
92 if (keys && keys[0])
93 snprintf(right_parts[right_count++], sizeof(right_parts[0]), "%s", keys);
94 else if (count != VIS_COUNT_UNKNOWN)
95 snprintf(right_parts[right_count++], sizeof(right_parts[0]), "%d", count);
96
97 int sel_count = view->selection_count;
98 if (sel_count > 1) {
99 Selection *s = view_selections_primary_get(view);
100 int sel_number = view_selections_number(s) + 1;
101 snprintf(right_parts[right_count++], sizeof(right_parts[0]),
102 "%d/%d", sel_number, sel_count);
103 }
104
105 size_t size = text_size(txt);
106 size_t pos = view_cursor_get(view);
107 size_t percent = 0;
108 if (size > 0) {
109 double tmp = ((double)pos/(double)size)*100;
110 percent = (size_t)(tmp+1);
111 }
112 snprintf(right_parts[right_count++], sizeof(right_parts[0]),
113 "%zu%%", percent);
114
115 if (!(options & UI_OPTION_LARGE_FILE)) {
116 Selection *sel = view_selections_primary_get(&win->view);
117 size_t line = view_cursors_line(sel);
118 size_t col = view_cursors_col(sel);
119 if (col > UI_LARGE_FILE_LINE_SIZE) {
120 options |= UI_OPTION_LARGE_FILE;
121 win_options_set(win, options);
122 }
123 snprintf(right_parts[right_count++], sizeof(right_parts[0]),
124 "%zu, %zu", line, col);
125 }
126
127 int left_len = snprintf(left, sizeof(left), " %s%s%s%s%s%s%s",
128 left_parts[0],
129 left_parts[1][0] ? " » " : "",
130 left_parts[1],
131 left_parts[2][0] ? " » " : "",
132 left_parts[2],
133 left_parts[3][0] ? " » " : "",
134 left_parts[3]);
135
136 int right_len = snprintf(right, sizeof(right), "%s%s%s%s%s%s%s ",
137 right_parts[0],
138 right_parts[1][0] ? " « " : "",
139 right_parts[1],
140 right_parts[2][0] ? " « " : "",
141 right_parts[2],
142 right_parts[3][0] ? " « " : "",
143 right_parts[3]);
144
145 if (left_len < 0 || right_len < 0)
146 return;
147 int left_width = text_string_width(left, left_len);
148 int right_width = text_string_width(right, right_len);
149
150 int spaces = width - left_width - right_width;
151 if (spaces < 1)
152 spaces = 1;
153
154 snprintf(status, sizeof(status), "%s%*s%s", left, spaces, " ", right);
155 ui_window_status(win, status);
156 }
157
158 void view_tabwidth_set(View *view, int tabwidth) {
159 if (tabwidth < 1 || tabwidth > 8)
160 return;
161 view->tabwidth = tabwidth;
162 view_draw(view);
163 }
164
165 /* reset internal view data structures (cell matrix, line offsets etc.) */
166 static void view_clear(View *view) {
167 memset(view->lines, 0, view->lines_size);
168 if (view->start != view->start_last) {
169 if (view->start == 0)
170 view->start_mark = EMARK;
171 else
172 view->start_mark = text_mark_set(view->text, view->start);
173 } else {
174 size_t start;
175 if (view->start_mark == EMARK)
176 start = 0;
177 else
178 start = text_mark_get(view->text, view->start_mark);
179 if (start != EPOS)
180 view->start = start;
181 }
182
183 view->start_last = view->start;
184 view->topline = view->lines;
185 view->topline->lineno = view->large_file ? 1 : text_lineno_by_pos(view->text, view->start);
186 view->lastline = view->topline;
187
188 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
189 size_t end = view->height * line_size;
190 Line *prev = NULL;
191 for (size_t i = 0; i < end; i += line_size) {
192 Line *line = (Line*)(((char*)view->lines) + i);
193 line->prev = prev;
194 if (prev)
195 prev->next = line;
196 prev = line;
197 }
198 view->bottomline = prev ? prev : view->topline;
199 view->bottomline->next = NULL;
200 view->line = view->topline;
201 view->col = 0;
202 view->wrapcol = 0;
203 view->prevch_breakat = false;
204
205 /* FIXME: awful garbage that only exists because every
206 * struct in this program is an interdependent hellscape */
207 Win *win = (Win *)((char *)view - offsetof(Win, view));
208 ui_window_style_set(&win->vis->ui, win->id, &cell_blank, UI_STYLE_DEFAULT, false);
209 }
210
211 static int view_max_text_width(const View *view) {
212 if (view->wrapcolumn > 0)
213 return MIN(view->wrapcolumn, view->width);
214 return view->width;
215 }
216
217 static void view_wrap_line(View *view) {
218 Line *wrapped_line = view->line;
219 int col = view->col;
220 int wrapcol = (view->wrapcol > 0) ? view->wrapcol : view->col;
221
222 view->line = view->line->next;
223 view->col = 0;
224 view->wrapcol = 0;
225
226 if (view->line) {
227 view->line->lineno = wrapped_line->lineno;
228 /* move extra cells to the next line */
229 for (int i = wrapcol; i < col; ++i) {
230 const Cell *cell = &wrapped_line->cells[i];
231 view->line->width += cell->width;
232 view->line->len += cell->len;
233 view->line->cells[view->col++] = *cell;
234 }
235 }
236
237 /* clear remaining cells on line */
238 for (int i = wrapcol; i < view->width; ++i) {
239 if (i < col) {
240 wrapped_line->width -= wrapped_line->cells[i].width;
241 wrapped_line->len -= wrapped_line->cells[i].len;
242 }
243 wrapped_line->cells[i] = cell_blank;
244 }
245 }
246
247 static bool view_add_cell(View *view, const Cell *cell) {
248 /* if the terminal is resized to a single (ASCII) char an out
249 * of bounds write could be performed for a wide char. this can
250 * be caught by iterating through the lines with view_wrap_line()
251 * until no lines remain. usually 0 or 1 iterations.
252 */
253 while (view->col + cell->width > view_max_text_width(view)) {
254 view_wrap_line(view);
255 if (!view->line)
256 return false;
257 }
258
259 view->line->width += cell->width;
260 view->line->len += cell->len;
261 view->line->cells[view->col++] = *cell;
262 /* set cells of a character which uses multiple columns */
263 for (int i = 1; i < cell->width; i++)
264 view->line->cells[view->col++] = (Cell){0};
265 return true;
266 }
267
268 static bool view_expand_tab(View *view, Cell *cell) {
269 cell->width = 1;
270
271 int displayed_width = view->tabwidth - (view->col % view->tabwidth);
272 for (int w = 0; w < displayed_width; ++w) {
273 int t = (w == 0) ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
274 const char *symbol = view->symbols[t];
275 strncpy(cell->data, symbol, sizeof(cell->data) - 1);
276 cell->len = (w == 0) ? 1 : 0;
277
278 if (!view_add_cell(view, cell))
279 return false;
280 }
281
282 cell->len = 1;
283 return true;
284 }
285
286 static bool view_expand_newline(View *view, Cell *cell) {
287 size_t lineno = view->line->lineno;
288 const char *symbol = view->symbols[SYNTAX_SYMBOL_EOL];
289
290 strncpy(cell->data, symbol, sizeof(cell->data) - 1);
291 cell->width = 1;
292 if (!view_add_cell(view, cell))
293 return false;
294
295 view->wrapcol = 0;
296 view_wrap_line(view);
297 if (view->line)
298 view->line->lineno = lineno + 1;
299 return true;
300 }
301
302 /* try to add another character to the view, return whether there was space left */
303 static bool view_addch(View *view, Cell *cell) {
304 if (!view->line)
305 return false;
306
307 bool ch_breakat = (cell->data[0] != 0) && strstr(view->breakat, cell->data);
308 if (view->prevch_breakat && !ch_breakat) {
309 /* this is a good place to wrap line if needed */
310 view->wrapcol = view->col;
311 }
312 view->prevch_breakat = ch_breakat;
313 cell->style = cell_blank.style;
314
315 unsigned char ch = (unsigned char)cell->data[0];
316 switch (ch) {
317 case '\t':
318 return view_expand_tab(view, cell);
319 case '\n':
320 return view_expand_newline(view, cell);
321 case ' ': {
322 const char *symbol = view->symbols[SYNTAX_SYMBOL_SPACE];
323 strncpy(cell->data, symbol, sizeof(cell->data) - 1);
324 return view_add_cell(view, cell);
325 }}
326
327 if (ch < 128 && !isprint(ch)) {
328 /* non-printable ascii char, represent it as ^(char + 64) */
329 *cell = (Cell) {
330 .data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
331 .len = 1,
332 .width = 2,
333 .style = cell->style,
334 };
335 }
336 return view_add_cell(view, cell);
337 }
338
339 static void cursor_to(Selection *s, size_t pos) {
340 Text *txt = s->view->text;
341 s->cursor = text_mark_set(txt, pos);
342 if (!s->anchored)
343 s->anchor = s->cursor;
344 if (pos != s->pos)
345 s->lastcol = 0;
346 s->pos = pos;
347 if (!view_coord_get(s->view, pos, &s->line, &s->row, &s->col)) {
348 if (s->view->selection == s) {
349 s->line = s->view->topline;
350 s->row = 0;
351 s->col = 0;
352 }
353 return;
354 }
355 // TODO: minimize number of redraws
356 view_draw(s->view);
357 }
358
359 bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
360 int row = 0, col = 0;
361 size_t cur = view->start;
362 Line *line = view->topline;
363
364 if (pos < view->start || pos > view->end) {
365 if (retline) *retline = NULL;
366 if (retrow) *retrow = -1;
367 if (retcol) *retcol = -1;
368 return false;
369 }
370
371 while (line && line != view->lastline && cur < pos) {
372 if (cur + line->len > pos)
373 break;
374 cur += line->len;
375 line = line->next;
376 row++;
377 }
378
379 if (line) {
380 int max_col = MIN(view->width, line->width);
381 while (cur < pos && col < max_col) {
382 cur += line->cells[col].len;
383 /* skip over columns occupied by the same character */
384 while (++col < max_col && line->cells[col].len == 0);
385 }
386 } else {
387 line = view->bottomline;
388 row = view->height - 1;
389 }
390
391 if (retline) *retline = line;
392 if (retrow) *retrow = row;
393 if (retcol) *retcol = col;
394 return true;
395 }
396
397 /* redraw the complete with data starting from view->start bytes into the file.
398 * stop once the screen is full, update view->end, view->lastline */
399 void view_draw(View *view) {
400 view_clear(view);
401 /* read a screenful of text considering each character as 4-byte UTF character*/
402 const size_t size = view->width * view->height * 4;
403 /* current buffer to work with */
404 char *text = view->textbuf;
405 /* remaining bytes to process in buffer */
406 size_t rem = text_bytes_get(view->text, view->start, size, text);
407 /* NUL terminate text section */
408 text[rem] = '\0';
409 /* absolute position of character currently being added to display */
410 size_t pos = view->start;
411 /* current position into buffer from which to interpret a character */
412 char *cur = text;
413 /* start from known multibyte state */
414 mbstate_t mbstate = { 0 };
415
416 Cell cell = { .data = "", .len = 0, .width = 0, }, prev_cell = cell;
417
418 while (rem > 0) {
419
420 /* current 'parsed' character' */
421 wchar_t wchar;
422
423 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
424 if (len == (size_t)-1 && errno == EILSEQ) {
425 /* ok, we encountered an invalid multibyte sequence,
426 * replace it with the Unicode Replacement Character
427 * (FFFD) and skip until the start of the next utf8 char */
428 mbstate = (mbstate_t){0};
429 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
430 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
431 } else if (len == (size_t)-2) {
432 /* not enough bytes available to convert to a
433 * wide character. Advance file position and read
434 * another junk into buffer.
435 */
436 rem = text_bytes_get(view->text, pos+prev_cell.len, size, text);
437 text[rem] = '\0';
438 cur = text;
439 continue;
440 } else if (len == 0) {
441 /* NUL byte encountered, store it and continue */
442 cell = (Cell){ .data = "\x00", .len = 1, .width = 2 };
443 } else {
444 if (len >= sizeof(cell.data))
445 len = sizeof(cell.data)-1;
446 for (size_t i = 0; i < len; i++)
447 cell.data[i] = cur[i];
448 cell.data[len] = '\0';
449 cell.len = len;
450 cell.width = wcwidth(wchar);
451 if (cell.width == -1)
452 cell.width = 1;
453 }
454
455 if (cell.width == 0) {
456 strncat(prev_cell.data, cell.data, sizeof(prev_cell.data)-strlen(prev_cell.data)-1);
457 prev_cell.len += cell.len;
458 } else {
459 if (prev_cell.len && !view_addch(view, &prev_cell))
460 break;
461 pos += prev_cell.len;
462 prev_cell = cell;
463 }
464
465 rem -= cell.len;
466 cur += cell.len;
467
468 memset(&cell, 0, sizeof cell);
469 }
470
471 if (prev_cell.len && view_addch(view, &prev_cell))
472 pos += prev_cell.len;
473
474 /* set end of viewing region */
475 view->end = pos;
476 if (view->line) {
477 bool eof = view->end == text_size(view->text);
478 if (view->line->len == 0 && eof && view->line->prev)
479 view->lastline = view->line->prev;
480 else
481 view->lastline = view->line;
482 } else {
483 view->lastline = view->bottomline;
484 }
485
486 /* clear remaining of line, important to show cursor at end of file */
487 if (view->line) {
488 for (int x = view->col; x < view->width; x++)
489 view->line->cells[x] = cell_blank;
490 }
491
492 /* resync position of cursors within visible area */
493 for (Selection *s = view->selections; s; s = s->next) {
494 size_t pos = view_cursors_pos(s);
495 if (!view_coord_get(view, pos, &s->line, &s->row, &s->col) &&
496 s == view->selection) {
497 s->line = view->topline;
498 s->row = 0;
499 s->col = 0;
500 }
501 }
502
503 view->need_update = true;
504 }
505
506 bool view_update(View *view) {
507 if (!view->need_update)
508 return false;
509 for (Line *l = view->lastline->next; l; l = l->next) {
510 for (int x = 0; x < view->width; x++)
511 l->cells[x] = cell_blank;
512 }
513 view->need_update = false;
514 return true;
515 }
516
517 bool view_resize(View *view, int width, int height) {
518 if (width <= 0)
519 width = 1;
520 if (height <= 0)
521 height = 1;
522 if (view->width == width && view->height == height) {
523 view->need_update = true;
524 return true;
525 }
526 char *textbuf = malloc(width * height * 4 + 1);
527 if (!textbuf)
528 return false;
529 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
530 if (lines_size > view->lines_size) {
531 Line *lines = realloc(view->lines, lines_size);
532 if (!lines) {
533 free(textbuf);
534 return false;
535 }
536 view->lines = lines;
537 view->lines_size = lines_size;
538 }
539 free(view->textbuf);
540 view->textbuf = textbuf;
541 view->width = width;
542 view->height = height;
543 memset(view->lines, 0, view->lines_size);
544 view_draw(view);
545 return true;
546 }
547
548 void view_free(View *view) {
549 if (!view)
550 return;
551 while (view->selections)
552 selection_free(view->selections);
553 free(view->textbuf);
554 free(view->lines);
555 free(view->breakat);
556 }
557
558 void view_reload(View *view, Text *text) {
559 view->text = text;
560 view_selections_clear_all(view);
561 view_cursors_to(view->selection, 0);
562 }
563
564 bool view_init(Win *win, Text *text) {
565 View *view = &win->view;
566 if (!text)
567 return false;
568
569 view->text = text;
570 view->tabwidth = 8;
571 view->breakat = strdup("");
572 view->wrapcolumn = 0;
573 win_options_set(win, 0);
574
575 if (!view->breakat ||
576 !view_selections_new(view, 0) ||
577 !view_resize(view, 1, 1))
578 {
579 return false;
580 }
581
582 view_cursors_to(view->selection, 0);
583 return true;
584 }
585
586 static size_t cursor_set(Selection *sel, Line *line, int col) {
587 int row = 0;
588 View *view = sel->view;
589 size_t pos = view->start;
590 /* get row number and file offset at start of the given line */
591 for (Line *l = view->topline; l && l != line; l = l->next) {
592 pos += l->len;
593 row++;
594 }
595
596 /* for characters which use more than 1 column, make sure we are on the left most */
597 while (col > 0 && line->cells[col].len == 0)
598 col--;
599 /* calculate offset within the line */
600 for (int i = 0; i < col; i++)
601 pos += line->cells[i].len;
602
603 sel->col = col;
604 sel->row = row;
605 sel->line = line;
606
607 cursor_to(sel, pos);
608
609 return pos;
610 }
611
612 static bool view_viewport_down(View *view, int n) {
613 Line *line;
614 if (view->end >= text_size(view->text))
615 return false;
616 if (n >= view->height) {
617 view->start = view->end;
618 } else {
619 for (line = view->topline; line && n > 0; line = line->next, n--)
620 view->start += line->len;
621 }
622 view_draw(view);
623 return true;
624 }
625
626 static bool view_viewport_up(View *view, int n) {
627 /* scrolling up is somewhat tricky because we do not yet know where
628 * the lines start, therefore scan backwards but stop at a reasonable
629 * maximum in case we are dealing with a file without any newlines
630 */
631 if (view->start == 0)
632 return false;
633 size_t max = view->width * view->height;
634 char c;
635 Iterator it = text_iterator_get(view->text, view->start - 1);
636
637 if (!text_iterator_byte_get(&it, &c))
638 return false;
639 size_t off = 0;
640 /* skip newlines immediately before display area */
641 if (c == '\n' && text_iterator_byte_prev(&it, &c))
642 off++;
643 do {
644 if (c == '\n' && --n == 0)
645 break;
646 if (++off > max)
647 break;
648 } while (text_iterator_byte_prev(&it, &c));
649 view->start -= MIN(view->start, off);
650 view_draw(view);
651 return true;
652 }
653
654 void view_redraw_top(View *view) {
655 Line *line = view->selection->line;
656 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
657 view->start += cur->len;
658 view_draw(view);
659 /* FIXME: does this logic make sense */
660 view_cursors_to(view->selection, view->selection->pos);
661 }
662
663 void view_redraw_center(View *view) {
664 int center = view->height / 2;
665 size_t pos = view->selection->pos;
666 for (int i = 0; i < 2; i++) {
667 int linenr = 0;
668 Line *line = view->selection->line;
669 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
670 linenr++;
671 if (linenr < center) {
672 view_slide_down(view, center - linenr);
673 continue;
674 }
675 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
676 view->start += cur->len;
677 linenr--;
678 }
679 break;
680 }
681 view_draw(view);
682 view_cursors_to(view->selection, pos);
683 }
684
685 void view_redraw_bottom(View *view) {
686 size_t pos = view->selection->pos;
687 view_viewport_up(view, view->height);
688 while (pos >= view->end && view_viewport_down(view, 1));
689 cursor_to(view->selection, pos);
690 }
691
692 size_t view_slide_up(View *view, int lines) {
693 Selection *sel = view->selection;
694 if (view_viewport_down(view, lines)) {
695 if (sel->line == view->topline)
696 cursor_set(sel, view->topline, sel->col);
697 else
698 view_cursors_to(view->selection, sel->pos);
699 } else {
700 view_screenline_down(sel);
701 }
702 return sel->pos;
703 }
704
705 size_t view_slide_down(View *view, int lines) {
706 Selection *sel = view->selection;
707 bool lastline = sel->line == view->lastline;
708 size_t col = sel->col;
709 if (view_viewport_up(view, lines)) {
710 if (lastline)
711 cursor_set(sel, view->lastline, col);
712 else
713 view_cursors_to(view->selection, sel->pos);
714 } else {
715 view_screenline_up(sel);
716 }
717 return sel->pos;
718 }
719
720 size_t view_scroll_up(View *view, int lines) {
721 Selection *sel = view->selection;
722 if (view_viewport_up(view, lines)) {
723 Line *line = sel->line < view->lastline ? sel->line : view->lastline;
724 cursor_set(sel, line, view->selection->col);
725 } else {
726 view_cursors_to(view->selection, 0);
727 }
728 return sel->pos;
729 }
730
731 size_t view_scroll_page_up(View *view) {
732 Selection *sel = view->selection;
733 if (view->start == 0) {
734 view_cursors_to(view->selection, 0);
735 } else {
736 view_cursors_to(view->selection, view->start-1);
737 view_redraw_bottom(view);
738 view_screenline_begin(sel);
739 }
740 return sel->pos;
741 }
742
743 size_t view_scroll_page_down(View *view) {
744 view_scroll_down(view, view->height);
745 return view_screenline_begin(view->selection);
746 }
747
748 size_t view_scroll_halfpage_up(View *view) {
749 Selection *sel = view->selection;
750 if (view->start == 0) {
751 view_cursors_to(view->selection, 0);
752 } else {
753 view_cursors_to(view->selection, view->start-1);
754 view_redraw_center(view);
755 view_screenline_begin(sel);
756 }
757 return sel->pos;
758 }
759
760 size_t view_scroll_halfpage_down(View *view) {
761 size_t end = view->end;
762 size_t pos = view_scroll_down(view, view->height/2);
763 if (pos < text_size(view->text))
764 view_cursors_to(view->selection, end);
765 return view->selection->pos;
766 }
767
768 size_t view_scroll_down(View *view, int lines) {
769 Selection *sel = view->selection;
770 if (view_viewport_down(view, lines)) {
771 Line *line = sel->line > view->topline ? sel->line : view->topline;
772 cursor_set(sel, line, sel->col);
773 } else {
774 view_cursors_to(view->selection, text_size(view->text));
775 }
776 return sel->pos;
777 }
778
779 size_t view_line_up(Selection *sel) {
780 View *view = sel->view;
781 int lastcol = sel->lastcol;
782 if (!lastcol)
783 lastcol = sel->col;
784 size_t pos = text_line_up(sel->view->text, sel->pos);
785 bool offscreen = view->selection == sel && pos < view->start;
786 view_cursors_to(sel, pos);
787 if (offscreen)
788 view_redraw_top(view);
789 if (sel->line)
790 cursor_set(sel, sel->line, lastcol);
791 sel->lastcol = lastcol;
792 return sel->pos;
793 }
794
795 size_t view_line_down(Selection *sel) {
796 View *view = sel->view;
797 int lastcol = sel->lastcol;
798 if (!lastcol)
799 lastcol = sel->col;
800 size_t pos = text_line_down(sel->view->text, sel->pos);
801 bool offscreen = view->selection == sel && pos > view->end;
802 view_cursors_to(sel, pos);
803 if (offscreen)
804 view_redraw_bottom(view);
805 if (sel->line)
806 cursor_set(sel, sel->line, lastcol);
807 sel->lastcol = lastcol;
808 return sel->pos;
809 }
810
811 size_t view_screenline_up(Selection *sel) {
812 if (!sel->line)
813 return view_line_up(sel);
814 int lastcol = sel->lastcol;
815 if (!lastcol)
816 lastcol = sel->col;
817 if (!sel->line->prev)
818 view_scroll_up(sel->view, 1);
819 if (sel->line->prev)
820 cursor_set(sel, sel->line->prev, lastcol);
821 sel->lastcol = lastcol;
822 return sel->pos;
823 }
824
825 size_t view_screenline_down(Selection *sel) {
826 if (!sel->line)
827 return view_line_down(sel);
828 int lastcol = sel->lastcol;
829 if (!lastcol)
830 lastcol = sel->col;
831 if (!sel->line->next && sel->line == sel->view->bottomline)
832 view_scroll_down(sel->view, 1);
833 if (sel->line->next)
834 cursor_set(sel, sel->line->next, lastcol);
835 sel->lastcol = lastcol;
836 return sel->pos;
837 }
838
839 size_t view_screenline_begin(Selection *sel) {
840 if (!sel->line)
841 return sel->pos;
842 return cursor_set(sel, sel->line, 0);
843 }
844
845 size_t view_screenline_middle(Selection *sel) {
846 if (!sel->line)
847 return sel->pos;
848 return cursor_set(sel, sel->line, sel->line->width / 2);
849 }
850
851 size_t view_screenline_end(Selection *sel) {
852 if (!sel->line)
853 return sel->pos;
854 int col = sel->line->width - 1;
855 return cursor_set(sel, sel->line, col >= 0 ? col : 0);
856 }
857
858 size_t view_cursor_get(View *view) {
859 return view_cursors_pos(view->selection);
860 }
861
862 void view_scroll_to(View *view, size_t pos) {
863 view_cursors_scroll_to(view->selection, pos);
864 }
865
866 void win_options_set(Win *win, enum UiOption options) {
867 const int mapping[] = {
868 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
869 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
870 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
871 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
872 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
873 };
874
875 for (int i = 0; i < LENGTH(mapping); i++) {
876 win->view.symbols[i] = (options & mapping[i]) ? symbols_default[i] :
877 symbols_none[i];
878 }
879
880 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
881 options &= ~UI_OPTION_LARGE_FILE;
882
883 win->view.large_file = (options & UI_OPTION_LARGE_FILE);
884
885 ui_window_options_set(win, options);
886 }
887
888 bool view_breakat_set(View *view, const char *breakat) {
889 char *copy = strdup(breakat);
890 if (!copy)
891 return false;
892 free(view->breakat);
893 view->breakat = copy;
894 return true;
895 }
896
897 size_t view_screenline_goto(View *view, int n) {
898 size_t pos = view->start;
899 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
900 pos += line->len;
901 return pos;
902 }
903
904 static Selection *selections_new(View *view, size_t pos, bool force) {
905 if (pos > text_size(view->text))
906 return NULL;
907 Selection *s = calloc(1, sizeof(*s));
908 if (!s)
909 return NULL;
910 s->view = view;
911 s->generation = view->selection_generation;
912 if (!view->selections) {
913 view->selection = s;
914 view->selection_latest = s;
915 view->selections = s;
916 view->selection_count = 1;
917 return s;
918 }
919
920 Selection *prev = NULL, *next = NULL;
921 Selection *latest = view->selection_latest ? view->selection_latest : view->selection;
922 size_t cur = view_cursors_pos(latest);
923 if (pos == cur) {
924 prev = latest;
925 next = prev->next;
926 } else if (pos > cur) {
927 prev = latest;
928 for (next = prev->next; next; prev = next, next = next->next) {
929 cur = view_cursors_pos(next);
930 if (pos <= cur)
931 break;
932 }
933 } else if (pos < cur) {
934 next = latest;
935 for (prev = next->prev; prev; next = prev, prev = prev->prev) {
936 cur = view_cursors_pos(prev);
937 if (pos >= cur)
938 break;
939 }
940 }
941
942 if (pos == cur && !force)
943 goto err;
944
945 for (Selection *after = next; after; after = after->next)
946 after->number++;
947
948 s->prev = prev;
949 s->next = next;
950 if (next)
951 next->prev = s;
952 if (prev) {
953 prev->next = s;
954 s->number = prev->number + 1;
955 } else {
956 view->selections = s;
957 }
958 view->selection_latest = s;
959 view->selection_count++;
960 view_selections_dispose(view->selection_dead);
961 view_cursors_to(s, pos);
962 return s;
963 err:
964 free(s);
965 return NULL;
966 }
967
968 Selection *view_selections_new(View *view, size_t pos) {
969 return selections_new(view, pos, false);
970 }
971
972 Selection *view_selections_new_force(View *view, size_t pos) {
973 return selections_new(view, pos, true);
974 }
975
976 int view_selections_number(Selection *sel) {
977 return sel->number;
978 }
979
980 int view_selections_column_count(View *view) {
981 Text *txt = view->text;
982 int cpl_max = 0, cpl = 0; /* cursors per line */
983 size_t line_prev = 0;
984 for (Selection *sel = view->selections; sel; sel = sel->next) {
985 size_t pos = view_cursors_pos(sel);
986 size_t line = text_lineno_by_pos(txt, pos);
987 if (line == line_prev)
988 cpl++;
989 else
990 cpl = 1;
991 line_prev = line;
992 if (cpl > cpl_max)
993 cpl_max = cpl;
994 }
995 return cpl_max;
996 }
997
998 static Selection *selections_column_next(View *view, Selection *sel, int column) {
999 size_t line_cur = 0;
1000 int column_cur = 0;
1001 Text *txt = view->text;
1002 if (sel) {
1003 size_t pos = view_cursors_pos(sel);
1004 line_cur = text_lineno_by_pos(txt, pos);
1005 column_cur = INT_MIN;
1006 } else {
1007 sel = view->selections;
1008 }
1009
1010 for (; sel; sel = sel->next) {
1011 size_t pos = view_cursors_pos(sel);
1012 size_t line = text_lineno_by_pos(txt, pos);
1013 if (line != line_cur) {
1014 line_cur = line;
1015 column_cur = 0;
1016 } else {
1017 column_cur++;
1018 }
1019 if (column == column_cur)
1020 return sel;
1021 }
1022 return NULL;
1023 }
1024
1025 Selection *view_selections_column(View *view, int column) {
1026 return selections_column_next(view, NULL, column);
1027 }
1028
1029 Selection *view_selections_column_next(Selection *sel, int column) {
1030 return selections_column_next(sel->view, sel, column);
1031 }
1032
1033 static void selection_free(Selection *s) {
1034 if (!s)
1035 return;
1036 for (Selection *after = s->next; after; after = after->next)
1037 after->number--;
1038 if (s->prev)
1039 s->prev->next = s->next;
1040 if (s->next)
1041 s->next->prev = s->prev;
1042 if (s->view->selections == s)
1043 s->view->selections = s->next;
1044 if (s->view->selection == s)
1045 s->view->selection = s->next ? s->next : s->prev;
1046 if (s->view->selection_dead == s)
1047 s->view->selection_dead = NULL;
1048 if (s->view->selection_latest == s)
1049 s->view->selection_latest = s->prev ? s->prev : s->next;
1050 s->view->selection_count--;
1051 free(s);
1052 }
1053
1054 bool view_selections_dispose(Selection *sel) {
1055 if (!sel)
1056 return true;
1057 View *view = sel->view;
1058 if (!view->selections || !view->selections->next)
1059 return false;
1060 selection_free(sel);
1061 view_selections_primary_set(view->selection);
1062 return true;
1063 }
1064
1065 bool view_selections_dispose_force(Selection *sel) {
1066 if (view_selections_dispose(sel))
1067 return true;
1068 View *view = sel->view;
1069 if (view->selection_dead)
1070 return false;
1071 view_selection_clear(sel);
1072 view->selection_dead = sel;
1073 return true;
1074 }
1075
1076 Selection *view_selection_disposed(View *view) {
1077 Selection *sel = view->selection_dead;
1078 view->selection_dead = NULL;
1079 return sel;
1080 }
1081
1082 Selection *view_selections(View *view) {
1083 view->selection_generation++;
1084 return view->selections;
1085 }
1086
1087 Selection *view_selections_primary_get(View *view) {
1088 view->selection_generation++;
1089 return view->selection;
1090 }
1091
1092 void view_selections_primary_set(Selection *s) {
1093 if (!s)
1094 return;
1095 s->view->selection = s;
1096 Mark anchor = s->anchor;
1097 view_cursors_to(s, view_cursors_pos(s));
1098 s->anchor = anchor;
1099 }
1100
1101 Selection *view_selections_prev(Selection *s) {
1102 View *view = s->view;
1103 for (s = s->prev; s; s = s->prev) {
1104 if (s->generation != view->selection_generation)
1105 return s;
1106 }
1107 view->selection_generation++;
1108 return NULL;
1109 }
1110
1111 Selection *view_selections_next(Selection *s) {
1112 View *view = s->view;
1113 for (s = s->next; s; s = s->next) {
1114 if (s->generation != view->selection_generation)
1115 return s;
1116 }
1117 view->selection_generation++;
1118 return NULL;
1119 }
1120
1121 size_t view_cursors_pos(Selection *s) {
1122 return text_mark_get(s->view->text, s->cursor);
1123 }
1124
1125 size_t view_cursors_line(Selection *s) {
1126 size_t pos = view_cursors_pos(s);
1127 return text_lineno_by_pos(s->view->text, pos);
1128 }
1129
1130 size_t view_cursors_col(Selection *s) {
1131 size_t pos = view_cursors_pos(s);
1132 return text_line_char_get(s->view->text, pos) + 1;
1133 }
1134
1135 int view_cursors_cell_set(Selection *s, int cell) {
1136 if (!s->line || cell < 0)
1137 return -1;
1138 cursor_set(s, s->line, cell);
1139 return s->col;
1140 }
1141
1142 void view_cursors_scroll_to(Selection *s, size_t pos) {
1143 View *view = s->view;
1144 if (view->selection == s) {
1145 view_draw(view);
1146 while (pos < view->start && view_viewport_up(view, 1));
1147 while (pos > view->end && view_viewport_down(view, 1));
1148 }
1149 view_cursors_to(s, pos);
1150 }
1151
1152 void view_cursors_to(Selection *s, size_t pos) {
1153 View *view = s->view;
1154 if (pos == EPOS)
1155 return;
1156 size_t size = text_size(view->text);
1157 if (pos > size)
1158 pos = size;
1159 if (s->view->selection == s) {
1160 /* make sure we redraw changes to the very first character of the window */
1161 if (view->start == pos)
1162 view->start_last = 0;
1163
1164 if (view->end == pos && view->lastline == view->bottomline) {
1165 view->start += view->topline->len;
1166 view_draw(view);
1167 }
1168
1169 if (pos < view->start || pos > view->end) {
1170 view->start = pos;
1171 view_viewport_up(view, view->height / 2);
1172 }
1173
1174 if (pos <= view->start || pos > view->end) {
1175 view->start = text_line_begin(view->text, pos);
1176 view_draw(view);
1177 }
1178
1179 if (pos <= view->start || pos > view->end) {
1180 view->start = pos;
1181 view_draw(view);
1182 }
1183 }
1184
1185 cursor_to(s, pos);
1186 }
1187
1188 void view_cursors_place(Selection *s, size_t line, size_t col) {
1189 Text *txt = s->view->text;
1190 size_t pos = text_pos_by_lineno(txt, line);
1191 pos = text_line_char_set(txt, pos, col > 0 ? col-1 : col);
1192 view_cursors_to(s, pos);
1193 }
1194
1195 void view_selection_clear(Selection *s) {
1196 s->anchored = false;
1197 s->anchor = s->cursor;
1198 s->view->need_update = true;
1199 }
1200
1201 void view_selections_flip(Selection *s) {
1202 Mark temp = s->anchor;
1203 s->anchor = s->cursor;
1204 s->cursor = temp;
1205 view_cursors_to(s, text_mark_get(s->view->text, s->cursor));
1206 }
1207
1208 void view_selections_clear_all(View *view) {
1209 for (Selection *s = view->selections; s; s = s->next)
1210 view_selection_clear(s);
1211 view_draw(view);
1212 }
1213
1214 void view_selections_dispose_all(View *view) {
1215 Selection *last = view->selections;
1216 while (last->next)
1217 last = last->next;
1218 for (Selection *s = last, *prev; s; s = prev) {
1219 prev = s->prev;
1220 if (s != view->selection)
1221 selection_free(s);
1222 }
1223 view_draw(view);
1224 }
1225
1226 Filerange view_selections_get(Selection *s) {
1227 if (!s)
1228 return text_range_empty();
1229 Text *txt = s->view->text;
1230 size_t anchor = text_mark_get(txt, s->anchor);
1231 size_t cursor = text_mark_get(txt, s->cursor);
1232 Filerange sel = text_range_new(anchor, cursor);
1233 if (text_range_valid(&sel))
1234 sel.end = text_char_next(txt, sel.end);
1235 return sel;
1236 }
1237
1238 bool view_selections_set(Selection *s, const Filerange *r) {
1239 Text *txt = s->view->text;
1240 size_t max = text_size(txt);
1241 if (!text_range_valid(r) || r->start >= max)
1242 return false;
1243 size_t anchor = text_mark_get(txt, s->anchor);
1244 size_t cursor = text_mark_get(txt, s->cursor);
1245 bool left_extending = anchor != EPOS && anchor > cursor;
1246 size_t end = r->end > max ? max : r->end;
1247 if (r->start != end)
1248 end = text_char_prev(txt, end);
1249 view_cursors_to(s, left_extending ? r->start : end);
1250 s->anchor = text_mark_set(txt, left_extending ? end : r->start);
1251 return true;
1252 }
1253
1254 Filerange view_regions_restore(View *view, SelectionRegion *s) {
1255 Text *txt = view->text;
1256 size_t anchor = text_mark_get(txt, s->anchor);
1257 size_t cursor = text_mark_get(txt, s->cursor);
1258 Filerange sel = text_range_new(anchor, cursor);
1259 if (text_range_valid(&sel))
1260 sel.end = text_char_next(txt, sel.end);
1261 return sel;
1262 }
1263
1264 bool view_regions_save(View *view, Filerange *r, SelectionRegion *s) {
1265 Text *txt = view->text;
1266 size_t max = text_size(txt);
1267 if (!text_range_valid(r) || r->start >= max)
1268 return false;
1269 size_t end = r->end > max ? max : r->end;
1270 if (r->start != end)
1271 end = text_char_prev(txt, end);
1272 s->anchor = text_mark_set(txt, r->start);
1273 s->cursor = text_mark_set(txt, end);
1274 return true;
1275 }
1276
1277 void view_selections_set_all(View *view, Array *arr, bool anchored) {
1278 Selection *s;
1279 Filerange *r;
1280 size_t i = 0;
1281 for (s = view->selections; s; s = s->next) {
1282 if (!(r = array_get(arr, i++)) || !view_selections_set(s, r)) {
1283 for (Selection *next; s; s = next) {
1284 next = view_selections_next(s);
1285 if (i == 1 && s == view->selection)
1286 view_selection_clear(s);
1287 else
1288 view_selections_dispose(s);
1289 }
1290 break;
1291 }
1292 s->anchored = anchored;
1293 }
1294 while ((r = array_get(arr, i++))) {
1295 s = view_selections_new_force(view, r->start);
1296 if (!s || !view_selections_set(s, r))
1297 break;
1298 s->anchored = anchored;
1299 }
1300 view_selections_primary_set(view->selections);
1301 }
1302
1303 Array view_selections_get_all(View *view) {
1304 Array arr;
1305 array_init_sized(&arr, sizeof(Filerange));
1306 if (!array_reserve(&arr, view->selection_count))
1307 return arr;
1308 for (Selection *s = view->selections; s; s = s->next) {
1309 Filerange r = view_selections_get(s);
1310 if (text_range_valid(&r))
1311 array_add(&arr, &r);
1312 }
1313 return arr;
1314 }
1315
1316 void view_selections_normalize(View *view) {
1317 Selection *prev = NULL;
1318 Filerange range_prev = text_range_empty();
1319 for (Selection *s = view->selections, *next; s; s = next) {
1320 next = s->next;
1321 Filerange range = view_selections_get(s);
1322 if (!text_range_valid(&range)) {
1323 view_selections_dispose(s);
1324 } else if (prev && text_range_overlap(&range_prev, &range)) {
1325 range_prev = text_range_union(&range_prev, &range);
1326 view_selections_dispose(s);
1327 } else {
1328 if (prev)
1329 view_selections_set(prev, &range_prev);
1330 range_prev = range;
1331 prev = s;
1332 }
1333 }
1334 if (prev)
1335 view_selections_set(prev, &range_prev);
1336 }
1337
1338 void win_style(Win *win, enum UiStyle style, size_t start, size_t end, bool keep_non_default) {
1339 View *view = &win->view;
1340 if (end < view->start || start > view->end)
1341 return;
1342
1343 size_t pos = view->start;
1344 Line *line = view->topline;
1345
1346 /* skip lines before range to be styled */
1347 while (line && pos + line->len <= start) {
1348 pos += line->len;
1349 line = line->next;
1350 }
1351
1352 if (!line)
1353 return;
1354
1355 int col = 0, width = view->width;
1356
1357 /* skip columns before range to be styled */
1358 while (pos < start && col < width)
1359 pos += line->cells[col++].len;
1360
1361 /* skip empty columns */
1362 while (!line->cells[col].len && col < width)
1363 col++;
1364
1365 do {
1366 while (pos <= end && col < width) {
1367 pos += line->cells[col].len;
1368 ui_window_style_set(&win->vis->ui, win->id, &line->cells[col++], style, keep_non_default);
1369 }
1370 col = 0;
1371 } while (pos <= end && (line = line->next));
1372 }