vis: make sure zb redraws line at the bottom if possible
[vis.git] / view.c
blob1bffb4f92da4d626b7fdebd6175543802d904022
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 UiWin *ui; /* corresponding ui window */
59 Cell cell_blank; /* used for empty/blank cells */
60 int width, height; /* size of display area */
61 size_t start, end; /* currently displayed area [start, end] in bytes from the start of the file */
62 size_t start_last; /* previously used start of visible area, used to update the mark */
63 Mark start_mark; /* mark to keep track of the start of the visible area */
64 size_t lines_size; /* number of allocated bytes for lines (grows only) */
65 Line *lines; /* view->height number of lines representing view content */
66 Line *topline; /* top of the view, first line currently shown */
67 Line *lastline; /* last currently used line, always <= bottomline */
68 Line *bottomline; /* bottom of view, might be unused if lastline < bottomline */
69 Selection *selection; /* primary selection, always placed within the visible viewport */
70 Selection *selection_latest; /* most recently created cursor */
71 Selection *selection_dead; /* primary cursor which was disposed, will be removed when another cursor is created */
72 int selection_count; /* how many cursors do currently exist */
73 Line *line; /* used while drawing view content, line where next char will be drawn */
74 int col; /* used while drawing view content, column where next char will be drawn */
75 const SyntaxSymbol *symbols[SYNTAX_SYMBOL_LAST]; /* symbols to use for white spaces etc */
76 int tabwidth; /* how many spaces should be used to display a tab character */
77 Selection *selections; /* all cursors currently active */
78 int selection_generation; /* used to filter out newly created cursors during iteration */
79 bool need_update; /* whether view has been redrawn */
80 bool large_file; /* optimize for displaying large files */
81 int colorcolumn;
84 static const SyntaxSymbol symbols_none[] = {
85 [SYNTAX_SYMBOL_SPACE] = { " " },
86 [SYNTAX_SYMBOL_TAB] = { " " },
87 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
88 [SYNTAX_SYMBOL_EOL] = { " " },
89 [SYNTAX_SYMBOL_EOF] = { " " },
92 static const SyntaxSymbol symbols_default[] = {
93 [SYNTAX_SYMBOL_SPACE] = { "·" /* Middle Dot U+00B7 */ },
94 [SYNTAX_SYMBOL_TAB] = { "›" /* Single Right-Pointing Angle Quotation Mark U+203A */ },
95 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
96 [SYNTAX_SYMBOL_EOL] = { "↵" /* Downwards Arrow with Corner Leftwards U+21B5 */ },
97 [SYNTAX_SYMBOL_EOF] = { "~" },
100 static Cell cell_unused;
103 /* move visible viewport n-lines up/down, redraws the view but does not change
104 * cursor position which becomes invalid and should be corrected by calling
105 * view_cursor_to. the return value indicates wether the visible area changed.
107 static bool view_viewport_up(View *view, int n);
108 static bool view_viewport_down(View *view, int n);
110 static void view_clear(View *view);
111 static bool view_addch(View *view, Cell *cell);
112 static void selection_free(Selection*);
113 /* set/move current cursor position to a given (line, column) pair */
114 static size_t cursor_set(Selection*, Line *line, int col);
116 void view_tabwidth_set(View *view, int tabwidth) {
117 view->tabwidth = tabwidth;
118 view_draw(view);
121 /* reset internal view data structures (cell matrix, line offsets etc.) */
122 static void view_clear(View *view) {
123 memset(view->lines, 0, view->lines_size);
124 if (view->start != view->start_last) {
125 if (view->start == 0)
126 view->start_mark = EMARK;
127 else
128 view->start_mark = text_mark_set(view->text, view->start);
129 } else {
130 size_t start;
131 if (view->start_mark == EMARK)
132 start = 0;
133 else
134 start = text_mark_get(view->text, view->start_mark);
135 if (start != EPOS)
136 view->start = start;
139 view->start_last = view->start;
140 view->topline = view->lines;
141 view->topline->lineno = view->large_file ? 1 : text_lineno_by_pos(view->text, view->start);
142 view->lastline = view->topline;
144 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
145 size_t end = view->height * line_size;
146 Line *prev = NULL;
147 for (size_t i = 0; i < end; i += line_size) {
148 Line *line = (Line*)(((char*)view->lines) + i);
149 line->prev = prev;
150 if (prev)
151 prev->next = line;
152 prev = line;
154 view->bottomline = prev ? prev : view->topline;
155 view->bottomline->next = NULL;
156 view->line = view->topline;
157 view->col = 0;
158 if (view->ui)
159 view->cell_blank.style = view->ui->style_get(view->ui, UI_STYLE_DEFAULT);
162 Filerange view_viewport_get(View *view) {
163 return (Filerange){ .start = view->start, .end = view->end };
166 /* try to add another character to the view, return whether there was space left */
167 static bool view_addch(View *view, Cell *cell) {
168 if (!view->line)
169 return false;
171 int width;
172 size_t lineno = view->line->lineno;
173 unsigned char ch = (unsigned char)cell->data[0];
174 cell->style = view->cell_blank.style;
176 switch (ch) {
177 case '\t':
178 cell->width = 1;
179 width = view->tabwidth - (view->col % view->tabwidth);
180 for (int w = 0; w < width; w++) {
181 if (view->col + 1 > view->width) {
182 view->line = view->line->next;
183 view->col = 0;
184 if (!view->line)
185 return false;
186 view->line->lineno = lineno;
189 cell->len = w == 0 ? 1 : 0;
190 int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
191 strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
192 view->line->cells[view->col] = *cell;
193 view->line->len += cell->len;
194 view->line->width += cell->width;
195 view->col++;
197 cell->len = 1;
198 return true;
199 case '\n':
200 cell->width = 1;
201 if (view->col + cell->width > view->width) {
202 view->line = view->line->next;
203 view->col = 0;
204 if (!view->line)
205 return false;
206 view->line->lineno = lineno;
209 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
211 view->line->cells[view->col] = *cell;
212 view->line->len += cell->len;
213 view->line->width += cell->width;
214 for (int i = view->col + 1; i < view->width; i++)
215 view->line->cells[i] = view->cell_blank;
217 view->line = view->line->next;
218 if (view->line)
219 view->line->lineno = lineno + 1;
220 view->col = 0;
221 return true;
222 default:
223 if (ch < 128 && !isprint(ch)) {
224 /* non-printable ascii char, represent it as ^(char + 64) */
225 *cell = (Cell) {
226 .data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
227 .len = 1,
228 .width = 2,
229 .style = cell->style,
233 if (ch == ' ') {
234 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
238 if (view->col + cell->width > view->width) {
239 for (int i = view->col; i < view->width; i++)
240 view->line->cells[i] = view->cell_blank;
241 view->line = view->line->next;
242 view->col = 0;
245 if (view->line) {
246 view->line->width += cell->width;
247 view->line->len += cell->len;
248 view->line->lineno = lineno;
249 view->line->cells[view->col] = *cell;
250 view->col++;
251 /* set cells of a character which uses multiple columns */
252 for (int i = 1; i < cell->width; i++)
253 view->line->cells[view->col++] = cell_unused;
254 return true;
256 return false;
260 static void cursor_to(Selection *s, size_t pos) {
261 Text *txt = s->view->text;
262 s->cursor = text_mark_set(txt, pos);
263 if (!s->anchored)
264 s->anchor = s->cursor;
265 if (pos != s->pos)
266 s->lastcol = 0;
267 s->pos = pos;
268 if (!view_coord_get(s->view, pos, &s->line, &s->row, &s->col)) {
269 if (s->view->selection == s) {
270 s->line = s->view->topline;
271 s->row = 0;
272 s->col = 0;
274 return;
276 // TODO: minimize number of redraws
277 view_draw(s->view);
280 bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
281 int row = 0, col = 0;
282 size_t cur = view->start;
283 Line *line = view->topline;
285 if (pos < view->start || pos > view->end) {
286 if (retline) *retline = NULL;
287 if (retrow) *retrow = -1;
288 if (retcol) *retcol = -1;
289 return false;
292 while (line && line != view->lastline && cur < pos) {
293 if (cur + line->len > pos)
294 break;
295 cur += line->len;
296 line = line->next;
297 row++;
300 if (line) {
301 int max_col = MIN(view->width, line->width);
302 while (cur < pos && col < max_col) {
303 cur += line->cells[col].len;
304 /* skip over columns occupied by the same character */
305 while (++col < max_col && line->cells[col].len == 0);
307 } else {
308 line = view->bottomline;
309 row = view->height - 1;
312 if (retline) *retline = line;
313 if (retrow) *retrow = row;
314 if (retcol) *retcol = col;
315 return true;
318 /* move the cursor to the character at pos bytes from the begining of the file.
319 * if pos is not in the current viewport, redraw the view to make it visible */
320 void view_cursor_to(View *view, size_t pos) {
321 view_cursors_to(view->selection, pos);
324 /* redraw the complete with data starting from view->start bytes into the file.
325 * stop once the screen is full, update view->end, view->lastline */
326 void view_draw(View *view) {
327 view_clear(view);
328 /* read a screenful of text considering each character as 4-byte UTF character*/
329 const size_t size = view->width * view->height * 4;
330 /* current buffer to work with */
331 char text[size+1];
332 /* remaining bytes to process in buffer */
333 size_t rem = text_bytes_get(view->text, view->start, size, text);
334 /* NUL terminate text section */
335 text[rem] = '\0';
336 /* absolute position of character currently being added to display */
337 size_t pos = view->start;
338 /* current position into buffer from which to interpret a character */
339 char *cur = text;
340 /* start from known multibyte state */
341 mbstate_t mbstate = { 0 };
343 Cell cell = { .data = "", .len = 0, .width = 0, }, prev_cell = cell;
345 while (rem > 0) {
347 /* current 'parsed' character' */
348 wchar_t wchar;
350 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
351 if (len == (size_t)-1 && errno == EILSEQ) {
352 /* ok, we encountered an invalid multibyte sequence,
353 * replace it with the Unicode Replacement Character
354 * (FFFD) and skip until the start of the next utf8 char */
355 mbstate = (mbstate_t){0};
356 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
357 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
358 } else if (len == (size_t)-2) {
359 /* not enough bytes available to convert to a
360 * wide character. advance file position and read
361 * another junk into buffer.
363 rem = text_bytes_get(view->text, pos+prev_cell.len, size, text);
364 text[rem] = '\0';
365 cur = text;
366 continue;
367 } else if (len == 0) {
368 /* NUL byte encountered, store it and continue */
369 cell = (Cell){ .data = "\x00", .len = 1, .width = 2 };
370 } else {
371 if (len >= sizeof(cell.data))
372 len = sizeof(cell.data)-1;
373 for (size_t i = 0; i < len; i++)
374 cell.data[i] = cur[i];
375 cell.data[len] = '\0';
376 cell.len = len;
377 cell.width = wcwidth(wchar);
378 if (cell.width == -1)
379 cell.width = 1;
382 if (cell.width == 0) {
383 strncat(prev_cell.data, cell.data, sizeof(prev_cell.data)-strlen(prev_cell.data)-1);
384 prev_cell.len += cell.len;
385 } else {
386 if (prev_cell.len && !view_addch(view, &prev_cell))
387 break;
388 pos += prev_cell.len;
389 prev_cell = cell;
392 rem -= cell.len;
393 cur += cell.len;
395 memset(&cell, 0, sizeof cell);
398 if (prev_cell.len && view_addch(view, &prev_cell))
399 pos += prev_cell.len;
401 /* set end of viewing region */
402 view->end = pos;
403 if (view->line) {
404 bool eof = view->end == text_size(view->text);
405 if (view->line->len == 0 && eof && view->line->prev)
406 view->lastline = view->line->prev;
407 else
408 view->lastline = view->line;
409 } else {
410 view->lastline = view->bottomline;
413 /* clear remaining of line, important to show cursor at end of file */
414 if (view->line) {
415 for (int x = view->col; x < view->width; x++)
416 view->line->cells[x] = view->cell_blank;
419 /* resync position of cursors within visible area */
420 for (Selection *s = view->selections; s; s = s->next) {
421 size_t pos = view_cursors_pos(s);
422 if (!view_coord_get(view, pos, &s->line, &s->row, &s->col) &&
423 s == view->selection) {
424 s->line = view->topline;
425 s->row = 0;
426 s->col = 0;
430 view->need_update = true;
433 void view_invalidate(View *view) {
434 view->need_update = true;
437 bool view_update(View *view) {
438 if (!view->need_update)
439 return false;
440 for (Line *l = view->lastline->next; l; l = l->next) {
441 for (int x = 0; x < view->width; x++)
442 l->cells[x] = view->cell_blank;
444 view->need_update = false;
445 return true;
448 bool view_resize(View *view, int width, int height) {
449 if (width <= 0)
450 width = 1;
451 if (height <= 0)
452 height = 1;
453 if (view->width == width && view->height == height) {
454 view->need_update = true;
455 return true;
457 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
458 if (lines_size > view->lines_size) {
459 Line *lines = realloc(view->lines, lines_size);
460 if (!lines)
461 return false;
462 view->lines = lines;
463 view->lines_size = lines_size;
465 view->width = width;
466 view->height = height;
467 memset(view->lines, 0, view->lines_size);
468 view_draw(view);
469 return true;
472 int view_height_get(View *view) {
473 return view->height;
476 int view_width_get(View *view) {
477 return view->width;
480 void view_free(View *view) {
481 if (!view)
482 return;
483 while (view->selections)
484 selection_free(view->selections);
485 free(view->lines);
486 free(view);
489 void view_reload(View *view, Text *text) {
490 view->text = text;
491 view_selections_clear_all(view);
492 view_cursor_to(view, 0);
495 View *view_new(Text *text) {
496 if (!text)
497 return NULL;
498 View *view = calloc(1, sizeof(View));
499 if (!view)
500 return NULL;
501 view->text = text;
502 if (!view_selections_new(view, 0)) {
503 view_free(view);
504 return NULL;
507 view->cell_blank = (Cell) {
508 .width = 0,
509 .len = 0,
510 .data = " ",
512 view->tabwidth = 8;
513 view_options_set(view, 0);
515 if (!view_resize(view, 1, 1)) {
516 view_free(view);
517 return NULL;
520 view_cursor_to(view, 0);
522 return view;
525 void view_ui(View *view, UiWin* ui) {
526 view->ui = ui;
529 static size_t cursor_set(Selection *sel, Line *line, int col) {
530 int row = 0;
531 View *view = sel->view;
532 size_t pos = view->start;
533 /* get row number and file offset at start of the given line */
534 for (Line *l = view->topline; l && l != line; l = l->next) {
535 pos += l->len;
536 row++;
539 /* for characters which use more than 1 column, make sure we are on the left most */
540 while (col > 0 && line->cells[col].len == 0)
541 col--;
542 /* calculate offset within the line */
543 for (int i = 0; i < col; i++)
544 pos += line->cells[i].len;
546 sel->col = col;
547 sel->row = row;
548 sel->line = line;
550 cursor_to(sel, pos);
552 return pos;
555 static bool view_viewport_down(View *view, int n) {
556 Line *line;
557 if (view->end >= text_size(view->text))
558 return false;
559 if (n >= view->height) {
560 view->start = view->end;
561 } else {
562 for (line = view->topline; line && n > 0; line = line->next, n--)
563 view->start += line->len;
565 view_draw(view);
566 return true;
569 static bool view_viewport_up(View *view, int n) {
570 /* scrolling up is somewhat tricky because we do not yet know where
571 * the lines start, therefore scan backwards but stop at a reasonable
572 * maximum in case we are dealing with a file without any newlines
574 if (view->start == 0)
575 return false;
576 size_t max = view->width * view->height;
577 char c;
578 Iterator it = text_iterator_get(view->text, view->start - 1);
580 if (!text_iterator_byte_get(&it, &c))
581 return false;
582 size_t off = 0;
583 /* skip newlines immediately before display area */
584 if (c == '\n' && text_iterator_byte_prev(&it, &c))
585 off++;
586 do {
587 if (c == '\n' && --n == 0)
588 break;
589 if (++off > max)
590 break;
591 } while (text_iterator_byte_prev(&it, &c));
592 view->start -= MIN(view->start, off);
593 view_draw(view);
594 return true;
597 void view_redraw_top(View *view) {
598 Line *line = view->selection->line;
599 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
600 view->start += cur->len;
601 view_draw(view);
602 view_cursor_to(view, view->selection->pos);
605 void view_redraw_center(View *view) {
606 int center = view->height / 2;
607 size_t pos = view->selection->pos;
608 for (int i = 0; i < 2; i++) {
609 int linenr = 0;
610 Line *line = view->selection->line;
611 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
612 linenr++;
613 if (linenr < center) {
614 view_slide_down(view, center - linenr);
615 continue;
617 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
618 view->start += cur->len;
619 linenr--;
621 break;
623 view_draw(view);
624 view_cursor_to(view, pos);
627 void view_redraw_bottom(View *view) {
628 size_t pos = view->selection->pos;
629 view_viewport_up(view, view->height);
630 while (pos >= view->end && view_viewport_down(view, 1));
631 cursor_to(view->selection, pos);
634 size_t view_slide_up(View *view, int lines) {
635 Selection *sel = view->selection;
636 if (view_viewport_down(view, lines)) {
637 if (sel->line == view->topline)
638 cursor_set(sel, view->topline, sel->col);
639 else
640 view_cursor_to(view, sel->pos);
641 } else {
642 view_screenline_down(sel);
644 return sel->pos;
647 size_t view_slide_down(View *view, int lines) {
648 Selection *sel = view->selection;
649 bool lastline = sel->line == view->lastline;
650 size_t col = sel->col;
651 if (view_viewport_up(view, lines)) {
652 if (lastline)
653 cursor_set(sel, view->lastline, col);
654 else
655 view_cursor_to(view, sel->pos);
656 } else {
657 view_screenline_up(sel);
659 return sel->pos;
662 size_t view_scroll_up(View *view, int lines) {
663 Selection *sel = view->selection;
664 if (view_viewport_up(view, lines)) {
665 Line *line = sel->line < view->lastline ? sel->line : view->lastline;
666 cursor_set(sel, line, view->selection->col);
667 } else {
668 view_cursor_to(view, 0);
670 return sel->pos;
673 size_t view_scroll_page_up(View *view) {
674 Selection *sel = view->selection;
675 if (view->start == 0) {
676 view_cursor_to(view, 0);
677 } else {
678 view_cursor_to(view, view->start-1);
679 view_redraw_bottom(view);
680 view_screenline_begin(sel);
682 return sel->pos;
685 size_t view_scroll_page_down(View *view) {
686 view_scroll_down(view, view->height);
687 return view_screenline_begin(view->selection);
690 size_t view_scroll_halfpage_up(View *view) {
691 Selection *sel = view->selection;
692 if (view->start == 0) {
693 view_cursor_to(view, 0);
694 } else {
695 view_cursor_to(view, view->start-1);
696 view_redraw_center(view);
697 view_screenline_begin(sel);
699 return sel->pos;
702 size_t view_scroll_halfpage_down(View *view) {
703 size_t end = view->end;
704 size_t pos = view_scroll_down(view, view->height/2);
705 if (pos < text_size(view->text))
706 view_cursor_to(view, end);
707 return view->selection->pos;
710 size_t view_scroll_down(View *view, int lines) {
711 Selection *sel = view->selection;
712 if (view_viewport_down(view, lines)) {
713 Line *line = sel->line > view->topline ? sel->line : view->topline;
714 cursor_set(sel, line, sel->col);
715 } else {
716 view_cursor_to(view, text_size(view->text));
718 return sel->pos;
721 size_t view_line_up(Selection *sel) {
722 View *view = sel->view;
723 int lastcol = sel->lastcol;
724 if (!lastcol)
725 lastcol = sel->col;
726 size_t pos = text_line_up(sel->view->text, sel->pos);
727 bool offscreen = view->selection == sel && pos < view->start;
728 view_cursors_to(sel, pos);
729 if (offscreen)
730 view_redraw_top(view);
731 if (sel->line)
732 cursor_set(sel, sel->line, lastcol);
733 sel->lastcol = lastcol;
734 return sel->pos;
737 size_t view_line_down(Selection *sel) {
738 View *view = sel->view;
739 int lastcol = sel->lastcol;
740 if (!lastcol)
741 lastcol = sel->col;
742 size_t pos = text_line_down(sel->view->text, sel->pos);
743 bool offscreen = view->selection == sel && pos > view->end;
744 view_cursors_to(sel, pos);
745 if (offscreen)
746 view_redraw_bottom(view);
747 if (sel->line)
748 cursor_set(sel, sel->line, lastcol);
749 sel->lastcol = lastcol;
750 return sel->pos;
753 size_t view_screenline_up(Selection *sel) {
754 if (!sel->line)
755 return view_line_up(sel);
756 int lastcol = sel->lastcol;
757 if (!lastcol)
758 lastcol = sel->col;
759 if (!sel->line->prev)
760 view_scroll_up(sel->view, 1);
761 if (sel->line->prev)
762 cursor_set(sel, sel->line->prev, lastcol);
763 sel->lastcol = lastcol;
764 return sel->pos;
767 size_t view_screenline_down(Selection *sel) {
768 if (!sel->line)
769 return view_line_down(sel);
770 int lastcol = sel->lastcol;
771 if (!lastcol)
772 lastcol = sel->col;
773 if (!sel->line->next && sel->line == sel->view->bottomline)
774 view_scroll_down(sel->view, 1);
775 if (sel->line->next)
776 cursor_set(sel, sel->line->next, lastcol);
777 sel->lastcol = lastcol;
778 return sel->pos;
781 size_t view_screenline_begin(Selection *sel) {
782 if (!sel->line)
783 return sel->pos;
784 return cursor_set(sel, sel->line, 0);
787 size_t view_screenline_middle(Selection *sel) {
788 if (!sel->line)
789 return sel->pos;
790 return cursor_set(sel, sel->line, sel->line->width / 2);
793 size_t view_screenline_end(Selection *sel) {
794 if (!sel->line)
795 return sel->pos;
796 int col = sel->line->width - 1;
797 return cursor_set(sel, sel->line, col >= 0 ? col : 0);
800 size_t view_cursor_get(View *view) {
801 return view_cursors_pos(view->selection);
804 Line *view_lines_first(View *view) {
805 return view->topline;
808 Line *view_lines_last(View *view) {
809 return view->lastline;
812 Line *view_cursors_line_get(Selection *sel) {
813 return sel->line;
816 void view_scroll_to(View *view, size_t pos) {
817 view_cursors_scroll_to(view->selection, pos);
820 void view_options_set(View *view, enum UiOption options) {
821 const int mapping[] = {
822 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
823 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
824 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
825 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
826 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
829 for (int i = 0; i < LENGTH(mapping); i++) {
830 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
831 &symbols_none[i];
834 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
835 options &= ~UI_OPTION_LARGE_FILE;
837 view->large_file = (options & UI_OPTION_LARGE_FILE);
839 if (view->ui)
840 view->ui->options_set(view->ui, options);
843 enum UiOption view_options_get(View *view) {
844 return view->ui ? view->ui->options_get(view->ui) : 0;
847 void view_colorcolumn_set(View *view, int col) {
848 if (col >= 0)
849 view->colorcolumn = col;
852 int view_colorcolumn_get(View *view) {
853 return view->colorcolumn;
856 size_t view_screenline_goto(View *view, int n) {
857 size_t pos = view->start;
858 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
859 pos += line->len;
860 return pos;
863 static Selection *selections_new(View *view, size_t pos, bool force) {
864 if (pos > text_size(view->text))
865 return NULL;
866 Selection *s = calloc(1, sizeof(*s));
867 if (!s)
868 return NULL;
869 s->view = view;
870 s->generation = view->selection_generation;
871 if (!view->selections) {
872 view->selection = s;
873 view->selection_latest = s;
874 view->selections = s;
875 view->selection_count = 1;
876 return s;
879 Selection *prev = NULL, *next = NULL;
880 Selection *latest = view->selection_latest ? view->selection_latest : view->selection;
881 size_t cur = view_cursors_pos(latest);
882 if (pos == cur) {
883 prev = latest;
884 next = prev->next;
885 } else if (pos > cur) {
886 prev = latest;
887 for (next = prev->next; next; prev = next, next = next->next) {
888 cur = view_cursors_pos(next);
889 if (pos <= cur)
890 break;
892 } else if (pos < cur) {
893 next = latest;
894 for (prev = next->prev; prev; next = prev, prev = prev->prev) {
895 cur = view_cursors_pos(prev);
896 if (pos >= cur)
897 break;
901 if (pos == cur && !force)
902 goto err;
904 for (Selection *after = next; after; after = after->next)
905 after->number++;
907 s->prev = prev;
908 s->next = next;
909 if (next)
910 next->prev = s;
911 if (prev) {
912 prev->next = s;
913 s->number = prev->number + 1;
914 } else {
915 view->selections = s;
917 view->selection_latest = s;
918 view->selection_count++;
919 view_selections_dispose(view->selection_dead);
920 view_cursors_to(s, pos);
921 return s;
922 err:
923 free(s);
924 return NULL;
927 Selection *view_selections_new(View *view, size_t pos) {
928 return selections_new(view, pos, false);
931 Selection *view_selections_new_force(View *view, size_t pos) {
932 return selections_new(view, pos, true);
935 int view_selections_count(View *view) {
936 return view->selection_count;
939 int view_selections_number(Selection *sel) {
940 return sel->number;
943 int view_selections_column_count(View *view) {
944 Text *txt = view->text;
945 int cpl_max = 0, cpl = 0; /* cursors per line */
946 size_t line_prev = 0;
947 for (Selection *sel = view->selections; sel; sel = sel->next) {
948 size_t pos = view_cursors_pos(sel);
949 size_t line = text_lineno_by_pos(txt, pos);
950 if (line == line_prev)
951 cpl++;
952 else
953 cpl = 1;
954 line_prev = line;
955 if (cpl > cpl_max)
956 cpl_max = cpl;
958 return cpl_max;
961 static Selection *selections_column_next(View *view, Selection *sel, int column) {
962 size_t line_cur = 0;
963 int column_cur = 0;
964 Text *txt = view->text;
965 if (sel) {
966 size_t pos = view_cursors_pos(sel);
967 line_cur = text_lineno_by_pos(txt, pos);
968 column_cur = INT_MIN;
969 } else {
970 sel = view->selections;
973 for (; sel; sel = sel->next) {
974 size_t pos = view_cursors_pos(sel);
975 size_t line = text_lineno_by_pos(txt, pos);
976 if (line != line_cur) {
977 line_cur = line;
978 column_cur = 0;
979 } else {
980 column_cur++;
982 if (column == column_cur)
983 return sel;
985 return NULL;
988 Selection *view_selections_column(View *view, int column) {
989 return selections_column_next(view, NULL, column);
992 Selection *view_selections_column_next(Selection *sel, int column) {
993 return selections_column_next(sel->view, sel, column);
996 static void selection_free(Selection *s) {
997 if (!s)
998 return;
999 for (Selection *after = s->next; after; after = after->next)
1000 after->number--;
1001 if (s->prev)
1002 s->prev->next = s->next;
1003 if (s->next)
1004 s->next->prev = s->prev;
1005 if (s->view->selections == s)
1006 s->view->selections = s->next;
1007 if (s->view->selection == s)
1008 s->view->selection = s->next ? s->next : s->prev;
1009 if (s->view->selection_dead == s)
1010 s->view->selection_dead = NULL;
1011 if (s->view->selection_latest == s)
1012 s->view->selection_latest = s->prev ? s->prev : s->next;
1013 s->view->selection_count--;
1014 free(s);
1017 bool view_selections_dispose(Selection *sel) {
1018 if (!sel)
1019 return true;
1020 View *view = sel->view;
1021 if (!view->selections || !view->selections->next)
1022 return false;
1023 selection_free(sel);
1024 view_selections_primary_set(view->selection);
1025 return true;
1028 bool view_selections_dispose_force(Selection *sel) {
1029 if (view_selections_dispose(sel))
1030 return true;
1031 View *view = sel->view;
1032 if (view->selection_dead)
1033 return false;
1034 view_selection_clear(sel);
1035 view->selection_dead = sel;
1036 return true;
1039 Selection *view_selection_disposed(View *view) {
1040 Selection *sel = view->selection_dead;
1041 view->selection_dead = NULL;
1042 return sel;
1045 Selection *view_selections(View *view) {
1046 view->selection_generation++;
1047 return view->selections;
1050 Selection *view_selections_primary_get(View *view) {
1051 view->selection_generation++;
1052 return view->selection;
1055 void view_selections_primary_set(Selection *s) {
1056 if (!s)
1057 return;
1058 s->view->selection = s;
1059 Mark anchor = s->anchor;
1060 view_cursors_to(s, view_cursors_pos(s));
1061 s->anchor = anchor;
1064 Selection *view_selections_prev(Selection *s) {
1065 View *view = s->view;
1066 for (s = s->prev; s; s = s->prev) {
1067 if (s->generation != view->selection_generation)
1068 return s;
1070 view->selection_generation++;
1071 return NULL;
1074 Selection *view_selections_next(Selection *s) {
1075 View *view = s->view;
1076 for (s = s->next; s; s = s->next) {
1077 if (s->generation != view->selection_generation)
1078 return s;
1080 view->selection_generation++;
1081 return NULL;
1084 size_t view_cursors_pos(Selection *s) {
1085 return text_mark_get(s->view->text, s->cursor);
1088 size_t view_cursors_line(Selection *s) {
1089 size_t pos = view_cursors_pos(s);
1090 return text_lineno_by_pos(s->view->text, pos);
1093 size_t view_cursors_col(Selection *s) {
1094 size_t pos = view_cursors_pos(s);
1095 return text_line_char_get(s->view->text, pos) + 1;
1098 int view_cursors_cell_get(Selection *s) {
1099 return s->line ? s->col : -1;
1102 int view_cursors_cell_set(Selection *s, int cell) {
1103 if (!s->line || cell < 0)
1104 return -1;
1105 cursor_set(s, s->line, cell);
1106 return s->col;
1109 void view_cursors_scroll_to(Selection *s, size_t pos) {
1110 View *view = s->view;
1111 if (view->selection == s) {
1112 view_draw(view);
1113 while (pos < view->start && view_viewport_up(view, 1));
1114 while (pos > view->end && view_viewport_down(view, 1));
1116 view_cursors_to(s, pos);
1119 void view_cursors_to(Selection *s, size_t pos) {
1120 View *view = s->view;
1121 if (pos == EPOS)
1122 return;
1123 size_t size = text_size(view->text);
1124 if (pos > size)
1125 pos = size;
1126 if (s->view->selection == s) {
1127 /* make sure we redraw changes to the very first character of the window */
1128 if (view->start == pos)
1129 view->start_last = 0;
1131 if (view->end == pos && view->lastline == view->bottomline) {
1132 view->start += view->topline->len;
1133 view_draw(view);
1136 if (pos < view->start || pos > view->end) {
1137 view->start = pos;
1138 view_viewport_up(view, view->height / 2);
1141 if (pos <= view->start || pos > view->end) {
1142 view->start = text_line_begin(view->text, pos);
1143 view_draw(view);
1146 if (pos <= view->start || pos > view->end) {
1147 view->start = pos;
1148 view_draw(view);
1152 cursor_to(s, pos);
1155 void view_cursors_place(Selection *s, size_t line, size_t col) {
1156 Text *txt = s->view->text;
1157 size_t pos = text_pos_by_lineno(txt, line);
1158 pos = text_line_char_set(txt, pos, col > 0 ? col-1 : col);
1159 view_cursors_to(s, pos);
1162 void view_selections_anchor(Selection *s, bool anchored) {
1163 s->anchored = anchored;
1166 void view_selection_clear(Selection *s) {
1167 s->anchored = false;
1168 s->anchor = s->cursor;
1169 s->view->need_update = true;
1172 void view_selections_flip(Selection *s) {
1173 Mark temp = s->anchor;
1174 s->anchor = s->cursor;
1175 s->cursor = temp;
1176 view_cursors_to(s, text_mark_get(s->view->text, s->cursor));
1179 bool view_selections_anchored(Selection *s) {
1180 return s->anchored;
1183 void view_selections_clear_all(View *view) {
1184 for (Selection *s = view->selections; s; s = s->next)
1185 view_selection_clear(s);
1186 view_draw(view);
1189 void view_selections_dispose_all(View *view) {
1190 for (Selection *s = view->selections, *next; s; s = next) {
1191 next = s->next;
1192 if (s != view->selection)
1193 selection_free(s);
1195 view_draw(view);
1198 Filerange view_selection_get(View *view) {
1199 return view_selections_get(view->selection);
1202 Filerange view_selections_get(Selection *s) {
1203 if (!s)
1204 return text_range_empty();
1205 Text *txt = s->view->text;
1206 size_t anchor = text_mark_get(txt, s->anchor);
1207 size_t cursor = text_mark_get(txt, s->cursor);
1208 Filerange sel = text_range_new(anchor, cursor);
1209 if (text_range_valid(&sel))
1210 sel.end = text_char_next(txt, sel.end);
1211 return sel;
1214 bool view_selections_set(Selection *s, const Filerange *r) {
1215 Text *txt = s->view->text;
1216 size_t max = text_size(txt);
1217 if (!text_range_valid(r) || r->start >= max)
1218 return false;
1219 size_t anchor = text_mark_get(txt, s->anchor);
1220 size_t cursor = text_mark_get(txt, s->cursor);
1221 bool left_extending = anchor != EPOS && anchor > cursor;
1222 size_t end = r->end > max ? max : r->end;
1223 if (r->start != end)
1224 end = text_char_prev(txt, end);
1225 view_cursors_to(s, left_extending ? r->start : end);
1226 s->anchor = text_mark_set(txt, left_extending ? end : r->start);
1227 return true;
1230 Filerange view_regions_restore(View *view, SelectionRegion *s) {
1231 Text *txt = view->text;
1232 size_t anchor = text_mark_get(txt, s->anchor);
1233 size_t cursor = text_mark_get(txt, s->cursor);
1234 Filerange sel = text_range_new(anchor, cursor);
1235 if (text_range_valid(&sel))
1236 sel.end = text_char_next(txt, sel.end);
1237 return sel;
1240 bool view_regions_save(View *view, Filerange *r, SelectionRegion *s) {
1241 Text *txt = view->text;
1242 size_t max = text_size(txt);
1243 if (!text_range_valid(r) || r->start >= max)
1244 return false;
1245 size_t end = r->end > max ? max : r->end;
1246 if (r->start != end)
1247 end = text_char_prev(txt, end);
1248 s->anchor = text_mark_set(txt, r->start);
1249 s->cursor = text_mark_set(txt, end);
1250 return true;
1253 void view_selections_set_all(View *view, Array *arr, bool anchored) {
1254 Selection *s;
1255 Filerange *r;
1256 size_t i = 0;
1257 for (s = view->selections; s; s = s->next) {
1258 if (!(r = array_get(arr, i++)) || !view_selections_set(s, r)) {
1259 for (Selection *next; s; s = next) {
1260 next = view_selections_next(s);
1261 if (i == 1 && s == view->selection)
1262 view_selection_clear(s);
1263 else
1264 view_selections_dispose(s);
1266 break;
1268 s->anchored = anchored;
1270 while ((r = array_get(arr, i++))) {
1271 s = view_selections_new_force(view, r->start);
1272 if (!s || !view_selections_set(s, r))
1273 break;
1274 s->anchored = anchored;
1276 view_selections_primary_set(view->selections);
1279 Array view_selections_get_all(View *view) {
1280 Array arr;
1281 array_init_sized(&arr, sizeof(Filerange));
1282 if (!array_reserve(&arr, view_selections_count(view)))
1283 return arr;
1284 for (Selection *s = view->selections; s; s = s->next) {
1285 Filerange r = view_selections_get(s);
1286 if (text_range_valid(&r))
1287 array_add(&arr, &r);
1289 return arr;
1292 void view_selections_normalize(View *view) {
1293 Selection *prev = NULL;
1294 Filerange range_prev = text_range_empty();
1295 for (Selection *s = view->selections, *next; s; s = next) {
1296 next = s->next;
1297 Filerange range = view_selections_get(s);
1298 if (!text_range_valid(&range)) {
1299 view_selections_dispose(s);
1300 } else if (prev && text_range_overlap(&range_prev, &range)) {
1301 range_prev = text_range_union(&range_prev, &range);
1302 view_selections_dispose(s);
1303 } else {
1304 if (prev)
1305 view_selections_set(prev, &range_prev);
1306 range_prev = range;
1307 prev = s;
1310 if (prev)
1311 view_selections_set(prev, &range_prev);
1314 Text *view_text(View *view) {
1315 return view->text;
1318 char *view_symbol_eof_get(View *view) {
1319 return view->symbols[SYNTAX_SYMBOL_EOF]->symbol;
1322 bool view_style_define(View *view, enum UiStyle id, const char *style) {
1323 return view->ui->style_define(view->ui, id, style);
1326 void view_style(View *view, enum UiStyle style_id, size_t start, size_t end) {
1327 if (end < view->start || start > view->end)
1328 return;
1330 CellStyle style = view->ui->style_get(view->ui, style_id);
1331 size_t pos = view->start;
1332 Line *line = view->topline;
1334 /* skip lines before range to be styled */
1335 while (line && pos + line->len <= start) {
1336 pos += line->len;
1337 line = line->next;
1340 if (!line)
1341 return;
1343 int col = 0, width = view->width;
1345 /* skip columns before range to be styled */
1346 while (pos < start && col < width)
1347 pos += line->cells[col++].len;
1349 do {
1350 while (pos <= end && col < width) {
1351 pos += line->cells[col].len;
1352 line->cells[col++].style = style;
1354 col = 0;
1355 } while (pos <= end && (line = line->next));