vis: implement normal/outer paragraph text object
[vis.git] / view.c
blobfafabc5a65390eaf45b43ecd742b254abc40a269
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 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
356 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
357 } else if (len == (size_t)-2) {
358 /* not enough bytes available to convert to a
359 * wide character. advance file position and read
360 * another junk into buffer.
362 rem = text_bytes_get(view->text, pos, size, text);
363 text[rem] = '\0';
364 cur = text;
365 continue;
366 } else if (len == 0) {
367 /* NUL byte encountered, store it and continue */
368 cell = (Cell){ .data = "\x00", .len = 1, .width = 2 };
369 } else {
370 if (len >= sizeof(cell.data))
371 len = sizeof(cell.data)-1;
372 for (size_t i = 0; i < len; i++)
373 cell.data[i] = cur[i];
374 cell.data[len] = '\0';
375 cell.len = len;
376 cell.width = wcwidth(wchar);
377 if (cell.width == -1)
378 cell.width = 1;
381 if (cell.width == 0 && prev_cell.len + cell.len < sizeof(cell.data)) {
382 prev_cell.len += cell.len;
383 strcat(prev_cell.data, cell.data);
384 } else {
385 if (prev_cell.len && !view_addch(view, &prev_cell))
386 break;
387 pos += prev_cell.len;
388 prev_cell = cell;
391 rem -= cell.len;
392 cur += cell.len;
394 memset(&cell, 0, sizeof cell);
397 if (prev_cell.len && view_addch(view, &prev_cell))
398 pos += prev_cell.len;
400 /* set end of viewing region */
401 view->end = pos;
402 if (view->line) {
403 bool eof = view->end == text_size(view->text);
404 if (view->line->len == 0 && eof && view->line->prev)
405 view->lastline = view->line->prev;
406 else
407 view->lastline = view->line;
408 } else {
409 view->lastline = view->bottomline;
412 /* clear remaining of line, important to show cursor at end of file */
413 if (view->line) {
414 for (int x = view->col; x < view->width; x++)
415 view->line->cells[x] = view->cell_blank;
418 /* resync position of cursors within visible area */
419 for (Selection *s = view->selections; s; s = s->next) {
420 size_t pos = view_cursors_pos(s);
421 if (!view_coord_get(view, pos, &s->line, &s->row, &s->col) &&
422 s == view->selection) {
423 s->line = view->topline;
424 s->row = 0;
425 s->col = 0;
429 view->need_update = true;
432 void view_invalidate(View *view) {
433 view->need_update = true;
436 bool view_update(View *view) {
437 if (!view->need_update)
438 return false;
439 for (Line *l = view->lastline->next; l; l = l->next) {
440 for (int x = 0; x < view->width; x++)
441 l->cells[x] = view->cell_blank;
443 view->need_update = false;
444 return true;
447 bool view_resize(View *view, int width, int height) {
448 if (width <= 0)
449 width = 1;
450 if (height <= 0)
451 height = 1;
452 if (view->width == width && view->height == height) {
453 view->need_update = true;
454 return true;
456 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
457 if (lines_size > view->lines_size) {
458 Line *lines = realloc(view->lines, lines_size);
459 if (!lines)
460 return false;
461 view->lines = lines;
462 view->lines_size = lines_size;
464 view->width = width;
465 view->height = height;
466 memset(view->lines, 0, view->lines_size);
467 view_draw(view);
468 return true;
471 int view_height_get(View *view) {
472 return view->height;
475 int view_width_get(View *view) {
476 return view->width;
479 void view_free(View *view) {
480 if (!view)
481 return;
482 while (view->selections)
483 selection_free(view->selections);
484 free(view->lines);
485 free(view);
488 void view_reload(View *view, Text *text) {
489 view->text = text;
490 view_selections_clear_all(view);
491 view_cursor_to(view, 0);
494 View *view_new(Text *text) {
495 if (!text)
496 return NULL;
497 View *view = calloc(1, sizeof(View));
498 if (!view)
499 return NULL;
500 view->text = text;
501 if (!view_selections_new(view, 0)) {
502 view_free(view);
503 return NULL;
506 view->cell_blank = (Cell) {
507 .width = 0,
508 .len = 0,
509 .data = " ",
511 view->tabwidth = 8;
512 view_options_set(view, 0);
514 if (!view_resize(view, 1, 1)) {
515 view_free(view);
516 return NULL;
519 view_cursor_to(view, 0);
521 return view;
524 void view_ui(View *view, UiWin* ui) {
525 view->ui = ui;
528 static size_t cursor_set(Selection *sel, Line *line, int col) {
529 int row = 0;
530 View *view = sel->view;
531 size_t pos = view->start;
532 /* get row number and file offset at start of the given line */
533 for (Line *l = view->topline; l && l != line; l = l->next) {
534 pos += l->len;
535 row++;
538 /* for characters which use more than 1 column, make sure we are on the left most */
539 while (col > 0 && line->cells[col].len == 0)
540 col--;
541 /* calculate offset within the line */
542 for (int i = 0; i < col; i++)
543 pos += line->cells[i].len;
545 sel->col = col;
546 sel->row = row;
547 sel->line = line;
549 cursor_to(sel, pos);
551 return pos;
554 static bool view_viewport_down(View *view, int n) {
555 Line *line;
556 if (view->end >= text_size(view->text))
557 return false;
558 if (n >= view->height) {
559 view->start = view->end;
560 } else {
561 for (line = view->topline; line && n > 0; line = line->next, n--)
562 view->start += line->len;
564 view_draw(view);
565 return true;
568 static bool view_viewport_up(View *view, int n) {
569 /* scrolling up is somewhat tricky because we do not yet know where
570 * the lines start, therefore scan backwards but stop at a reasonable
571 * maximum in case we are dealing with a file without any newlines
573 if (view->start == 0)
574 return false;
575 size_t max = view->width * view->height;
576 char c;
577 Iterator it = text_iterator_get(view->text, view->start - 1);
579 if (!text_iterator_byte_get(&it, &c))
580 return false;
581 size_t off = 0;
582 /* skip newlines immediately before display area */
583 if (c == '\n' && text_iterator_byte_prev(&it, &c))
584 off++;
585 do {
586 if (c == '\n' && --n == 0)
587 break;
588 if (++off > max)
589 break;
590 } while (text_iterator_byte_prev(&it, &c));
591 view->start -= MIN(view->start, off);
592 view_draw(view);
593 return true;
596 void view_redraw_top(View *view) {
597 Line *line = view->selection->line;
598 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
599 view->start += cur->len;
600 view_draw(view);
601 view_cursor_to(view, view->selection->pos);
604 void view_redraw_center(View *view) {
605 int center = view->height / 2;
606 size_t pos = view->selection->pos;
607 for (int i = 0; i < 2; i++) {
608 int linenr = 0;
609 Line *line = view->selection->line;
610 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
611 linenr++;
612 if (linenr < center) {
613 view_slide_down(view, center - linenr);
614 continue;
616 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
617 view->start += cur->len;
618 linenr--;
620 break;
622 view_draw(view);
623 view_cursor_to(view, pos);
626 void view_redraw_bottom(View *view) {
627 Line *line = view->selection->line;
628 if (line == view->lastline)
629 return;
630 size_t pos = view->selection->pos;
631 view_viewport_up(view, view->height);
632 while (pos >= view->end && view_viewport_down(view, 1));
633 cursor_to(view->selection, pos);
636 size_t view_slide_up(View *view, int lines) {
637 Selection *sel = view->selection;
638 if (view_viewport_down(view, lines)) {
639 if (sel->line == view->topline)
640 cursor_set(sel, view->topline, sel->col);
641 else
642 view_cursor_to(view, sel->pos);
643 } else {
644 view_screenline_down(sel);
646 return sel->pos;
649 size_t view_slide_down(View *view, int lines) {
650 Selection *sel = view->selection;
651 bool lastline = sel->line == view->lastline;
652 size_t col = sel->col;
653 if (view_viewport_up(view, lines)) {
654 if (lastline)
655 cursor_set(sel, view->lastline, col);
656 else
657 view_cursor_to(view, sel->pos);
658 } else {
659 view_screenline_up(sel);
661 return sel->pos;
664 size_t view_scroll_up(View *view, int lines) {
665 Selection *sel = view->selection;
666 if (view_viewport_up(view, lines)) {
667 Line *line = sel->line < view->lastline ? sel->line : view->lastline;
668 cursor_set(sel, line, view->selection->col);
669 } else {
670 view_cursor_to(view, 0);
672 return sel->pos;
675 size_t view_scroll_page_up(View *view) {
676 Selection *sel = view->selection;
677 if (view->start == 0) {
678 view_cursor_to(view, 0);
679 } else {
680 view_cursor_to(view, view->start-1);
681 view_redraw_bottom(view);
682 view_screenline_begin(sel);
684 return sel->pos;
687 size_t view_scroll_page_down(View *view) {
688 view_scroll_down(view, view->height);
689 return view_screenline_begin(view->selection);
692 size_t view_scroll_halfpage_up(View *view) {
693 Selection *sel = view->selection;
694 if (view->start == 0) {
695 view_cursor_to(view, 0);
696 } else {
697 view_cursor_to(view, view->start-1);
698 view_redraw_center(view);
699 view_screenline_begin(sel);
701 return sel->pos;
704 size_t view_scroll_halfpage_down(View *view) {
705 size_t end = view->end;
706 size_t pos = view_scroll_down(view, view->height/2);
707 if (pos < text_size(view->text))
708 view_cursor_to(view, end);
709 return view->selection->pos;
712 size_t view_scroll_down(View *view, int lines) {
713 Selection *sel = view->selection;
714 if (view_viewport_down(view, lines)) {
715 Line *line = sel->line > view->topline ? sel->line : view->topline;
716 cursor_set(sel, line, sel->col);
717 } else {
718 view_cursor_to(view, text_size(view->text));
720 return sel->pos;
723 size_t view_line_up(Selection *sel) {
724 View *view = sel->view;
725 int lastcol = sel->lastcol;
726 if (!lastcol)
727 lastcol = sel->col;
728 size_t pos = text_line_up(sel->view->text, sel->pos);
729 bool offscreen = view->selection == sel && pos < view->start;
730 view_cursors_to(sel, pos);
731 if (offscreen)
732 view_redraw_top(view);
733 if (sel->line)
734 cursor_set(sel, sel->line, lastcol);
735 sel->lastcol = lastcol;
736 return sel->pos;
739 size_t view_line_down(Selection *sel) {
740 View *view = sel->view;
741 int lastcol = sel->lastcol;
742 if (!lastcol)
743 lastcol = sel->col;
744 size_t pos = text_line_down(sel->view->text, sel->pos);
745 bool offscreen = view->selection == sel && pos > view->end;
746 view_cursors_to(sel, pos);
747 if (offscreen)
748 view_redraw_bottom(view);
749 if (sel->line)
750 cursor_set(sel, sel->line, lastcol);
751 sel->lastcol = lastcol;
752 return sel->pos;
755 size_t view_screenline_up(Selection *sel) {
756 if (!sel->line)
757 return view_line_up(sel);
758 int lastcol = sel->lastcol;
759 if (!lastcol)
760 lastcol = sel->col;
761 if (!sel->line->prev)
762 view_scroll_up(sel->view, 1);
763 if (sel->line->prev)
764 cursor_set(sel, sel->line->prev, lastcol);
765 sel->lastcol = lastcol;
766 return sel->pos;
769 size_t view_screenline_down(Selection *sel) {
770 if (!sel->line)
771 return view_line_down(sel);
772 int lastcol = sel->lastcol;
773 if (!lastcol)
774 lastcol = sel->col;
775 if (!sel->line->next && sel->line == sel->view->bottomline)
776 view_scroll_down(sel->view, 1);
777 if (sel->line->next)
778 cursor_set(sel, sel->line->next, lastcol);
779 sel->lastcol = lastcol;
780 return sel->pos;
783 size_t view_screenline_begin(Selection *sel) {
784 if (!sel->line)
785 return sel->pos;
786 return cursor_set(sel, sel->line, 0);
789 size_t view_screenline_middle(Selection *sel) {
790 if (!sel->line)
791 return sel->pos;
792 return cursor_set(sel, sel->line, sel->line->width / 2);
795 size_t view_screenline_end(Selection *sel) {
796 if (!sel->line)
797 return sel->pos;
798 int col = sel->line->width - 1;
799 return cursor_set(sel, sel->line, col >= 0 ? col : 0);
802 size_t view_cursor_get(View *view) {
803 return view_cursors_pos(view->selection);
806 Line *view_lines_first(View *view) {
807 return view->topline;
810 Line *view_lines_last(View *view) {
811 return view->lastline;
814 Line *view_cursors_line_get(Selection *sel) {
815 return sel->line;
818 void view_scroll_to(View *view, size_t pos) {
819 view_cursors_scroll_to(view->selection, pos);
822 void view_options_set(View *view, enum UiOption options) {
823 const int mapping[] = {
824 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
825 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
826 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
827 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
828 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
831 for (int i = 0; i < LENGTH(mapping); i++) {
832 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
833 &symbols_none[i];
836 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
837 options &= ~UI_OPTION_LARGE_FILE;
839 view->large_file = (options & UI_OPTION_LARGE_FILE);
841 if (view->ui)
842 view->ui->options_set(view->ui, options);
845 enum UiOption view_options_get(View *view) {
846 return view->ui ? view->ui->options_get(view->ui) : 0;
849 void view_colorcolumn_set(View *view, int col) {
850 if (col >= 0)
851 view->colorcolumn = col;
854 int view_colorcolumn_get(View *view) {
855 return view->colorcolumn;
858 size_t view_screenline_goto(View *view, int n) {
859 size_t pos = view->start;
860 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
861 pos += line->len;
862 return pos;
865 static Selection *selections_new(View *view, size_t pos, bool force) {
866 if (pos > text_size(view->text))
867 return NULL;
868 Selection *s = calloc(1, sizeof(*s));
869 if (!s)
870 return NULL;
871 s->view = view;
872 s->generation = view->selection_generation;
873 if (!view->selections) {
874 view->selection = s;
875 view->selection_latest = s;
876 view->selections = s;
877 view->selection_count = 1;
878 return s;
881 Selection *prev = NULL, *next = NULL;
882 Selection *latest = view->selection_latest ? view->selection_latest : view->selection;
883 size_t cur = view_cursors_pos(latest);
884 if (pos == cur) {
885 prev = latest;
886 next = prev->next;
887 } else if (pos > cur) {
888 prev = latest;
889 for (next = prev->next; next; prev = next, next = next->next) {
890 cur = view_cursors_pos(next);
891 if (pos <= cur)
892 break;
894 } else if (pos < cur) {
895 next = latest;
896 for (prev = next->prev; prev; next = prev, prev = prev->prev) {
897 cur = view_cursors_pos(prev);
898 if (pos >= cur)
899 break;
903 if (pos == cur && !force)
904 goto err;
906 for (Selection *after = next; after; after = after->next)
907 after->number++;
909 s->prev = prev;
910 s->next = next;
911 if (next)
912 next->prev = s;
913 if (prev) {
914 prev->next = s;
915 s->number = prev->number + 1;
916 } else {
917 view->selections = s;
919 view->selection_latest = s;
920 view->selection_count++;
921 view_selections_dispose(view->selection_dead);
922 view_cursors_to(s, pos);
923 return s;
924 err:
925 free(s);
926 return NULL;
929 Selection *view_selections_new(View *view, size_t pos) {
930 return selections_new(view, pos, false);
933 Selection *view_selections_new_force(View *view, size_t pos) {
934 return selections_new(view, pos, true);
937 int view_selections_count(View *view) {
938 return view->selection_count;
941 int view_selections_number(Selection *sel) {
942 return sel->number;
945 int view_selections_column_count(View *view) {
946 Text *txt = view->text;
947 int cpl_max = 0, cpl = 0; /* cursors per line */
948 size_t line_prev = 0;
949 for (Selection *sel = view->selections; sel; sel = sel->next) {
950 size_t pos = view_cursors_pos(sel);
951 size_t line = text_lineno_by_pos(txt, pos);
952 if (line == line_prev)
953 cpl++;
954 else
955 cpl = 1;
956 line_prev = line;
957 if (cpl > cpl_max)
958 cpl_max = cpl;
960 return cpl_max;
963 static Selection *selections_column_next(View *view, Selection *sel, int column) {
964 size_t line_cur = 0;
965 int column_cur = 0;
966 Text *txt = view->text;
967 if (sel) {
968 size_t pos = view_cursors_pos(sel);
969 line_cur = text_lineno_by_pos(txt, pos);
970 column_cur = INT_MIN;
971 } else {
972 sel = view->selections;
975 for (; sel; sel = sel->next) {
976 size_t pos = view_cursors_pos(sel);
977 size_t line = text_lineno_by_pos(txt, pos);
978 if (line != line_cur) {
979 line_cur = line;
980 column_cur = 0;
981 } else {
982 column_cur++;
984 if (column == column_cur)
985 return sel;
987 return NULL;
990 Selection *view_selections_column(View *view, int column) {
991 return selections_column_next(view, NULL, column);
994 Selection *view_selections_column_next(Selection *sel, int column) {
995 return selections_column_next(sel->view, sel, column);
998 static void selection_free(Selection *s) {
999 if (!s)
1000 return;
1001 for (Selection *after = s->next; after; after = after->next)
1002 after->number--;
1003 if (s->prev)
1004 s->prev->next = s->next;
1005 if (s->next)
1006 s->next->prev = s->prev;
1007 if (s->view->selections == s)
1008 s->view->selections = s->next;
1009 if (s->view->selection == s)
1010 s->view->selection = s->next ? s->next : s->prev;
1011 if (s->view->selection_dead == s)
1012 s->view->selection_dead = NULL;
1013 if (s->view->selection_latest == s)
1014 s->view->selection_latest = s->prev ? s->prev : s->next;
1015 s->view->selection_count--;
1016 free(s);
1019 bool view_selections_dispose(Selection *sel) {
1020 if (!sel)
1021 return true;
1022 View *view = sel->view;
1023 if (!view->selections || !view->selections->next)
1024 return false;
1025 selection_free(sel);
1026 view_selections_primary_set(view->selection);
1027 return true;
1030 bool view_selections_dispose_force(Selection *sel) {
1031 if (view_selections_dispose(sel))
1032 return true;
1033 View *view = sel->view;
1034 if (view->selection_dead)
1035 return false;
1036 view_selection_clear(sel);
1037 view->selection_dead = sel;
1038 return true;
1041 Selection *view_selection_disposed(View *view) {
1042 Selection *sel = view->selection_dead;
1043 view->selection_dead = NULL;
1044 return sel;
1047 Selection *view_selections(View *view) {
1048 view->selection_generation++;
1049 return view->selections;
1052 Selection *view_selections_primary_get(View *view) {
1053 view->selection_generation++;
1054 return view->selection;
1057 void view_selections_primary_set(Selection *s) {
1058 if (!s)
1059 return;
1060 s->view->selection = s;
1061 Mark anchor = s->anchor;
1062 view_cursors_to(s, view_cursors_pos(s));
1063 s->anchor = anchor;
1066 Selection *view_selections_prev(Selection *s) {
1067 View *view = s->view;
1068 for (s = s->prev; s; s = s->prev) {
1069 if (s->generation != view->selection_generation)
1070 return s;
1072 view->selection_generation++;
1073 return NULL;
1076 Selection *view_selections_next(Selection *s) {
1077 View *view = s->view;
1078 for (s = s->next; s; s = s->next) {
1079 if (s->generation != view->selection_generation)
1080 return s;
1082 view->selection_generation++;
1083 return NULL;
1086 size_t view_cursors_pos(Selection *s) {
1087 return text_mark_get(s->view->text, s->cursor);
1090 size_t view_cursors_line(Selection *s) {
1091 size_t pos = view_cursors_pos(s);
1092 return text_lineno_by_pos(s->view->text, pos);
1095 size_t view_cursors_col(Selection *s) {
1096 size_t pos = view_cursors_pos(s);
1097 return text_line_char_get(s->view->text, pos) + 1;
1100 int view_cursors_cell_get(Selection *s) {
1101 return s->line ? s->col : -1;
1104 int view_cursors_cell_set(Selection *s, int cell) {
1105 if (!s->line || cell < 0)
1106 return -1;
1107 cursor_set(s, s->line, cell);
1108 return s->col;
1111 void view_cursors_scroll_to(Selection *s, size_t pos) {
1112 View *view = s->view;
1113 if (view->selection == s) {
1114 view_draw(view);
1115 while (pos < view->start && view_viewport_up(view, 1));
1116 while (pos > view->end && view_viewport_down(view, 1));
1118 view_cursors_to(s, pos);
1121 void view_cursors_to(Selection *s, size_t pos) {
1122 View *view = s->view;
1123 if (pos == EPOS)
1124 return;
1125 size_t size = text_size(view->text);
1126 if (pos > size)
1127 pos = size;
1128 if (s->view->selection == s) {
1129 /* make sure we redraw changes to the very first character of the window */
1130 if (view->start == pos)
1131 view->start_last = 0;
1133 if (view->end == pos && view->lastline == view->bottomline) {
1134 view->start += view->topline->len;
1135 view_draw(view);
1138 if (pos < view->start || pos > view->end) {
1139 view->start = pos;
1140 view_viewport_up(view, view->height / 2);
1143 if (pos <= view->start || pos > view->end) {
1144 view->start = text_line_begin(view->text, pos);
1145 view_draw(view);
1148 if (pos <= view->start || pos > view->end) {
1149 view->start = pos;
1150 view_draw(view);
1154 cursor_to(s, pos);
1157 void view_cursors_place(Selection *s, size_t line, size_t col) {
1158 Text *txt = s->view->text;
1159 size_t pos = text_pos_by_lineno(txt, line);
1160 pos = text_line_char_set(txt, pos, col > 0 ? col-1 : col);
1161 view_cursors_to(s, pos);
1164 void view_selections_anchor(Selection *s, bool anchored) {
1165 s->anchored = anchored;
1168 void view_selection_clear(Selection *s) {
1169 s->anchored = false;
1170 s->anchor = s->cursor;
1171 s->view->need_update = true;
1174 void view_selections_flip(Selection *s) {
1175 Mark temp = s->anchor;
1176 s->anchor = s->cursor;
1177 s->cursor = temp;
1178 view_cursors_to(s, text_mark_get(s->view->text, s->cursor));
1181 bool view_selections_anchored(Selection *s) {
1182 return s->anchored;
1185 void view_selections_clear_all(View *view) {
1186 for (Selection *s = view->selections; s; s = s->next)
1187 view_selection_clear(s);
1188 view_draw(view);
1191 void view_selections_dispose_all(View *view) {
1192 for (Selection *s = view->selections, *next; s; s = next) {
1193 next = s->next;
1194 if (s != view->selection)
1195 selection_free(s);
1197 view_draw(view);
1200 Filerange view_selection_get(View *view) {
1201 return view_selections_get(view->selection);
1204 Filerange view_selections_get(Selection *s) {
1205 if (!s)
1206 return text_range_empty();
1207 Text *txt = s->view->text;
1208 size_t anchor = text_mark_get(txt, s->anchor);
1209 size_t cursor = text_mark_get(txt, s->cursor);
1210 Filerange sel = text_range_new(anchor, cursor);
1211 if (text_range_valid(&sel))
1212 sel.end = text_char_next(txt, sel.end);
1213 return sel;
1216 bool view_selections_set(Selection *s, const Filerange *r) {
1217 Text *txt = s->view->text;
1218 size_t max = text_size(txt);
1219 if (!text_range_valid(r) || r->start >= max)
1220 return false;
1221 size_t anchor = text_mark_get(txt, s->anchor);
1222 size_t cursor = text_mark_get(txt, s->cursor);
1223 bool left_extending = anchor != EPOS && anchor > cursor;
1224 size_t end = r->end > max ? max : r->end;
1225 if (r->start != end)
1226 end = text_char_prev(txt, end);
1227 view_cursors_to(s, left_extending ? r->start : end);
1228 s->anchor = text_mark_set(txt, left_extending ? end : r->start);
1229 return true;
1232 Filerange view_regions_restore(View *view, SelectionRegion *s) {
1233 Text *txt = view->text;
1234 size_t anchor = text_mark_get(txt, s->anchor);
1235 size_t cursor = text_mark_get(txt, s->cursor);
1236 Filerange sel = text_range_new(anchor, cursor);
1237 if (text_range_valid(&sel))
1238 sel.end = text_char_next(txt, sel.end);
1239 return sel;
1242 bool view_regions_save(View *view, Filerange *r, SelectionRegion *s) {
1243 Text *txt = view->text;
1244 size_t max = text_size(txt);
1245 if (!text_range_valid(r) || r->start >= max)
1246 return false;
1247 size_t end = r->end > max ? max : r->end;
1248 if (r->start != end)
1249 end = text_char_prev(txt, end);
1250 s->anchor = text_mark_set(txt, r->start);
1251 s->cursor = text_mark_set(txt, end);
1252 return true;
1255 void view_selections_set_all(View *view, Array *arr, bool anchored) {
1256 Selection *s;
1257 Filerange *r;
1258 size_t i = 0;
1259 for (s = view->selections; s; s = s->next) {
1260 if (!(r = array_get(arr, i++)) || !view_selections_set(s, r)) {
1261 for (Selection *next; s; s = next) {
1262 next = view_selections_next(s);
1263 if (i == 1 && s == view->selection)
1264 view_selection_clear(s);
1265 else
1266 view_selections_dispose(s);
1268 break;
1270 s->anchored = anchored;
1272 while ((r = array_get(arr, i++))) {
1273 s = view_selections_new_force(view, r->start);
1274 if (!s || !view_selections_set(s, r))
1275 break;
1276 s->anchored = anchored;
1278 view_selections_primary_set(view->selections);
1281 Array view_selections_get_all(View *view) {
1282 Array arr;
1283 array_init_sized(&arr, sizeof(Filerange));
1284 if (!array_reserve(&arr, view_selections_count(view)))
1285 return arr;
1286 for (Selection *s = view->selections; s; s = s->next) {
1287 Filerange r = view_selections_get(s);
1288 if (text_range_valid(&r))
1289 array_add(&arr, &r);
1291 return arr;
1294 void view_selections_normalize(View *view) {
1295 Selection *prev = NULL;
1296 Filerange range_prev = text_range_empty();
1297 for (Selection *s = view->selections, *next; s; s = next) {
1298 next = s->next;
1299 Filerange range = view_selections_get(s);
1300 if (!text_range_valid(&range)) {
1301 view_selections_dispose(s);
1302 } else if (prev && text_range_overlap(&range_prev, &range)) {
1303 range_prev = text_range_union(&range_prev, &range);
1304 view_selections_dispose(s);
1305 } else {
1306 if (prev)
1307 view_selections_set(prev, &range_prev);
1308 range_prev = range;
1309 prev = s;
1312 if (prev)
1313 view_selections_set(prev, &range_prev);
1316 Text *view_text(View *view) {
1317 return view->text;
1320 char *view_symbol_eof_get(View *view) {
1321 return view->symbols[SYNTAX_SYMBOL_EOF]->symbol;
1324 bool view_style_define(View *view, enum UiStyle id, const char *style) {
1325 return view->ui->style_define(view->ui, id, style);
1328 void view_style(View *view, enum UiStyle style_id, size_t start, size_t end) {
1329 if (end < view->start || start > view->end)
1330 return;
1332 CellStyle style = view->ui->style_get(view->ui, style_id);
1333 size_t pos = view->start;
1334 Line *line = view->topline;
1336 /* skip lines before range to be styled */
1337 while (line && pos + line->len <= start) {
1338 pos += line->len;
1339 line = line->next;
1342 if (!line)
1343 return;
1345 int col = 0, width = view->width;
1347 /* skip columns before range to be styled */
1348 while (pos < start && col < width)
1349 pos += line->cells[col++].len;
1351 do {
1352 while (pos <= end && col < width) {
1353 pos += line->cells[col].len;
1354 line->cells[col++].style = style;
1356 col = 0;
1357 } while (pos <= end && (line = line->next));