filetype: Set "groovy" for Jenkinsfile
[vis.git] / view.c
blob74967dc610590832e3b47bdb0bae9c0ad85a58e8
1 #include <string.h>
2 #include <stdlib.h>
3 #include <wchar.h>
4 #include <ctype.h>
5 #include <errno.h>
6 #include <limits.h>
7 #include "view.h"
8 #include "text.h"
9 #include "text-motions.h"
10 #include "text-util.h"
11 #include "util.h"
13 typedef struct {
14 char *symbol;
15 } SyntaxSymbol;
17 enum {
18 SYNTAX_SYMBOL_SPACE,
19 SYNTAX_SYMBOL_TAB,
20 SYNTAX_SYMBOL_TAB_FILL,
21 SYNTAX_SYMBOL_EOL,
22 SYNTAX_SYMBOL_EOF,
23 SYNTAX_SYMBOL_LAST,
26 /* A selection is made up of two marks named cursor and anchor.
27 * While the anchor remains fixed the cursor mark follows cursor motions.
28 * For a selection (indicated by []), the marks (^) are placed as follows:
30 * [some text] [!]
31 * ^ ^ ^
32 * ^
34 * That is the marks point to the *start* of the first and last character
35 * of the selection. In particular for a single character selection (as
36 * depicted on the right above) both marks point to the same location.
38 * The view_selections_{get,set} functions take care of adding/removing
39 * the necessary offset for the last character.
42 struct Selection {
43 Mark cursor; /* other selection endpoint where it changes */
44 Mark anchor; /* position where the selection was created */
45 bool anchored; /* whether anchor remains fixed */
46 size_t pos; /* in bytes from the start of the file */
47 int row, col; /* in terms of zero based screen coordinates */
48 int lastcol; /* remembered column used when moving across lines */
49 Line *line; /* screen line on which cursor currently resides */
50 int generation; /* used to filter out newly created cursors during iteration */
51 int number; /* how many cursors are located before this one */
52 View *view; /* associated view to which this cursor belongs */
53 Selection *prev, *next; /* previous/next cursors ordered by location at creation time */
56 struct View {
57 Text *text; /* underlying text management */
58 char *textbuf; /* scratch buffer used for drawing */
59 UiWin *ui; /* corresponding ui window */
60 Cell cell_blank; /* used for empty/blank cells */
61 int width, height; /* size of display area */
62 size_t start, end; /* currently displayed area [start, end] in bytes from the start of the file */
63 size_t start_last; /* previously used start of visible area, used to update the mark */
64 Mark start_mark; /* mark to keep track of the start of the visible area */
65 size_t lines_size; /* number of allocated bytes for lines (grows only) */
66 Line *lines; /* view->height number of lines representing view content */
67 Line *topline; /* top of the view, first line currently shown */
68 Line *lastline; /* last currently used line, always <= bottomline */
69 Line *bottomline; /* bottom of view, might be unused if lastline < bottomline */
70 Selection *selection; /* primary selection, always placed within the visible viewport */
71 Selection *selection_latest; /* most recently created cursor */
72 Selection *selection_dead; /* primary cursor which was disposed, will be removed when another cursor is created */
73 int selection_count; /* how many cursors do currently exist */
74 Line *line; /* used while drawing view content, line where next char will be drawn */
75 int col; /* used while drawing view content, column where next char will be drawn */
76 const SyntaxSymbol *symbols[SYNTAX_SYMBOL_LAST]; /* symbols to use for white spaces etc */
77 int tabwidth; /* how many spaces should be used to display a tab character */
78 Selection *selections; /* all cursors currently active */
79 int selection_generation; /* used to filter out newly created cursors during iteration */
80 bool need_update; /* whether view has been redrawn */
81 bool large_file; /* optimize for displaying large files */
82 int colorcolumn;
85 static const SyntaxSymbol symbols_none[] = {
86 [SYNTAX_SYMBOL_SPACE] = { " " },
87 [SYNTAX_SYMBOL_TAB] = { " " },
88 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
89 [SYNTAX_SYMBOL_EOL] = { " " },
90 [SYNTAX_SYMBOL_EOF] = { " " },
93 static const SyntaxSymbol symbols_default[] = {
94 [SYNTAX_SYMBOL_SPACE] = { "·" /* Middle Dot U+00B7 */ },
95 [SYNTAX_SYMBOL_TAB] = { "›" /* Single Right-Pointing Angle Quotation Mark U+203A */ },
96 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
97 [SYNTAX_SYMBOL_EOL] = { "↵" /* Downwards Arrow with Corner Leftwards U+21B5 */ },
98 [SYNTAX_SYMBOL_EOF] = { "~" },
101 static Cell cell_unused;
104 /* move visible viewport n-lines up/down, redraws the view but does not change
105 * cursor position which becomes invalid and should be corrected by calling
106 * view_cursor_to. the return value indicates wether the visible area changed.
108 static bool view_viewport_up(View *view, int n);
109 static bool view_viewport_down(View *view, int n);
111 static void view_clear(View *view);
112 static bool view_addch(View *view, Cell *cell);
113 static void selection_free(Selection*);
114 /* set/move current cursor position to a given (line, column) pair */
115 static size_t cursor_set(Selection*, Line *line, int col);
117 void view_tabwidth_set(View *view, int tabwidth) {
118 view->tabwidth = tabwidth;
119 view_draw(view);
122 /* reset internal view data structures (cell matrix, line offsets etc.) */
123 static void view_clear(View *view) {
124 memset(view->lines, 0, view->lines_size);
125 if (view->start != view->start_last) {
126 if (view->start == 0)
127 view->start_mark = EMARK;
128 else
129 view->start_mark = text_mark_set(view->text, view->start);
130 } else {
131 size_t start;
132 if (view->start_mark == EMARK)
133 start = 0;
134 else
135 start = text_mark_get(view->text, view->start_mark);
136 if (start != EPOS)
137 view->start = start;
140 view->start_last = view->start;
141 view->topline = view->lines;
142 view->topline->lineno = view->large_file ? 1 : text_lineno_by_pos(view->text, view->start);
143 view->lastline = view->topline;
145 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
146 size_t end = view->height * line_size;
147 Line *prev = NULL;
148 for (size_t i = 0; i < end; i += line_size) {
149 Line *line = (Line*)(((char*)view->lines) + i);
150 line->prev = prev;
151 if (prev)
152 prev->next = line;
153 prev = line;
155 view->bottomline = prev ? prev : view->topline;
156 view->bottomline->next = NULL;
157 view->line = view->topline;
158 view->col = 0;
159 if (view->ui)
160 view->cell_blank.style = view->ui->style_get(view->ui, UI_STYLE_DEFAULT);
163 Filerange view_viewport_get(View *view) {
164 return (Filerange){ .start = view->start, .end = view->end };
167 /* try to add another character to the view, return whether there was space left */
168 static bool view_addch(View *view, Cell *cell) {
169 if (!view->line)
170 return false;
172 int width;
173 size_t lineno = view->line->lineno;
174 unsigned char ch = (unsigned char)cell->data[0];
175 cell->style = view->cell_blank.style;
177 switch (ch) {
178 case '\t':
179 cell->width = 1;
180 width = view->tabwidth - (view->col % view->tabwidth);
181 for (int w = 0; w < width; w++) {
182 if (view->col + 1 > view->width) {
183 view->line = view->line->next;
184 view->col = 0;
185 if (!view->line)
186 return false;
187 view->line->lineno = lineno;
190 cell->len = w == 0 ? 1 : 0;
191 int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
192 strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
193 view->line->cells[view->col] = *cell;
194 view->line->len += cell->len;
195 view->line->width += cell->width;
196 view->col++;
198 cell->len = 1;
199 return true;
200 case '\n':
201 cell->width = 1;
202 if (view->col + cell->width > view->width) {
203 view->line = view->line->next;
204 view->col = 0;
205 if (!view->line)
206 return false;
207 view->line->lineno = lineno;
210 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
212 view->line->cells[view->col] = *cell;
213 view->line->len += cell->len;
214 view->line->width += cell->width;
215 for (int i = view->col + 1; i < view->width; i++)
216 view->line->cells[i] = view->cell_blank;
218 view->line = view->line->next;
219 if (view->line)
220 view->line->lineno = lineno + 1;
221 view->col = 0;
222 return true;
223 default:
224 if (ch < 128 && !isprint(ch)) {
225 /* non-printable ascii char, represent it as ^(char + 64) */
226 *cell = (Cell) {
227 .data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
228 .len = 1,
229 .width = 2,
230 .style = cell->style,
234 if (ch == ' ') {
235 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
239 if (view->col + cell->width > view->width) {
240 for (int i = view->col; i < view->width; i++)
241 view->line->cells[i] = view->cell_blank;
242 view->line = view->line->next;
243 view->col = 0;
246 if (view->line) {
247 view->line->width += cell->width;
248 view->line->len += cell->len;
249 view->line->lineno = lineno;
250 view->line->cells[view->col] = *cell;
251 view->col++;
252 /* set cells of a character which uses multiple columns */
253 for (int i = 1; i < cell->width; i++)
254 view->line->cells[view->col++] = cell_unused;
255 return true;
257 return false;
261 static void cursor_to(Selection *s, size_t pos) {
262 Text *txt = s->view->text;
263 s->cursor = text_mark_set(txt, pos);
264 if (!s->anchored)
265 s->anchor = s->cursor;
266 if (pos != s->pos)
267 s->lastcol = 0;
268 s->pos = pos;
269 if (!view_coord_get(s->view, pos, &s->line, &s->row, &s->col)) {
270 if (s->view->selection == s) {
271 s->line = s->view->topline;
272 s->row = 0;
273 s->col = 0;
275 return;
277 // TODO: minimize number of redraws
278 view_draw(s->view);
281 bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
282 int row = 0, col = 0;
283 size_t cur = view->start;
284 Line *line = view->topline;
286 if (pos < view->start || pos > view->end) {
287 if (retline) *retline = NULL;
288 if (retrow) *retrow = -1;
289 if (retcol) *retcol = -1;
290 return false;
293 while (line && line != view->lastline && cur < pos) {
294 if (cur + line->len > pos)
295 break;
296 cur += line->len;
297 line = line->next;
298 row++;
301 if (line) {
302 int max_col = MIN(view->width, line->width);
303 while (cur < pos && col < max_col) {
304 cur += line->cells[col].len;
305 /* skip over columns occupied by the same character */
306 while (++col < max_col && line->cells[col].len == 0);
308 } else {
309 line = view->bottomline;
310 row = view->height - 1;
313 if (retline) *retline = line;
314 if (retrow) *retrow = row;
315 if (retcol) *retcol = col;
316 return true;
319 /* move the cursor to the character at pos bytes from the begining of the file.
320 * if pos is not in the current viewport, redraw the view to make it visible */
321 void view_cursor_to(View *view, size_t pos) {
322 view_cursors_to(view->selection, pos);
325 /* redraw the complete with data starting from view->start bytes into the file.
326 * stop once the screen is full, update view->end, view->lastline */
327 void view_draw(View *view) {
328 view_clear(view);
329 /* read a screenful of text considering each character as 4-byte UTF character*/
330 const size_t size = view->width * view->height * 4;
331 /* current buffer to work with */
332 char *text = view->textbuf;
333 /* remaining bytes to process in buffer */
334 size_t rem = text_bytes_get(view->text, view->start, size, text);
335 /* NUL terminate text section */
336 text[rem] = '\0';
337 /* absolute position of character currently being added to display */
338 size_t pos = view->start;
339 /* current position into buffer from which to interpret a character */
340 char *cur = text;
341 /* start from known multibyte state */
342 mbstate_t mbstate = { 0 };
344 Cell cell = { .data = "", .len = 0, .width = 0, }, prev_cell = cell;
346 while (rem > 0) {
348 /* current 'parsed' character' */
349 wchar_t wchar;
351 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
352 if (len == (size_t)-1 && errno == EILSEQ) {
353 /* ok, we encountered an invalid multibyte sequence,
354 * replace it with the Unicode Replacement Character
355 * (FFFD) and skip until the start of the next utf8 char */
356 mbstate = (mbstate_t){0};
357 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
358 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
359 } else if (len == (size_t)-2) {
360 /* not enough bytes available to convert to a
361 * wide character. Advance file position and read
362 * another junk into buffer.
364 rem = text_bytes_get(view->text, pos+prev_cell.len, size, text);
365 text[rem] = '\0';
366 cur = text;
367 continue;
368 } else if (len == 0) {
369 /* NUL byte encountered, store it and continue */
370 cell = (Cell){ .data = "\x00", .len = 1, .width = 2 };
371 } else {
372 if (len >= sizeof(cell.data))
373 len = sizeof(cell.data)-1;
374 for (size_t i = 0; i < len; i++)
375 cell.data[i] = cur[i];
376 cell.data[len] = '\0';
377 cell.len = len;
378 cell.width = wcwidth(wchar);
379 if (cell.width == -1)
380 cell.width = 1;
383 if (cell.width == 0) {
384 strncat(prev_cell.data, cell.data, sizeof(prev_cell.data)-strlen(prev_cell.data)-1);
385 prev_cell.len += cell.len;
386 } else {
387 if (prev_cell.len && !view_addch(view, &prev_cell))
388 break;
389 pos += prev_cell.len;
390 prev_cell = cell;
393 rem -= cell.len;
394 cur += cell.len;
396 memset(&cell, 0, sizeof cell);
399 if (prev_cell.len && view_addch(view, &prev_cell))
400 pos += prev_cell.len;
402 /* set end of viewing region */
403 view->end = pos;
404 if (view->line) {
405 bool eof = view->end == text_size(view->text);
406 if (view->line->len == 0 && eof && view->line->prev)
407 view->lastline = view->line->prev;
408 else
409 view->lastline = view->line;
410 } else {
411 view->lastline = view->bottomline;
414 /* clear remaining of line, important to show cursor at end of file */
415 if (view->line) {
416 for (int x = view->col; x < view->width; x++)
417 view->line->cells[x] = view->cell_blank;
420 /* resync position of cursors within visible area */
421 for (Selection *s = view->selections; s; s = s->next) {
422 size_t pos = view_cursors_pos(s);
423 if (!view_coord_get(view, pos, &s->line, &s->row, &s->col) &&
424 s == view->selection) {
425 s->line = view->topline;
426 s->row = 0;
427 s->col = 0;
431 view->need_update = true;
434 void view_invalidate(View *view) {
435 view->need_update = true;
438 bool view_update(View *view) {
439 if (!view->need_update)
440 return false;
441 for (Line *l = view->lastline->next; l; l = l->next) {
442 for (int x = 0; x < view->width; x++)
443 l->cells[x] = view->cell_blank;
445 view->need_update = false;
446 return true;
449 bool view_resize(View *view, int width, int height) {
450 if (width <= 0)
451 width = 1;
452 if (height <= 0)
453 height = 1;
454 if (view->width == width && view->height == height) {
455 view->need_update = true;
456 return true;
458 char *textbuf = malloc(width * height * 4 + 1);
459 if (!textbuf)
460 return false;
461 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
462 if (lines_size > view->lines_size) {
463 Line *lines = realloc(view->lines, lines_size);
464 if (!lines) {
465 free(textbuf);
466 return false;
468 view->lines = lines;
469 view->lines_size = lines_size;
471 free(view->textbuf);
472 view->textbuf = textbuf;
473 view->width = width;
474 view->height = height;
475 memset(view->lines, 0, view->lines_size);
476 view_draw(view);
477 return true;
480 int view_height_get(View *view) {
481 return view->height;
484 int view_width_get(View *view) {
485 return view->width;
488 void view_free(View *view) {
489 if (!view)
490 return;
491 while (view->selections)
492 selection_free(view->selections);
493 free(view->textbuf);
494 free(view->lines);
495 free(view);
498 void view_reload(View *view, Text *text) {
499 view->text = text;
500 view_selections_clear_all(view);
501 view_cursor_to(view, 0);
504 View *view_new(Text *text) {
505 if (!text)
506 return NULL;
507 View *view = calloc(1, sizeof(View));
508 if (!view)
509 return NULL;
510 view->text = text;
511 if (!view_selections_new(view, 0)) {
512 view_free(view);
513 return NULL;
516 view->cell_blank = (Cell) {
517 .width = 0,
518 .len = 0,
519 .data = " ",
521 view->tabwidth = 8;
522 view_options_set(view, 0);
524 if (!view_resize(view, 1, 1)) {
525 view_free(view);
526 return NULL;
529 view_cursor_to(view, 0);
531 return view;
534 void view_ui(View *view, UiWin* ui) {
535 view->ui = ui;
538 static size_t cursor_set(Selection *sel, Line *line, int col) {
539 int row = 0;
540 View *view = sel->view;
541 size_t pos = view->start;
542 /* get row number and file offset at start of the given line */
543 for (Line *l = view->topline; l && l != line; l = l->next) {
544 pos += l->len;
545 row++;
548 /* for characters which use more than 1 column, make sure we are on the left most */
549 while (col > 0 && line->cells[col].len == 0)
550 col--;
551 /* calculate offset within the line */
552 for (int i = 0; i < col; i++)
553 pos += line->cells[i].len;
555 sel->col = col;
556 sel->row = row;
557 sel->line = line;
559 cursor_to(sel, pos);
561 return pos;
564 static bool view_viewport_down(View *view, int n) {
565 Line *line;
566 if (view->end >= text_size(view->text))
567 return false;
568 if (n >= view->height) {
569 view->start = view->end;
570 } else {
571 for (line = view->topline; line && n > 0; line = line->next, n--)
572 view->start += line->len;
574 view_draw(view);
575 return true;
578 static bool view_viewport_up(View *view, int n) {
579 /* scrolling up is somewhat tricky because we do not yet know where
580 * the lines start, therefore scan backwards but stop at a reasonable
581 * maximum in case we are dealing with a file without any newlines
583 if (view->start == 0)
584 return false;
585 size_t max = view->width * view->height;
586 char c;
587 Iterator it = text_iterator_get(view->text, view->start - 1);
589 if (!text_iterator_byte_get(&it, &c))
590 return false;
591 size_t off = 0;
592 /* skip newlines immediately before display area */
593 if (c == '\n' && text_iterator_byte_prev(&it, &c))
594 off++;
595 do {
596 if (c == '\n' && --n == 0)
597 break;
598 if (++off > max)
599 break;
600 } while (text_iterator_byte_prev(&it, &c));
601 view->start -= MIN(view->start, off);
602 view_draw(view);
603 return true;
606 void view_redraw_top(View *view) {
607 Line *line = view->selection->line;
608 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
609 view->start += cur->len;
610 view_draw(view);
611 view_cursor_to(view, view->selection->pos);
614 void view_redraw_center(View *view) {
615 int center = view->height / 2;
616 size_t pos = view->selection->pos;
617 for (int i = 0; i < 2; i++) {
618 int linenr = 0;
619 Line *line = view->selection->line;
620 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
621 linenr++;
622 if (linenr < center) {
623 view_slide_down(view, center - linenr);
624 continue;
626 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
627 view->start += cur->len;
628 linenr--;
630 break;
632 view_draw(view);
633 view_cursor_to(view, pos);
636 void view_redraw_bottom(View *view) {
637 size_t pos = view->selection->pos;
638 view_viewport_up(view, view->height);
639 while (pos >= view->end && view_viewport_down(view, 1));
640 cursor_to(view->selection, pos);
643 size_t view_slide_up(View *view, int lines) {
644 Selection *sel = view->selection;
645 if (view_viewport_down(view, lines)) {
646 if (sel->line == view->topline)
647 cursor_set(sel, view->topline, sel->col);
648 else
649 view_cursor_to(view, sel->pos);
650 } else {
651 view_screenline_down(sel);
653 return sel->pos;
656 size_t view_slide_down(View *view, int lines) {
657 Selection *sel = view->selection;
658 bool lastline = sel->line == view->lastline;
659 size_t col = sel->col;
660 if (view_viewport_up(view, lines)) {
661 if (lastline)
662 cursor_set(sel, view->lastline, col);
663 else
664 view_cursor_to(view, sel->pos);
665 } else {
666 view_screenline_up(sel);
668 return sel->pos;
671 size_t view_scroll_up(View *view, int lines) {
672 Selection *sel = view->selection;
673 if (view_viewport_up(view, lines)) {
674 Line *line = sel->line < view->lastline ? sel->line : view->lastline;
675 cursor_set(sel, line, view->selection->col);
676 } else {
677 view_cursor_to(view, 0);
679 return sel->pos;
682 size_t view_scroll_page_up(View *view) {
683 Selection *sel = view->selection;
684 if (view->start == 0) {
685 view_cursor_to(view, 0);
686 } else {
687 view_cursor_to(view, view->start-1);
688 view_redraw_bottom(view);
689 view_screenline_begin(sel);
691 return sel->pos;
694 size_t view_scroll_page_down(View *view) {
695 view_scroll_down(view, view->height);
696 return view_screenline_begin(view->selection);
699 size_t view_scroll_halfpage_up(View *view) {
700 Selection *sel = view->selection;
701 if (view->start == 0) {
702 view_cursor_to(view, 0);
703 } else {
704 view_cursor_to(view, view->start-1);
705 view_redraw_center(view);
706 view_screenline_begin(sel);
708 return sel->pos;
711 size_t view_scroll_halfpage_down(View *view) {
712 size_t end = view->end;
713 size_t pos = view_scroll_down(view, view->height/2);
714 if (pos < text_size(view->text))
715 view_cursor_to(view, end);
716 return view->selection->pos;
719 size_t view_scroll_down(View *view, int lines) {
720 Selection *sel = view->selection;
721 if (view_viewport_down(view, lines)) {
722 Line *line = sel->line > view->topline ? sel->line : view->topline;
723 cursor_set(sel, line, sel->col);
724 } else {
725 view_cursor_to(view, text_size(view->text));
727 return sel->pos;
730 size_t view_line_up(Selection *sel) {
731 View *view = sel->view;
732 int lastcol = sel->lastcol;
733 if (!lastcol)
734 lastcol = sel->col;
735 size_t pos = text_line_up(sel->view->text, sel->pos);
736 bool offscreen = view->selection == sel && pos < view->start;
737 view_cursors_to(sel, pos);
738 if (offscreen)
739 view_redraw_top(view);
740 if (sel->line)
741 cursor_set(sel, sel->line, lastcol);
742 sel->lastcol = lastcol;
743 return sel->pos;
746 size_t view_line_down(Selection *sel) {
747 View *view = sel->view;
748 int lastcol = sel->lastcol;
749 if (!lastcol)
750 lastcol = sel->col;
751 size_t pos = text_line_down(sel->view->text, sel->pos);
752 bool offscreen = view->selection == sel && pos > view->end;
753 view_cursors_to(sel, pos);
754 if (offscreen)
755 view_redraw_bottom(view);
756 if (sel->line)
757 cursor_set(sel, sel->line, lastcol);
758 sel->lastcol = lastcol;
759 return sel->pos;
762 size_t view_screenline_up(Selection *sel) {
763 if (!sel->line)
764 return view_line_up(sel);
765 int lastcol = sel->lastcol;
766 if (!lastcol)
767 lastcol = sel->col;
768 if (!sel->line->prev)
769 view_scroll_up(sel->view, 1);
770 if (sel->line->prev)
771 cursor_set(sel, sel->line->prev, lastcol);
772 sel->lastcol = lastcol;
773 return sel->pos;
776 size_t view_screenline_down(Selection *sel) {
777 if (!sel->line)
778 return view_line_down(sel);
779 int lastcol = sel->lastcol;
780 if (!lastcol)
781 lastcol = sel->col;
782 if (!sel->line->next && sel->line == sel->view->bottomline)
783 view_scroll_down(sel->view, 1);
784 if (sel->line->next)
785 cursor_set(sel, sel->line->next, lastcol);
786 sel->lastcol = lastcol;
787 return sel->pos;
790 size_t view_screenline_begin(Selection *sel) {
791 if (!sel->line)
792 return sel->pos;
793 return cursor_set(sel, sel->line, 0);
796 size_t view_screenline_middle(Selection *sel) {
797 if (!sel->line)
798 return sel->pos;
799 return cursor_set(sel, sel->line, sel->line->width / 2);
802 size_t view_screenline_end(Selection *sel) {
803 if (!sel->line)
804 return sel->pos;
805 int col = sel->line->width - 1;
806 return cursor_set(sel, sel->line, col >= 0 ? col : 0);
809 size_t view_cursor_get(View *view) {
810 return view_cursors_pos(view->selection);
813 Line *view_lines_first(View *view) {
814 return view->topline;
817 Line *view_lines_last(View *view) {
818 return view->lastline;
821 Line *view_cursors_line_get(Selection *sel) {
822 return sel->line;
825 void view_scroll_to(View *view, size_t pos) {
826 view_cursors_scroll_to(view->selection, pos);
829 void view_options_set(View *view, enum UiOption options) {
830 const int mapping[] = {
831 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
832 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
833 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
834 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
835 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
838 for (int i = 0; i < LENGTH(mapping); i++) {
839 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
840 &symbols_none[i];
843 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
844 options &= ~UI_OPTION_LARGE_FILE;
846 view->large_file = (options & UI_OPTION_LARGE_FILE);
848 if (view->ui)
849 view->ui->options_set(view->ui, options);
852 enum UiOption view_options_get(View *view) {
853 return view->ui ? view->ui->options_get(view->ui) : 0;
856 void view_colorcolumn_set(View *view, int col) {
857 if (col >= 0)
858 view->colorcolumn = col;
861 int view_colorcolumn_get(View *view) {
862 return view->colorcolumn;
865 size_t view_screenline_goto(View *view, int n) {
866 size_t pos = view->start;
867 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
868 pos += line->len;
869 return pos;
872 static Selection *selections_new(View *view, size_t pos, bool force) {
873 if (pos > text_size(view->text))
874 return NULL;
875 Selection *s = calloc(1, sizeof(*s));
876 if (!s)
877 return NULL;
878 s->view = view;
879 s->generation = view->selection_generation;
880 if (!view->selections) {
881 view->selection = s;
882 view->selection_latest = s;
883 view->selections = s;
884 view->selection_count = 1;
885 return s;
888 Selection *prev = NULL, *next = NULL;
889 Selection *latest = view->selection_latest ? view->selection_latest : view->selection;
890 size_t cur = view_cursors_pos(latest);
891 if (pos == cur) {
892 prev = latest;
893 next = prev->next;
894 } else if (pos > cur) {
895 prev = latest;
896 for (next = prev->next; next; prev = next, next = next->next) {
897 cur = view_cursors_pos(next);
898 if (pos <= cur)
899 break;
901 } else if (pos < cur) {
902 next = latest;
903 for (prev = next->prev; prev; next = prev, prev = prev->prev) {
904 cur = view_cursors_pos(prev);
905 if (pos >= cur)
906 break;
910 if (pos == cur && !force)
911 goto err;
913 for (Selection *after = next; after; after = after->next)
914 after->number++;
916 s->prev = prev;
917 s->next = next;
918 if (next)
919 next->prev = s;
920 if (prev) {
921 prev->next = s;
922 s->number = prev->number + 1;
923 } else {
924 view->selections = s;
926 view->selection_latest = s;
927 view->selection_count++;
928 view_selections_dispose(view->selection_dead);
929 view_cursors_to(s, pos);
930 return s;
931 err:
932 free(s);
933 return NULL;
936 Selection *view_selections_new(View *view, size_t pos) {
937 return selections_new(view, pos, false);
940 Selection *view_selections_new_force(View *view, size_t pos) {
941 return selections_new(view, pos, true);
944 int view_selections_count(View *view) {
945 return view->selection_count;
948 int view_selections_number(Selection *sel) {
949 return sel->number;
952 int view_selections_column_count(View *view) {
953 Text *txt = view->text;
954 int cpl_max = 0, cpl = 0; /* cursors per line */
955 size_t line_prev = 0;
956 for (Selection *sel = view->selections; sel; sel = sel->next) {
957 size_t pos = view_cursors_pos(sel);
958 size_t line = text_lineno_by_pos(txt, pos);
959 if (line == line_prev)
960 cpl++;
961 else
962 cpl = 1;
963 line_prev = line;
964 if (cpl > cpl_max)
965 cpl_max = cpl;
967 return cpl_max;
970 static Selection *selections_column_next(View *view, Selection *sel, int column) {
971 size_t line_cur = 0;
972 int column_cur = 0;
973 Text *txt = view->text;
974 if (sel) {
975 size_t pos = view_cursors_pos(sel);
976 line_cur = text_lineno_by_pos(txt, pos);
977 column_cur = INT_MIN;
978 } else {
979 sel = view->selections;
982 for (; sel; sel = sel->next) {
983 size_t pos = view_cursors_pos(sel);
984 size_t line = text_lineno_by_pos(txt, pos);
985 if (line != line_cur) {
986 line_cur = line;
987 column_cur = 0;
988 } else {
989 column_cur++;
991 if (column == column_cur)
992 return sel;
994 return NULL;
997 Selection *view_selections_column(View *view, int column) {
998 return selections_column_next(view, NULL, column);
1001 Selection *view_selections_column_next(Selection *sel, int column) {
1002 return selections_column_next(sel->view, sel, column);
1005 static void selection_free(Selection *s) {
1006 if (!s)
1007 return;
1008 for (Selection *after = s->next; after; after = after->next)
1009 after->number--;
1010 if (s->prev)
1011 s->prev->next = s->next;
1012 if (s->next)
1013 s->next->prev = s->prev;
1014 if (s->view->selections == s)
1015 s->view->selections = s->next;
1016 if (s->view->selection == s)
1017 s->view->selection = s->next ? s->next : s->prev;
1018 if (s->view->selection_dead == s)
1019 s->view->selection_dead = NULL;
1020 if (s->view->selection_latest == s)
1021 s->view->selection_latest = s->prev ? s->prev : s->next;
1022 s->view->selection_count--;
1023 free(s);
1026 bool view_selections_dispose(Selection *sel) {
1027 if (!sel)
1028 return true;
1029 View *view = sel->view;
1030 if (!view->selections || !view->selections->next)
1031 return false;
1032 selection_free(sel);
1033 view_selections_primary_set(view->selection);
1034 return true;
1037 bool view_selections_dispose_force(Selection *sel) {
1038 if (view_selections_dispose(sel))
1039 return true;
1040 View *view = sel->view;
1041 if (view->selection_dead)
1042 return false;
1043 view_selection_clear(sel);
1044 view->selection_dead = sel;
1045 return true;
1048 Selection *view_selection_disposed(View *view) {
1049 Selection *sel = view->selection_dead;
1050 view->selection_dead = NULL;
1051 return sel;
1054 Selection *view_selections(View *view) {
1055 view->selection_generation++;
1056 return view->selections;
1059 Selection *view_selections_primary_get(View *view) {
1060 view->selection_generation++;
1061 return view->selection;
1064 void view_selections_primary_set(Selection *s) {
1065 if (!s)
1066 return;
1067 s->view->selection = s;
1068 Mark anchor = s->anchor;
1069 view_cursors_to(s, view_cursors_pos(s));
1070 s->anchor = anchor;
1073 Selection *view_selections_prev(Selection *s) {
1074 View *view = s->view;
1075 for (s = s->prev; s; s = s->prev) {
1076 if (s->generation != view->selection_generation)
1077 return s;
1079 view->selection_generation++;
1080 return NULL;
1083 Selection *view_selections_next(Selection *s) {
1084 View *view = s->view;
1085 for (s = s->next; s; s = s->next) {
1086 if (s->generation != view->selection_generation)
1087 return s;
1089 view->selection_generation++;
1090 return NULL;
1093 size_t view_cursors_pos(Selection *s) {
1094 return text_mark_get(s->view->text, s->cursor);
1097 size_t view_cursors_line(Selection *s) {
1098 size_t pos = view_cursors_pos(s);
1099 return text_lineno_by_pos(s->view->text, pos);
1102 size_t view_cursors_col(Selection *s) {
1103 size_t pos = view_cursors_pos(s);
1104 return text_line_char_get(s->view->text, pos) + 1;
1107 int view_cursors_cell_get(Selection *s) {
1108 return s->line ? s->col : -1;
1111 int view_cursors_cell_set(Selection *s, int cell) {
1112 if (!s->line || cell < 0)
1113 return -1;
1114 cursor_set(s, s->line, cell);
1115 return s->col;
1118 void view_cursors_scroll_to(Selection *s, size_t pos) {
1119 View *view = s->view;
1120 if (view->selection == s) {
1121 view_draw(view);
1122 while (pos < view->start && view_viewport_up(view, 1));
1123 while (pos > view->end && view_viewport_down(view, 1));
1125 view_cursors_to(s, pos);
1128 void view_cursors_to(Selection *s, size_t pos) {
1129 View *view = s->view;
1130 if (pos == EPOS)
1131 return;
1132 size_t size = text_size(view->text);
1133 if (pos > size)
1134 pos = size;
1135 if (s->view->selection == s) {
1136 /* make sure we redraw changes to the very first character of the window */
1137 if (view->start == pos)
1138 view->start_last = 0;
1140 if (view->end == pos && view->lastline == view->bottomline) {
1141 view->start += view->topline->len;
1142 view_draw(view);
1145 if (pos < view->start || pos > view->end) {
1146 view->start = pos;
1147 view_viewport_up(view, view->height / 2);
1150 if (pos <= view->start || pos > view->end) {
1151 view->start = text_line_begin(view->text, pos);
1152 view_draw(view);
1155 if (pos <= view->start || pos > view->end) {
1156 view->start = pos;
1157 view_draw(view);
1161 cursor_to(s, pos);
1164 void view_cursors_place(Selection *s, size_t line, size_t col) {
1165 Text *txt = s->view->text;
1166 size_t pos = text_pos_by_lineno(txt, line);
1167 pos = text_line_char_set(txt, pos, col > 0 ? col-1 : col);
1168 view_cursors_to(s, pos);
1171 void view_selections_anchor(Selection *s, bool anchored) {
1172 s->anchored = anchored;
1175 void view_selection_clear(Selection *s) {
1176 s->anchored = false;
1177 s->anchor = s->cursor;
1178 s->view->need_update = true;
1181 void view_selections_flip(Selection *s) {
1182 Mark temp = s->anchor;
1183 s->anchor = s->cursor;
1184 s->cursor = temp;
1185 view_cursors_to(s, text_mark_get(s->view->text, s->cursor));
1188 bool view_selections_anchored(Selection *s) {
1189 return s->anchored;
1192 void view_selections_clear_all(View *view) {
1193 for (Selection *s = view->selections; s; s = s->next)
1194 view_selection_clear(s);
1195 view_draw(view);
1198 void view_selections_dispose_all(View *view) {
1199 Selection *last = view->selections;
1200 while (last->next)
1201 last = last->next;
1202 for (Selection *s = last, *prev; s; s = prev) {
1203 prev = s->prev;
1204 if (s != view->selection)
1205 selection_free(s);
1207 view_draw(view);
1210 Filerange view_selection_get(View *view) {
1211 return view_selections_get(view->selection);
1214 Filerange view_selections_get(Selection *s) {
1215 if (!s)
1216 return text_range_empty();
1217 Text *txt = s->view->text;
1218 size_t anchor = text_mark_get(txt, s->anchor);
1219 size_t cursor = text_mark_get(txt, s->cursor);
1220 Filerange sel = text_range_new(anchor, cursor);
1221 if (text_range_valid(&sel))
1222 sel.end = text_char_next(txt, sel.end);
1223 return sel;
1226 bool view_selections_set(Selection *s, const Filerange *r) {
1227 Text *txt = s->view->text;
1228 size_t max = text_size(txt);
1229 if (!text_range_valid(r) || r->start >= max)
1230 return false;
1231 size_t anchor = text_mark_get(txt, s->anchor);
1232 size_t cursor = text_mark_get(txt, s->cursor);
1233 bool left_extending = anchor != EPOS && anchor > cursor;
1234 size_t end = r->end > max ? max : r->end;
1235 if (r->start != end)
1236 end = text_char_prev(txt, end);
1237 view_cursors_to(s, left_extending ? r->start : end);
1238 s->anchor = text_mark_set(txt, left_extending ? end : r->start);
1239 return true;
1242 Filerange view_regions_restore(View *view, SelectionRegion *s) {
1243 Text *txt = view->text;
1244 size_t anchor = text_mark_get(txt, s->anchor);
1245 size_t cursor = text_mark_get(txt, s->cursor);
1246 Filerange sel = text_range_new(anchor, cursor);
1247 if (text_range_valid(&sel))
1248 sel.end = text_char_next(txt, sel.end);
1249 return sel;
1252 bool view_regions_save(View *view, Filerange *r, SelectionRegion *s) {
1253 Text *txt = view->text;
1254 size_t max = text_size(txt);
1255 if (!text_range_valid(r) || r->start >= max)
1256 return false;
1257 size_t end = r->end > max ? max : r->end;
1258 if (r->start != end)
1259 end = text_char_prev(txt, end);
1260 s->anchor = text_mark_set(txt, r->start);
1261 s->cursor = text_mark_set(txt, end);
1262 return true;
1265 void view_selections_set_all(View *view, Array *arr, bool anchored) {
1266 Selection *s;
1267 Filerange *r;
1268 size_t i = 0;
1269 for (s = view->selections; s; s = s->next) {
1270 if (!(r = array_get(arr, i++)) || !view_selections_set(s, r)) {
1271 for (Selection *next; s; s = next) {
1272 next = view_selections_next(s);
1273 if (i == 1 && s == view->selection)
1274 view_selection_clear(s);
1275 else
1276 view_selections_dispose(s);
1278 break;
1280 s->anchored = anchored;
1282 while ((r = array_get(arr, i++))) {
1283 s = view_selections_new_force(view, r->start);
1284 if (!s || !view_selections_set(s, r))
1285 break;
1286 s->anchored = anchored;
1288 view_selections_primary_set(view->selections);
1291 Array view_selections_get_all(View *view) {
1292 Array arr;
1293 array_init_sized(&arr, sizeof(Filerange));
1294 if (!array_reserve(&arr, view_selections_count(view)))
1295 return arr;
1296 for (Selection *s = view->selections; s; s = s->next) {
1297 Filerange r = view_selections_get(s);
1298 if (text_range_valid(&r))
1299 array_add(&arr, &r);
1301 return arr;
1304 void view_selections_normalize(View *view) {
1305 Selection *prev = NULL;
1306 Filerange range_prev = text_range_empty();
1307 for (Selection *s = view->selections, *next; s; s = next) {
1308 next = s->next;
1309 Filerange range = view_selections_get(s);
1310 if (!text_range_valid(&range)) {
1311 view_selections_dispose(s);
1312 } else if (prev && text_range_overlap(&range_prev, &range)) {
1313 range_prev = text_range_union(&range_prev, &range);
1314 view_selections_dispose(s);
1315 } else {
1316 if (prev)
1317 view_selections_set(prev, &range_prev);
1318 range_prev = range;
1319 prev = s;
1322 if (prev)
1323 view_selections_set(prev, &range_prev);
1326 Text *view_text(View *view) {
1327 return view->text;
1330 char *view_symbol_eof_get(View *view) {
1331 return view->symbols[SYNTAX_SYMBOL_EOF]->symbol;
1334 bool view_style_define(View *view, enum UiStyle id, const char *style) {
1335 return view->ui->style_define(view->ui, id, style);
1338 void view_style(View *view, enum UiStyle style_id, size_t start, size_t end) {
1339 if (end < view->start || start > view->end)
1340 return;
1342 CellStyle style = view->ui->style_get(view->ui, style_id);
1343 size_t pos = view->start;
1344 Line *line = view->topline;
1346 /* skip lines before range to be styled */
1347 while (line && pos + line->len <= start) {
1348 pos += line->len;
1349 line = line->next;
1352 if (!line)
1353 return;
1355 int col = 0, width = view->width;
1357 /* skip columns before range to be styled */
1358 while (pos < start && col < width)
1359 pos += line->cells[col++].len;
1361 do {
1362 while (pos <= end && col < width) {
1363 pos += line->cells[col].len;
1364 line->cells[col++].style = style;
1366 col = 0;
1367 } while (pos <= end && (line = line->next));