doc: enable mathjax support for sphinx documentation
[vis.git] / view.c
blob2106b0ece084c8bd7ff058b591fc1274b558598a
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_LAST,
25 /* A selection is made up of two marks named cursor and anchor.
26 * While the anchor remains fixed the cursor mark follows cursor motions.
27 * For a selection (indicated by []), the marks (^) are placed as follows:
29 * [some text] [!]
30 * ^ ^ ^
31 * ^
33 * That is the marks point to the *start* of the first and last character
34 * of the selection. In particular for a single character selection (as
35 * depicted on the right above) both marks point to the same location.
37 * The view_selections_{get,set} functions take care of adding/removing
38 * the necessary offset for the last character.
41 typedef struct {
42 Mark anchor;
43 Mark cursor;
44 } SelectionRegion;
46 struct Cursor { /* cursor position */
47 Mark cursor; /* other selection endpoint where it changes */
48 Mark anchor; /* position where the selection was created */
49 bool anchored; /* whether anchor remains fixed */
50 size_t pos; /* in bytes from the start of the file */
51 int row, col; /* in terms of zero based screen coordinates */
52 int lastcol; /* remembered column used when moving across lines */
53 Line *line; /* screen line on which cursor currently resides */
54 int generation; /* used to filter out newly created cursors during iteration */
55 int number; /* how many cursors are located before this one */
56 SelectionRegion region; /* saved selection region */
57 View *view; /* associated view to which this cursor belongs */
58 Cursor *prev, *next;/* previous/next cursors ordered by location at creation time */
61 /* Viewable area, showing part of a file. Keeps track of cursors and selections.
62 * At all times there exists at least one cursor, which is placed in the visible viewport.
63 * Additional cursors can be created and positioned anywhere in the file. */
64 struct View {
65 Text *text; /* underlying text management */
66 UiWin *ui;
67 Cell cell_blank; /* used for empty/blank cells */
68 int width, height; /* size of display area */
69 size_t start, end; /* currently displayed area [start, end] in bytes from the start of the file */
70 size_t start_last; /* previously used start of visible area, used to update the mark */
71 Mark start_mark; /* mark to keep track of the start of the visible area */
72 size_t lines_size; /* number of allocated bytes for lines (grows only) */
73 Line *lines; /* view->height number of lines representing view content */
74 Line *topline; /* top of the view, first line currently shown */
75 Line *lastline; /* last currently used line, always <= bottomline */
76 Line *bottomline; /* bottom of view, might be unused if lastline < bottomline */
77 Cursor *cursor; /* main cursor, always placed within the visible viewport */
78 Cursor *cursor_latest; /* most recently created cursor */
79 Cursor *cursor_dead;/* main cursor which was disposed, will be removed when another cursor is created */
80 int cursor_count; /* how many cursors do currently exist */
81 Line *line; /* used while drawing view content, line where next char will be drawn */
82 int col; /* used while drawing view content, column where next char will be drawn */
83 const SyntaxSymbol *symbols[SYNTAX_SYMBOL_LAST]; /* symbols to use for white spaces etc */
84 int tabwidth; /* how many spaces should be used to display a tab character */
85 Cursor *cursors; /* all cursors currently active */
86 int cursor_generation; /* used to filter out newly created cursors during iteration */
87 bool need_update; /* whether view has been redrawn */
88 bool large_file; /* optimize for displaying large files */
89 int colorcolumn;
92 static const SyntaxSymbol symbols_none[] = {
93 [SYNTAX_SYMBOL_SPACE] = { " " },
94 [SYNTAX_SYMBOL_TAB] = { " " },
95 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
96 [SYNTAX_SYMBOL_EOL] = { " " },
99 static const SyntaxSymbol symbols_default[] = {
100 [SYNTAX_SYMBOL_SPACE] = { "·" /* Middle Dot U+00B7 */ },
101 [SYNTAX_SYMBOL_TAB] = { "›" /* Single Right-Pointing Angle Quotation Mark U+203A */ },
102 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
103 [SYNTAX_SYMBOL_EOL] = { "↵" /* Downwards Arrow with Corner Leftwards U+21B5 */ },
106 static Cell cell_unused;
108 static void view_clear(View *view);
109 static bool view_addch(View *view, Cell *cell);
110 static void view_cursors_free(Cursor *c);
111 /* set/move current cursor position to a given (line, column) pair */
112 static size_t cursor_set(Cursor *cursor, Line *line, int col);
114 void view_tabwidth_set(View *view, int tabwidth) {
115 view->tabwidth = tabwidth;
116 view_draw(view);
119 /* reset internal view data structures (cell matrix, line offsets etc.) */
120 static void view_clear(View *view) {
121 memset(view->lines, 0, view->lines_size);
122 if (view->start != view->start_last) {
123 if (view->start == 0)
124 view->start_mark = EMARK;
125 else
126 view->start_mark = text_mark_set(view->text, view->start);
127 } else {
128 size_t start;
129 if (view->start_mark == EMARK)
130 start = 0;
131 else
132 start = text_mark_get(view->text, view->start_mark);
133 if (start != EPOS)
134 view->start = start;
137 view->start_last = view->start;
138 view->topline = view->lines;
139 view->topline->lineno = view->large_file ? 1 : text_lineno_by_pos(view->text, view->start);
140 view->lastline = view->topline;
142 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
143 size_t end = view->height * line_size;
144 Line *prev = NULL;
145 for (size_t i = 0; i < end; i += line_size) {
146 Line *line = (Line*)(((char*)view->lines) + i);
147 line->prev = prev;
148 if (prev)
149 prev->next = line;
150 prev = line;
152 view->bottomline = prev ? prev : view->topline;
153 view->bottomline->next = NULL;
154 view->line = view->topline;
155 view->col = 0;
156 if (view->ui)
157 view->cell_blank.style = view->ui->style_get(view->ui, UI_STYLE_DEFAULT);
160 Filerange view_viewport_get(View *view) {
161 return (Filerange){ .start = view->start, .end = view->end };
164 /* try to add another character to the view, return whether there was space left */
165 static bool view_addch(View *view, Cell *cell) {
166 if (!view->line)
167 return false;
169 int width;
170 size_t lineno = view->line->lineno;
171 unsigned char ch = (unsigned char)cell->data[0];
172 cell->style = view->cell_blank.style;
174 switch (ch) {
175 case '\t':
176 cell->width = 1;
177 width = view->tabwidth - (view->col % view->tabwidth);
178 for (int w = 0; w < width; w++) {
179 if (view->col + 1 > view->width) {
180 view->line = view->line->next;
181 view->col = 0;
182 if (!view->line)
183 return false;
184 view->line->lineno = lineno;
187 cell->len = w == 0 ? 1 : 0;
188 int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
189 strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
190 view->line->cells[view->col] = *cell;
191 view->line->len += cell->len;
192 view->line->width += cell->width;
193 view->col++;
195 cell->len = 1;
196 return true;
197 case '\n':
198 cell->width = 1;
199 if (view->col + cell->width > view->width) {
200 view->line = view->line->next;
201 view->col = 0;
202 if (!view->line)
203 return false;
204 view->line->lineno = lineno;
207 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
209 view->line->cells[view->col] = *cell;
210 view->line->len += cell->len;
211 view->line->width += cell->width;
212 for (int i = view->col + 1; i < view->width; i++)
213 view->line->cells[i] = view->cell_blank;
215 view->line = view->line->next;
216 if (view->line)
217 view->line->lineno = lineno + 1;
218 view->col = 0;
219 return true;
220 default:
221 if (ch < 128 && !isprint(ch)) {
222 /* non-printable ascii char, represent it as ^(char + 64) */
223 *cell = (Cell) {
224 .data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
225 .len = 1,
226 .width = 2,
227 .style = cell->style,
231 if (ch == ' ') {
232 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
236 if (view->col + cell->width > view->width) {
237 for (int i = view->col; i < view->width; i++)
238 view->line->cells[i] = view->cell_blank;
239 view->line = view->line->next;
240 view->col = 0;
243 if (view->line) {
244 view->line->width += cell->width;
245 view->line->len += cell->len;
246 view->line->lineno = lineno;
247 view->line->cells[view->col] = *cell;
248 view->col++;
249 /* set cells of a character which uses multiple columns */
250 for (int i = 1; i < cell->width; i++)
251 view->line->cells[view->col++] = cell_unused;
252 return true;
254 return false;
258 static void cursor_to(Cursor *c, size_t pos) {
259 Text *txt = c->view->text;
260 c->cursor = text_mark_set(txt, pos);
261 if (!c->anchored)
262 c->anchor = c->cursor;
263 if (pos != c->pos)
264 c->lastcol = 0;
265 c->pos = pos;
266 if (!view_coord_get(c->view, pos, &c->line, &c->row, &c->col)) {
267 if (c->view->cursor == c) {
268 c->line = c->view->topline;
269 c->row = 0;
270 c->col = 0;
272 return;
274 // TODO: minimize number of redraws
275 view_draw(c->view);
278 bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
279 int row = 0, col = 0;
280 size_t cur = view->start;
281 Line *line = view->topline;
283 if (pos < view->start || pos > view->end) {
284 if (retline) *retline = NULL;
285 if (retrow) *retrow = -1;
286 if (retcol) *retcol = -1;
287 return false;
290 while (line && line != view->lastline && cur < pos) {
291 if (cur + line->len > pos)
292 break;
293 cur += line->len;
294 line = line->next;
295 row++;
298 if (line) {
299 int max_col = MIN(view->width, line->width);
300 while (cur < pos && col < max_col) {
301 cur += line->cells[col].len;
302 /* skip over columns occupied by the same character */
303 while (++col < max_col && line->cells[col].len == 0);
305 } else {
306 line = view->bottomline;
307 row = view->height - 1;
310 if (retline) *retline = line;
311 if (retrow) *retrow = row;
312 if (retcol) *retcol = col;
313 return true;
316 /* move the cursor to the character at pos bytes from the begining of the file.
317 * if pos is not in the current viewport, redraw the view to make it visible */
318 void view_cursor_to(View *view, size_t pos) {
319 view_cursors_to(view->cursor, pos);
322 /* redraw the complete with data starting from view->start bytes into the file.
323 * stop once the screen is full, update view->end, view->lastline */
324 void view_draw(View *view) {
325 view_clear(view);
326 /* read a screenful of text considering each character as 4-byte UTF character*/
327 const size_t size = view->width * view->height * 4;
328 /* current buffer to work with */
329 char text[size+1];
330 /* remaining bytes to process in buffer */
331 size_t rem = text_bytes_get(view->text, view->start, size, text);
332 /* NUL terminate text section */
333 text[rem] = '\0';
334 /* absolute position of character currently being added to display */
335 size_t pos = view->start;
336 /* current position into buffer from which to interpret a character */
337 char *cur = text;
338 /* start from known multibyte state */
339 mbstate_t mbstate = { 0 };
341 Cell cell = { .data = "", .len = 0, .width = 0, }, prev_cell = cell;
343 while (rem > 0) {
345 /* current 'parsed' character' */
346 wchar_t wchar;
348 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
349 if (len == (size_t)-1 && errno == EILSEQ) {
350 /* ok, we encountered an invalid multibyte sequence,
351 * replace it with the Unicode Replacement Character
352 * (FFFD) and skip until the start of the next utf8 char */
353 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
354 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
355 } else if (len == (size_t)-2) {
356 /* not enough bytes available to convert to a
357 * wide character. advance file position and read
358 * another junk into buffer.
360 rem = text_bytes_get(view->text, pos, size, text);
361 text[rem] = '\0';
362 cur = text;
363 continue;
364 } else if (len == 0) {
365 /* NUL byte encountered, store it and continue */
366 cell = (Cell){ .data = "\x00", .len = 1, .width = 2 };
367 } else {
368 if (len >= sizeof(cell.data))
369 len = sizeof(cell.data)-1;
370 for (size_t i = 0; i < len; i++)
371 cell.data[i] = cur[i];
372 cell.data[len] = '\0';
373 cell.len = len;
374 cell.width = wcwidth(wchar);
375 if (cell.width == -1)
376 cell.width = 1;
379 if (cell.width == 0 && prev_cell.len + cell.len < sizeof(cell.data)) {
380 prev_cell.len += cell.len;
381 strcat(prev_cell.data, cell.data);
382 } else {
383 if (prev_cell.len && !view_addch(view, &prev_cell))
384 break;
385 pos += prev_cell.len;
386 prev_cell = cell;
389 rem -= cell.len;
390 cur += cell.len;
392 memset(&cell, 0, sizeof cell);
395 if (prev_cell.len && view_addch(view, &prev_cell))
396 pos += prev_cell.len;
398 /* set end of viewing region */
399 view->end = pos;
400 if (view->line) {
401 bool eof = view->end == text_size(view->text);
402 if (view->line->len == 0 && eof && view->line->prev)
403 view->lastline = view->line->prev;
404 else
405 view->lastline = view->line;
406 } else {
407 view->lastline = view->bottomline;
410 /* clear remaining of line, important to show cursor at end of file */
411 if (view->line) {
412 for (int x = view->col; x < view->width; x++)
413 view->line->cells[x] = view->cell_blank;
416 /* resync position of cursors within visible area */
417 for (Cursor *c = view->cursors; c; c = c->next) {
418 size_t pos = view_cursors_pos(c);
419 if (!view_coord_get(view, pos, &c->line, &c->row, &c->col) &&
420 c == view->cursor) {
421 c->line = view->topline;
422 c->row = 0;
423 c->col = 0;
427 view->need_update = true;
430 void view_invalidate(View *view) {
431 view->need_update = true;
434 bool view_update(View *view) {
435 if (!view->need_update)
436 return false;
437 for (Line *l = view->lastline->next; l; l = l->next) {
438 for (int x = 0; x < view->width; x++)
439 l->cells[x] = view->cell_blank;
441 view->need_update = false;
442 return true;
445 bool view_resize(View *view, int width, int height) {
446 if (width <= 0)
447 width = 1;
448 if (height <= 0)
449 height = 1;
450 if (view->width == width && view->height == height) {
451 view->need_update = true;
452 return true;
454 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
455 if (lines_size > view->lines_size) {
456 Line *lines = realloc(view->lines, lines_size);
457 if (!lines)
458 return false;
459 view->lines = lines;
460 view->lines_size = lines_size;
462 view->width = width;
463 view->height = height;
464 memset(view->lines, 0, view->lines_size);
465 view_draw(view);
466 return true;
469 int view_height_get(View *view) {
470 return view->height;
473 int view_width_get(View *view) {
474 return view->width;
477 void view_free(View *view) {
478 if (!view)
479 return;
480 while (view->cursors)
481 view_cursors_free(view->cursors);
482 free(view->lines);
483 free(view);
486 void view_reload(View *view, Text *text) {
487 view->text = text;
488 view_selections_clear(view);
489 view_cursor_to(view, 0);
492 View *view_new(Text *text) {
493 if (!text)
494 return NULL;
495 View *view = calloc(1, sizeof(View));
496 if (!view)
497 return NULL;
498 if (!view_cursors_new(view, 0)) {
499 view_free(view);
500 return NULL;
503 view->cell_blank = (Cell) {
504 .width = 0,
505 .len = 0,
506 .data = " ",
508 view->text = text;
509 view->tabwidth = 8;
510 view_options_set(view, 0);
512 if (!view_resize(view, 1, 1)) {
513 view_free(view);
514 return NULL;
517 view_cursor_to(view, 0);
519 return view;
522 void view_ui(View *view, UiWin* ui) {
523 view->ui = ui;
526 static size_t cursor_set(Cursor *cursor, Line *line, int col) {
527 int row = 0;
528 View *view = cursor->view;
529 size_t pos = view->start;
530 /* get row number and file offset at start of the given line */
531 for (Line *cur = view->topline; cur && cur != line; cur = cur->next) {
532 pos += cur->len;
533 row++;
536 /* for characters which use more than 1 column, make sure we are on the left most */
537 while (col > 0 && line->cells[col].len == 0)
538 col--;
539 /* calculate offset within the line */
540 for (int i = 0; i < col; i++)
541 pos += line->cells[i].len;
543 cursor->col = col;
544 cursor->row = row;
545 cursor->line = line;
547 cursor_to(cursor, pos);
549 return pos;
552 bool view_viewport_down(View *view, int n) {
553 Line *line;
554 if (view->end >= text_size(view->text))
555 return false;
556 if (n >= view->height) {
557 view->start = view->end;
558 } else {
559 for (line = view->topline; line && n > 0; line = line->next, n--)
560 view->start += line->len;
562 view_draw(view);
563 return true;
566 bool view_viewport_up(View *view, int n) {
567 /* scrolling up is somewhat tricky because we do not yet know where
568 * the lines start, therefore scan backwards but stop at a reasonable
569 * maximum in case we are dealing with a file without any newlines
571 if (view->start == 0)
572 return false;
573 size_t max = view->width * view->height;
574 char c;
575 Iterator it = text_iterator_get(view->text, view->start - 1);
577 if (!text_iterator_byte_get(&it, &c))
578 return false;
579 size_t off = 0;
580 /* skip newlines immediately before display area */
581 if (c == '\n' && text_iterator_byte_prev(&it, &c))
582 off++;
583 do {
584 if (c == '\n' && --n == 0)
585 break;
586 if (++off > max)
587 break;
588 } while (text_iterator_byte_prev(&it, &c));
589 view->start -= MIN(view->start, off);
590 view_draw(view);
591 return true;
594 void view_redraw_top(View *view) {
595 Line *line = view->cursor->line;
596 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
597 view->start += cur->len;
598 view_draw(view);
599 view_cursor_to(view, view->cursor->pos);
602 void view_redraw_center(View *view) {
603 int center = view->height / 2;
604 size_t pos = view->cursor->pos;
605 for (int i = 0; i < 2; i++) {
606 int linenr = 0;
607 Line *line = view->cursor->line;
608 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
609 linenr++;
610 if (linenr < center) {
611 view_slide_down(view, center - linenr);
612 continue;
614 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
615 view->start += cur->len;
616 linenr--;
618 break;
620 view_draw(view);
621 view_cursor_to(view, pos);
624 void view_redraw_bottom(View *view) {
625 Line *line = view->cursor->line;
626 if (line == view->lastline)
627 return;
628 size_t pos = view->cursor->pos;
629 view_viewport_up(view, view->height);
630 while (pos >= view->end && view_viewport_down(view, 1));
631 cursor_to(view->cursor, pos);
634 size_t view_slide_up(View *view, int lines) {
635 Cursor *cursor = view->cursor;
636 if (view_viewport_down(view, lines)) {
637 if (cursor->line == view->topline)
638 cursor_set(cursor, view->topline, cursor->col);
639 else
640 view_cursor_to(view, cursor->pos);
641 } else {
642 view_screenline_down(cursor);
644 return cursor->pos;
647 size_t view_slide_down(View *view, int lines) {
648 Cursor *cursor = view->cursor;
649 bool lastline = cursor->line == view->lastline;
650 size_t col = cursor->col;
651 if (view_viewport_up(view, lines)) {
652 if (lastline)
653 cursor_set(cursor, view->lastline, col);
654 else
655 view_cursor_to(view, cursor->pos);
656 } else {
657 view_screenline_up(cursor);
659 return cursor->pos;
662 size_t view_scroll_up(View *view, int lines) {
663 Cursor *cursor = view->cursor;
664 if (view_viewport_up(view, lines)) {
665 Line *line = cursor->line < view->lastline ? cursor->line : view->lastline;
666 cursor_set(cursor, line, view->cursor->col);
667 } else {
668 view_cursor_to(view, 0);
670 return cursor->pos;
673 size_t view_scroll_page_up(View *view) {
674 Cursor *cursor = view->cursor;
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(cursor);
682 return cursor->pos;
685 size_t view_scroll_page_down(View *view) {
686 view_scroll_down(view, view->height);
687 return view_screenline_begin(view->cursor);
690 size_t view_scroll_halfpage_up(View *view) {
691 Cursor *cursor = view->cursor;
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(cursor);
699 return cursor->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->cursor->pos;
710 size_t view_scroll_down(View *view, int lines) {
711 Cursor *cursor = view->cursor;
712 if (view_viewport_down(view, lines)) {
713 Line *line = cursor->line > view->topline ? cursor->line : view->topline;
714 cursor_set(cursor, line, cursor->col);
715 } else {
716 view_cursor_to(view, text_size(view->text));
718 return cursor->pos;
721 size_t view_line_up(Cursor *cursor) {
722 View *view = cursor->view;
723 int lastcol = cursor->lastcol;
724 if (!lastcol)
725 lastcol = cursor->col;
726 size_t pos = text_line_up(cursor->view->text, cursor->pos);
727 bool offscreen = view->cursor == cursor && pos < view->start;
728 view_cursors_to(cursor, pos);
729 if (offscreen)
730 view_redraw_top(view);
731 if (cursor->line)
732 cursor_set(cursor, cursor->line, lastcol);
733 cursor->lastcol = lastcol;
734 return cursor->pos;
737 size_t view_line_down(Cursor *cursor) {
738 View *view = cursor->view;
739 int lastcol = cursor->lastcol;
740 if (!lastcol)
741 lastcol = cursor->col;
742 size_t pos = text_line_down(cursor->view->text, cursor->pos);
743 bool offscreen = view->cursor == cursor && pos > view->end;
744 view_cursors_to(cursor, pos);
745 if (offscreen)
746 view_redraw_bottom(view);
747 if (cursor->line)
748 cursor_set(cursor, cursor->line, lastcol);
749 cursor->lastcol = lastcol;
750 return cursor->pos;
753 size_t view_screenline_up(Cursor *cursor) {
754 if (!cursor->line)
755 return view_line_up(cursor);
756 int lastcol = cursor->lastcol;
757 if (!lastcol)
758 lastcol = cursor->col;
759 if (!cursor->line->prev)
760 view_scroll_up(cursor->view, 1);
761 if (cursor->line->prev)
762 cursor_set(cursor, cursor->line->prev, lastcol);
763 cursor->lastcol = lastcol;
764 return cursor->pos;
767 size_t view_screenline_down(Cursor *cursor) {
768 if (!cursor->line)
769 return view_line_down(cursor);
770 int lastcol = cursor->lastcol;
771 if (!lastcol)
772 lastcol = cursor->col;
773 if (!cursor->line->next && cursor->line == cursor->view->bottomline)
774 view_scroll_down(cursor->view, 1);
775 if (cursor->line->next)
776 cursor_set(cursor, cursor->line->next, lastcol);
777 cursor->lastcol = lastcol;
778 return cursor->pos;
781 size_t view_screenline_begin(Cursor *cursor) {
782 if (!cursor->line)
783 return cursor->pos;
784 return cursor_set(cursor, cursor->line, 0);
787 size_t view_screenline_middle(Cursor *cursor) {
788 if (!cursor->line)
789 return cursor->pos;
790 return cursor_set(cursor, cursor->line, cursor->line->width / 2);
793 size_t view_screenline_end(Cursor *cursor) {
794 if (!cursor->line)
795 return cursor->pos;
796 int col = cursor->line->width - 1;
797 return cursor_set(cursor, cursor->line, col >= 0 ? col : 0);
800 size_t view_cursor_get(View *view) {
801 return view_cursors_pos(view->cursor);
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(Cursor *c) {
813 return c->line;
816 void view_scroll_to(View *view, size_t pos) {
817 view_cursors_scroll_to(view->cursor, 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,
828 for (int i = 0; i < LENGTH(mapping); i++) {
829 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
830 &symbols_none[i];
833 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
834 options &= ~UI_OPTION_LARGE_FILE;
836 view->large_file = (options & UI_OPTION_LARGE_FILE);
838 if (view->ui)
839 view->ui->options_set(view->ui, options);
842 enum UiOption view_options_get(View *view) {
843 return view->ui ? view->ui->options_get(view->ui) : 0;
846 void view_colorcolumn_set(View *view, int col) {
847 if (col >= 0)
848 view->colorcolumn = col;
851 int view_colorcolumn_get(View *view) {
852 return view->colorcolumn;
855 size_t view_screenline_goto(View *view, int n) {
856 size_t pos = view->start;
857 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
858 pos += line->len;
859 return pos;
862 static Cursor *cursors_new(View *view, size_t pos, bool force) {
863 Cursor *c = calloc(1, sizeof(*c));
864 if (!c)
865 return NULL;
866 c->view = view;
867 c->generation = view->cursor_generation;
868 if (!view->cursors) {
869 view->cursor = c;
870 view->cursor_latest = c;
871 view->cursors = c;
872 view->cursor_count = 1;
873 return c;
876 Cursor *prev = NULL, *next = NULL;
877 Cursor *latest = view->cursor_latest ? view->cursor_latest : view->cursor;
878 size_t cur = view_cursors_pos(latest);
879 if (pos == cur) {
880 prev = latest;
881 next = prev->next;
882 } else if (pos > cur) {
883 prev = latest;
884 for (next = prev->next; next; prev = next, next = next->next) {
885 cur = view_cursors_pos(next);
886 if (pos <= cur)
887 break;
889 } else if (pos < cur) {
890 next = latest;
891 for (prev = next->prev; prev; next = prev, prev = prev->prev) {
892 cur = view_cursors_pos(prev);
893 if (pos >= cur)
894 break;
898 if (pos == cur && !force)
899 goto err;
901 for (Cursor *after = next; after; after = after->next)
902 after->number++;
904 c->prev = prev;
905 c->next = next;
906 if (next)
907 next->prev = c;
908 if (prev) {
909 prev->next = c;
910 c->number = prev->number + 1;
911 } else {
912 view->cursors = c;
914 view->cursor_latest = c;
915 view->cursor_count++;
916 view_cursors_dispose(view->cursor_dead);
917 view_cursors_to(c, pos);
918 return c;
919 err:
920 free(c);
921 return NULL;
924 Cursor *view_cursors_new(View *view, size_t pos) {
925 return cursors_new(view, pos, false);
928 Cursor *view_cursors_new_force(View *view, size_t pos) {
929 return cursors_new(view, pos, true);
932 int view_cursors_count(View *view) {
933 return view->cursor_count;
936 int view_cursors_number(Cursor *c) {
937 return c->number;
940 int view_cursors_column_count(View *view) {
941 Text *txt = view->text;
942 int cpl_max = 0, cpl = 0; /* cursors per line */
943 size_t line_prev = 0;
944 for (Cursor *c = view->cursors; c; c = c->next) {
945 size_t pos = view_cursors_pos(c);
946 size_t line = text_lineno_by_pos(txt, pos);
947 if (line == line_prev)
948 cpl++;
949 else
950 cpl = 1;
951 line_prev = line;
952 if (cpl > cpl_max)
953 cpl_max = cpl;
955 return cpl_max;
958 static Cursor *cursors_column_next(View *view, Cursor *c, int column) {
959 size_t line_cur = 0;
960 int column_cur = 0;
961 Text *txt = view->text;
962 if (c) {
963 size_t pos = view_cursors_pos(c);
964 line_cur = text_lineno_by_pos(txt, pos);
965 column_cur = INT_MIN;
966 } else {
967 c = view->cursors;
970 for (; c; c = c->next) {
971 size_t pos = view_cursors_pos(c);
972 size_t line = text_lineno_by_pos(txt, pos);
973 if (line != line_cur) {
974 line_cur = line;
975 column_cur = 0;
976 } else {
977 column_cur++;
979 if (column == column_cur)
980 return c;
982 return NULL;
985 Cursor *view_cursors_column(View *view, int column) {
986 return cursors_column_next(view, NULL, column);
989 Cursor *view_cursors_column_next(Cursor *c, int column) {
990 return cursors_column_next(c->view, c, column);
993 bool view_cursors_multiple(View *view) {
994 return view->cursors && view->cursors->next;
997 static void view_cursors_free(Cursor *c) {
998 if (!c)
999 return;
1000 for (Cursor *after = c->next; after; after = after->next)
1001 after->number--;
1002 if (c->prev)
1003 c->prev->next = c->next;
1004 if (c->next)
1005 c->next->prev = c->prev;
1006 if (c->view->cursors == c)
1007 c->view->cursors = c->next;
1008 if (c->view->cursor == c)
1009 c->view->cursor = c->next ? c->next : c->prev;
1010 if (c->view->cursor_dead == c)
1011 c->view->cursor_dead = NULL;
1012 if (c->view->cursor_latest == c)
1013 c->view->cursor_latest = c->prev ? c->prev : c->next;
1014 c->view->cursor_count--;
1015 free(c);
1018 bool view_cursors_dispose(Cursor *c) {
1019 if (!c)
1020 return true;
1021 View *view = c->view;
1022 if (!view->cursors || !view->cursors->next)
1023 return false;
1024 view_cursors_free(c);
1025 view_cursors_primary_set(view->cursor);
1026 return true;
1029 bool view_cursors_dispose_force(Cursor *c) {
1030 if (view_cursors_dispose(c))
1031 return true;
1032 View *view = c->view;
1033 if (view->cursor_dead)
1034 return false;
1035 view_cursors_selection_clear(c);
1036 view->cursor_dead = c;
1037 return true;
1040 Cursor *view_cursor_disposed(View *view) {
1041 Cursor *c = view->cursor_dead;
1042 view->cursor_dead = NULL;
1043 return c;
1046 Cursor *view_cursors(View *view) {
1047 view->cursor_generation++;
1048 return view->cursors;
1051 Cursor *view_cursors_primary_get(View *view) {
1052 view->cursor_generation++;
1053 return view->cursor;
1056 void view_cursors_primary_set(Cursor *c) {
1057 if (!c)
1058 return;
1059 c->view->cursor = c;
1062 Cursor *view_cursors_prev(Cursor *c) {
1063 View *view = c->view;
1064 for (c = c->prev; c; c = c->prev) {
1065 if (c->generation != view->cursor_generation)
1066 return c;
1068 view->cursor_generation++;
1069 return NULL;
1072 Cursor *view_cursors_next(Cursor *c) {
1073 View *view = c->view;
1074 for (c = c->next; c; c = c->next) {
1075 if (c->generation != view->cursor_generation)
1076 return c;
1078 view->cursor_generation++;
1079 return NULL;
1082 size_t view_cursors_pos(Cursor *c) {
1083 return text_mark_get(c->view->text, c->cursor);
1086 size_t view_cursors_line(Cursor *c) {
1087 size_t pos = view_cursors_pos(c);
1088 return text_lineno_by_pos(c->view->text, pos);
1091 size_t view_cursors_col(Cursor *c) {
1092 size_t pos = view_cursors_pos(c);
1093 return text_line_char_get(c->view->text, pos) + 1;
1096 int view_cursors_cell_get(Cursor *c) {
1097 return c->line ? c->col : -1;
1100 int view_cursors_cell_set(Cursor *c, int cell) {
1101 if (!c->line || cell < 0)
1102 return -1;
1103 cursor_set(c, c->line, cell);
1104 return c->col;
1107 void view_cursors_scroll_to(Cursor *c, size_t pos) {
1108 View *view = c->view;
1109 if (view->cursor == c) {
1110 view_draw(view);
1111 while (pos < view->start && view_viewport_up(view, 1));
1112 while (pos > view->end && view_viewport_down(view, 1));
1114 view_cursors_to(c, pos);
1117 void view_cursors_to(Cursor *c, size_t pos) {
1118 View *view = c->view;
1119 if (pos == EPOS)
1120 return;
1121 size_t size = text_size(view->text);
1122 if (pos > size)
1123 pos = size;
1124 if (c->view->cursor == c) {
1125 /* make sure we redraw changes to the very first character of the window */
1126 if (view->start == pos)
1127 view->start_last = 0;
1129 if (view->end == pos && view->lastline == view->bottomline) {
1130 view->start += view->topline->len;
1131 view_draw(view);
1134 if (pos < view->start || pos > view->end) {
1135 view->start = pos;
1136 view_viewport_up(view, view->height / 2);
1139 if (pos <= view->start || pos > view->end) {
1140 view->start = text_line_begin(view->text, pos);
1141 view_draw(view);
1144 if (pos <= view->start || pos > view->end) {
1145 view->start = pos;
1146 view_draw(view);
1150 cursor_to(c, pos);
1153 void view_cursors_place(Cursor *c, size_t line, size_t col) {
1154 Text *txt = c->view->text;
1155 size_t pos = text_pos_by_lineno(txt, line);
1156 pos = text_line_char_set(txt, pos, col > 0 ? col-1 : col);
1157 view_cursors_to(c, pos);
1160 void view_cursors_selection_start(Cursor *c) {
1161 c->anchored = true;
1164 void view_cursors_selection_clear(Cursor *c) {
1165 c->anchored = false;
1166 c->anchor = c->cursor;
1167 c->view->need_update = true;
1170 void view_cursors_selection_swap(Cursor *s) {
1171 Mark temp = s->anchor;
1172 s->anchor = s->cursor;
1173 s->cursor = temp;
1174 view_cursors_to(s, text_mark_get(s->view->text, s->cursor));
1177 bool view_selection_anchored(Selection *s) {
1178 return s->anchored;
1181 void view_selections_clear(View *view) {
1182 for (Cursor *c = view->cursors; c; c = c->next)
1183 view_cursors_selection_clear(c);
1184 view_draw(view);
1187 void view_cursors_clear(View *view) {
1188 for (Cursor *c = view->cursors, *next; c; c = next) {
1189 next = c->next;
1190 if (c != view->cursor)
1191 view_cursors_free(c);
1193 view_draw(view);
1196 Filerange view_selection_get(View *view) {
1197 return view_cursors_selection_get(view->cursor);
1200 Filerange view_cursors_selection_get(Selection *s) {
1201 if (!s)
1202 return text_range_empty();
1203 Text *txt = s->view->text;
1204 size_t anchor = text_mark_get(txt, s->anchor);
1205 size_t cursor = text_mark_get(txt, s->cursor);
1206 Filerange sel = text_range_new(anchor, cursor);
1207 if (text_range_valid(&sel))
1208 sel.end = text_char_next(txt, sel.end);
1209 return sel;
1212 void view_cursors_selection_set(Selection *s, const Filerange *r) {
1213 if (!text_range_valid(r))
1214 return;
1215 Text *txt = s->view->text;
1216 size_t anchor = text_mark_get(txt, s->anchor);
1217 size_t cursor = text_mark_get(txt, s->cursor);
1218 bool left_extending = anchor != EPOS && anchor > cursor;
1219 size_t end = r->end;
1220 if (r->start != end)
1221 end = text_char_prev(txt, end);
1222 if (left_extending) {
1223 s->anchor = text_mark_set(txt, end);
1224 s->cursor = text_mark_set(txt, r->start);
1225 } else {
1226 s->anchor = text_mark_set(txt, r->start);
1227 s->cursor = text_mark_set(txt, end);
1229 s->anchored = true;
1230 view_cursors_to(s, text_mark_get(s->view->text, s->cursor));
1233 void view_cursors_selection_save(Selection *s) {
1234 s->region.cursor = s->cursor;
1235 s->region.anchor = s->anchor;
1238 bool view_cursors_selection_restore(Selection *s) {
1239 Text *txt = s->view->text;
1240 size_t pos = text_mark_get(txt, s->region.cursor);
1241 if (pos == EPOS)
1242 return false;
1243 if (s->region.anchor != s->region.cursor && text_mark_get(txt, s->region.anchor) == EPOS)
1244 return false;
1245 s->cursor = s->region.cursor;
1246 s->anchor = s->region.anchor;
1247 s->anchored = true;
1248 view_cursors_to(s, pos);
1249 return true;
1252 Text *view_text(View *view) {
1253 return view->text;
1256 bool view_style_define(View *view, enum UiStyle id, const char *style) {
1257 return view->ui->style_define(view->ui, id, style);
1260 void view_style(View *view, enum UiStyle style_id, size_t start, size_t end) {
1261 if (end < view->start || start > view->end)
1262 return;
1264 CellStyle style = view->ui->style_get(view->ui, style_id);
1265 size_t pos = view->start;
1266 Line *line = view->topline;
1268 /* skip lines before range to be styled */
1269 while (line && pos + line->len <= start) {
1270 pos += line->len;
1271 line = line->next;
1274 if (!line)
1275 return;
1277 int col = 0, width = view->width;
1279 /* skip columns before range to be styled */
1280 while (pos < start && col < width)
1281 pos += line->cells[col++].len;
1283 do {
1284 while (pos <= end && col < width) {
1285 pos += line->cells[col].len;
1286 line->cells[col++].style = style;
1288 col = 0;
1289 } while (pos <= end && (line = line->next));