vis: more comments and cleanups
[vis.git] / view.c
blob0491c78426f0f78f3f9c5ad1678650dbe6df7057
1 /*
2 * Copyright (c) 2014 Marc André Tanner <mat at brain-dump.org>
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 #include <string.h>
17 #include <stdlib.h>
18 #include <wchar.h>
19 #include <ctype.h>
20 #include <errno.h>
21 #include <regex.h>
22 #include "vis.h"
23 #include "view.h"
24 #include "syntax.h"
25 #include "text.h"
26 #include "text-motions.h"
27 #include "text-util.h"
28 #include "util.h"
30 struct Selection {
31 Mark anchor; /* position where the selection was created */
32 Mark cursor; /* other selection endpoint where it changes */
33 View *view; /* associated view to which this selection belongs */
34 Selection *prev, *next; /* previsous/next selections in no particular order */
37 struct Cursor { /* cursor position */
38 Filepos pos; /* in bytes from the start of the file */
39 int row, col; /* in terms of zero based screen coordinates */
40 int lastcol; /* remembered column used when moving across lines */
41 Line *line; /* screen line on which cursor currently resides */
42 Mark mark; /* mark used to keep track of current cursor position */
43 Selection *sel; /* selection (if any) which folows the cursor upon movement */
44 Mark lastsel_anchor;/* previously used selection data, */
45 Mark lastsel_cursor;/* used to restore it */
46 Register reg; /* per cursor register to support yank/put operation */
47 View *view; /* associated view to which this cursor belongs */
48 Cursor *prev, *next;/* previous/next cursors in no particular order */
51 /* Viewable area, showing part of a file. Keeps track of cursors and selections.
52 * At all times there exists at least one cursor, which is placed in the visible viewport.
53 * Additional cursors can be created and positioned anywhere in the file. */
54 struct View {
55 Text *text; /* underlying text management */
56 UiWin *ui;
57 ViewEvent *events;
58 int width, height; /* size of display area */
59 Filepos start, end; /* currently displayed area [start, end] in bytes from the start of the file */
60 Filepos start_last; /* previously used start of visible area, used to update the mark */
61 Mark start_mark; /* mark to keep track of the start of the visible area */
62 size_t lines_size; /* number of allocated bytes for lines (grows only) */
63 Line *lines; /* view->height number of lines representing view content */
64 Line *topline; /* top of the view, first line currently shown */
65 Line *lastline; /* last currently used line, always <= bottomline */
66 Line *bottomline; /* bottom of view, might be unused if lastline < bottomline */
67 Cursor *cursor; /* main cursor, always placed within the visible viewport */
68 Line *line; /* used while drawing view content, line where next char will be drawn */
69 int col; /* used while drawing view content, column where next char will be drawn */
70 Syntax *syntax; /* syntax highlighting definitions for this view or NULL */
71 const SyntaxSymbol *symbols[SYNTAX_SYMBOL_LAST]; /* symbols to use for white spaces etc */
72 int tabwidth; /* how many spaces should be used to display a tab character */
73 Cursor *cursors; /* all cursors currently active */
74 Selection *selections; /* all selected regions */
77 static const SyntaxSymbol symbols_none[] = {
78 [SYNTAX_SYMBOL_SPACE] = { " " },
79 [SYNTAX_SYMBOL_TAB] = { " " },
80 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
81 [SYNTAX_SYMBOL_EOL] = { " " },
82 [SYNTAX_SYMBOL_EOF] = { "~" },
85 static const SyntaxSymbol symbols_default[] = {
86 [SYNTAX_SYMBOL_SPACE] = { "\xC2\xB7" },
87 [SYNTAX_SYMBOL_TAB] = { "\xE2\x96\xB6" },
88 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
89 [SYNTAX_SYMBOL_EOL] = { "\xE2\x8F\x8E" },
90 [SYNTAX_SYMBOL_EOF] = { "~" },
93 static Cell cell_unused;
94 static Cell cell_blank = { .data = " " };
96 static void view_clear(View *view);
97 static bool view_addch(View *view, Cell *cell);
98 static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol);
99 static void view_cursors_free(Cursor *c);
100 /* set/move current cursor position to a given (line, column) pair */
101 static size_t cursor_set(Cursor *cursor, Line *line, int col);
103 void view_tabwidth_set(View *view, int tabwidth) {
104 view->tabwidth = tabwidth;
105 view_draw(view);
108 /* reset internal view data structures (cell matrix, line offsets etc.) */
109 static void view_clear(View *view) {
110 if (view->start != view->start_last) {
111 view->start_mark = text_mark_set(view->text, view->start);
112 view->start_last = view->start;
113 } else {
114 size_t start = text_mark_get(view->text, view->start_mark);
115 if (start != EPOS)
116 view->start = start;
118 view->topline = view->lines;
119 view->topline->lineno = text_lineno_by_pos(view->text, view->start);
120 view->lastline = view->topline;
122 /* reset all other lines */
123 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
124 size_t end = view->height * line_size;
125 Line *prev = NULL;
126 for (size_t i = 0; i < end; i += line_size) {
127 Line *line = (Line*)(((char*)view->lines) + i);
128 line->width = 0;
129 line->len = 0;
130 line->prev = prev;
131 if (prev)
132 prev->next = line;
133 prev = line;
135 view->bottomline = prev ? prev : view->topline;
136 view->bottomline->next = NULL;
137 view->line = view->topline;
138 view->col = 0;
141 Filerange view_viewport_get(View *view) {
142 return (Filerange){ .start = view->start, .end = view->end };
145 /* try to add another character to the view, return whether there was space left */
146 static bool view_addch(View *view, Cell *cell) {
147 if (!view->line)
148 return false;
150 int width;
151 size_t lineno = view->line->lineno;
153 switch (cell->data[0]) {
154 case '\t':
155 cell->istab = true;
156 cell->width = 1;
157 width = view->tabwidth - (view->col % view->tabwidth);
158 for (int w = 0; w < width; w++) {
159 if (view->col + 1 > view->width) {
160 view->line = view->line->next;
161 view->col = 0;
162 if (!view->line)
163 return false;
164 view->line->lineno = lineno;
167 cell->len = w == 0 ? 1 : 0;
168 int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
169 strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
170 cell->attr = view->symbols[t]->style;
171 view->line->cells[view->col] = *cell;
172 view->line->len += cell->len;
173 view->line->width += cell->width;
174 view->col++;
176 cell->len = 1;
177 return true;
178 case '\n':
179 cell->width = 1;
180 if (view->col + cell->width > view->width) {
181 view->line = view->line->next;
182 view->col = 0;
183 if (!view->line)
184 return false;
185 view->line->lineno = lineno;
188 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
189 cell->attr = view->symbols[SYNTAX_SYMBOL_EOL]->style;
191 view->line->cells[view->col] = *cell;
192 view->line->len += cell->len;
193 view->line->width += cell->width;
194 for (int i = view->col + 1; i < view->width; i++)
195 view->line->cells[i] = cell_blank;
197 view->line = view->line->next;
198 if (view->line)
199 view->line->lineno = lineno + 1;
200 view->col = 0;
201 return true;
202 default:
203 if ((unsigned char)cell->data[0] < 128 && !isprint((unsigned char)cell->data[0])) {
204 /* non-printable ascii char, represent it as ^(char + 64) */
205 *cell = (Cell) {
206 .data = { '^', cell->data[0] + 64, '\0' },
207 .len = 1,
208 .width = 2,
209 .istab = false,
210 .attr = cell->attr,
214 if (cell->data[0] == ' ') {
215 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
216 cell->attr = view->symbols[SYNTAX_SYMBOL_SPACE]->style;
220 if (view->col + cell->width > view->width) {
221 for (int i = view->col; i < view->width; i++)
222 view->line->cells[i] = cell_blank;
223 view->line = view->line->next;
224 view->col = 0;
227 if (view->line) {
228 view->line->width += cell->width;
229 view->line->len += cell->len;
230 view->line->lineno = lineno;
231 view->line->cells[view->col] = *cell;
232 view->col++;
233 /* set cells of a character which uses multiple columns */
234 for (int i = 1; i < cell->width; i++)
235 view->line->cells[view->col++] = cell_unused;
236 return true;
238 return false;
242 CursorPos view_cursor_getpos(View *view) {
243 Cursor *cursor = view->cursor;
244 Line *line = cursor->line;
245 CursorPos pos = { .line = line->lineno, .col = cursor->col };
246 while (line->prev && line->prev->lineno == pos.line) {
247 line = line->prev;
248 pos.col += line->width;
250 pos.col++;
251 return pos;
254 static void cursor_to(Cursor *c, size_t pos) {
255 Text *txt = c->view->text;
256 c->mark = text_mark_set(txt, pos);
257 if (pos != c->pos)
258 c->lastcol = 0;
259 c->pos = pos;
260 if (c->sel) {
261 size_t anchor = text_mark_get(txt, c->sel->anchor);
262 size_t cursor = text_mark_get(txt, c->sel->cursor);
263 /* do we have to change the orientation of the selection? */
264 if (pos < anchor && anchor < cursor) {
265 /* right extend -> left extend */
266 anchor = text_char_next(txt, anchor);
267 c->sel->anchor = text_mark_set(txt, anchor);
268 } else if (cursor < anchor && anchor <= pos) {
269 /* left extend -> right extend */
270 anchor = text_char_prev(txt, anchor);
271 c->sel->anchor = text_mark_set(txt, anchor);
273 if (anchor <= pos)
274 pos = text_char_next(txt, pos);
275 c->sel->cursor = text_mark_set(txt, pos);
277 if (!view_coord_get(c->view, pos, &c->line, &c->row, &c->col)) {
278 if (c->view->cursor == c) {
279 c->line = c->view->topline;
280 c->row = 0;
281 c->col = 0;
283 return;
285 // TODO: minimize number of redraws
286 view_draw(c->view);
289 static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
290 int row = 0, col = 0;
291 size_t cur = view->start;
292 Line *line = view->topline;
294 if (pos < view->start || pos > view->end) {
295 if (retline) *retline = NULL;
296 if (retrow) *retrow = -1;
297 if (retcol) *retcol = -1;
298 return false;
301 while (line && line != view->lastline && cur < pos) {
302 if (cur + line->len > pos)
303 break;
304 cur += line->len;
305 line = line->next;
306 row++;
309 if (line) {
310 int max_col = MIN(view->width, line->width);
311 while (cur < pos && col < max_col) {
312 cur += line->cells[col].len;
313 /* skip over columns occupied by the same character */
314 while (++col < max_col && line->cells[col].len == 0);
316 } else {
317 line = view->bottomline;
318 row = view->height - 1;
321 if (retline) *retline = line;
322 if (retrow) *retrow = row;
323 if (retcol) *retcol = col;
324 return true;
327 /* move the cursor to the character at pos bytes from the begining of the file.
328 * if pos is not in the current viewport, redraw the view to make it visible */
329 void view_cursor_to(View *view, size_t pos) {
330 view_cursors_to(view->cursor, pos);
333 /* redraw the complete with data starting from view->start bytes into the file.
334 * stop once the screen is full, update view->end, view->lastline */
335 void view_draw(View *view) {
336 view_clear(view);
337 /* current absolute file position */
338 size_t pos = view->start;
339 /* number of bytes to read in one go */
340 size_t text_len = view->width * view->height;
341 /* current buffer to work with */
342 char text[text_len+1];
343 /* remaining bytes to process in buffer*/
344 size_t rem = text_bytes_get(view->text, pos, text_len, text);
345 /* NUL terminate because regex(3) function expect it */
346 text[rem] = '\0';
347 /* current position into buffer from which to interpret a character */
348 char *cur = text;
349 /* syntax definition to use */
350 Syntax *syntax = view->syntax;
351 /* matched tokens for each syntax rule */
352 regmatch_t match[syntax ? LENGTH(syntax->rules) : 1][1], *matched = NULL;
353 memset(match, 0, sizeof match);
354 /* default and current curses attributes to use */
355 int default_attrs = 0, attrs = default_attrs;
356 /* start from known multibyte state */
357 mbstate_t mbstate = { 0 };
359 while (rem > 0) {
361 /* current 'parsed' character' */
362 wchar_t wchar;
363 Cell cell;
364 memset(&cell, 0, sizeof cell);
366 if (syntax) {
367 if (matched && cur >= text + matched->rm_eo) {
368 /* end of current match */
369 matched = NULL;
370 attrs = default_attrs;
371 for (int i = 0; i < LENGTH(syntax->rules); i++) {
372 if (match[i][0].rm_so == -1)
373 continue; /* no match on whole text */
374 /* reset matches which overlap with matched */
375 if (text + match[i][0].rm_so <= cur && cur < text + match[i][0].rm_eo) {
376 match[i][0].rm_so = 0;
377 match[i][0].rm_eo = 0;
382 if (!matched) {
383 size_t off = cur - text; /* number of already processed bytes */
384 for (int i = 0; i < LENGTH(syntax->rules); i++) {
385 SyntaxRule *rule = &syntax->rules[i];
386 if (!rule->rule)
387 break;
388 if (match[i][0].rm_so == -1)
389 continue; /* no match on whole text */
390 if (off >= (size_t)match[i][0].rm_eo) {
391 /* past match, continue search from current position */
392 if (regexec(&rule->regex, cur, 1, match[i], 0) ||
393 match[i][0].rm_so == match[i][0].rm_eo) {
394 match[i][0].rm_so = -1;
395 match[i][0].rm_eo = -1;
396 continue;
398 match[i][0].rm_so += off;
399 match[i][0].rm_eo += off;
402 if (text + match[i][0].rm_so <= cur && cur < text + match[i][0].rm_eo) {
403 /* within matched expression */
404 matched = &match[i][0];
405 attrs = rule->style;
406 break; /* first match views */
412 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
413 if (len == (size_t)-1 && errno == EILSEQ) {
414 /* ok, we encountered an invalid multibyte sequence,
415 * replace it with the Unicode Replacement Character
416 * (FFFD) and skip until the start of the next utf8 char */
417 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
418 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1, .istab = false };
419 } else if (len == (size_t)-2) {
420 /* not enough bytes available to convert to a
421 * wide character. advance file position and read
422 * another junk into buffer.
424 rem = text_bytes_get(view->text, pos, text_len, text);
425 text[rem] = '\0';
426 cur = text;
427 continue;
428 } else if (len == 0) {
429 /* NUL byte encountered, store it and continue */
430 cell = (Cell){ .data = "\x00", .len = 1, .width = 0, .istab = false };
431 } else {
432 for (size_t i = 0; i < len; i++)
433 cell.data[i] = cur[i];
434 cell.data[len] = '\0';
435 cell.istab = false;
436 cell.len = len;
437 cell.width = wcwidth(wchar);
438 if (cell.width == -1)
439 cell.width = 1;
442 if (cur[0] == '\r' && rem > 1 && cur[1] == '\n') {
443 /* convert views style newline \r\n into a single char with len = 2 */
444 cell = (Cell){ .data = "\n", .len = 2, .width = 1, .istab = false };
447 cell.attr = attrs;
448 if (!view_addch(view, &cell))
449 break;
451 rem -= cell.len;
452 cur += cell.len;
453 pos += cell.len;
456 /* set end of vieviewg region */
457 view->end = pos;
458 view->lastline = view->line ? view->line : view->bottomline;
459 if (view->line) {
460 for (int x = view->col; x < view->width; x++)
461 view->line->cells[x] = cell_blank;
464 for (Line *l = view->lastline->next; l; l = l->next) {
465 strncpy(l->cells[0].data, view->symbols[SYNTAX_SYMBOL_EOF]->symbol, sizeof(l->cells[0].data));
466 l->cells[0].attr = view->symbols[SYNTAX_SYMBOL_EOF]->style;
467 for (int x = 1; x < view->width; x++)
468 l->cells[x] = cell_blank;
469 l->width = 1;
470 l->len = 0;
473 for (Selection *s = view->selections; s; s = s->next) {
474 Filerange sel = view_selections_get(s);
475 if (text_range_valid(&sel)) {
476 Line *start_line; int start_col;
477 Line *end_line; int end_col;
478 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
479 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
480 if (start_line || end_line) {
481 if (!start_line) {
482 start_line = view->topline;
483 start_col = 0;
485 if (!end_line) {
486 end_line = view->lastline;
487 end_col = end_line->width;
489 for (Line *l = start_line; l != end_line->next; l = l->next) {
490 int col = (l == start_line) ? start_col : 0;
491 int end = (l == end_line) ? end_col : l->width;
492 while (col < end) {
493 l->cells[col++].selected = true;
498 if (view->events && view->events->selection)
499 view->events->selection(view->events->data, &sel);
503 for (Cursor *c = view->cursors; c; c = c->next) {
504 size_t pos = view_cursors_pos(c);
505 if (view_coord_get(view, pos, &c->line, &c->row, &c->col)) {
506 c->line->cells[c->col].cursor = true;
507 if (view->ui && view->syntax) {
508 Line *line_match; int col_match;
509 size_t pos_match = text_bracket_match_except(view->text, pos, "<>");
510 if (pos != pos_match && view_coord_get(view, pos_match, &line_match, NULL, &col_match)) {
511 line_match->cells[col_match].selected = true;
514 } else if (c == view->cursor) {
515 c->line = view->topline;
516 c->row = 0;
517 c->col = 0;
521 if (view->ui)
522 view->ui->draw(view->ui);
525 bool view_resize(View *view, int width, int height) {
526 if (width <= 0)
527 width = 1;
528 if (height <= 0)
529 height = 1;
530 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
531 if (lines_size > view->lines_size) {
532 Line *lines = realloc(view->lines, lines_size);
533 if (!lines)
534 return false;
535 view->lines = lines;
536 view->lines_size = lines_size;
538 view->width = width;
539 view->height = height;
540 if (view->lines)
541 memset(view->lines, 0, view->lines_size);
542 view_draw(view);
543 return true;
546 int view_height_get(View *view) {
547 return view->height;
550 int view_width_get(View *view) {
551 return view->width;
554 void view_free(View *view) {
555 if (!view)
556 return;
557 while (view->cursors)
558 view_cursors_free(view->cursors);
559 while (view->selections)
560 view_selections_free(view->selections);
561 free(view->lines);
562 free(view);
565 void view_reload(View *view, Text *text) {
566 view->text = text;
567 view_selections_clear(view);
568 view_cursor_to(view, 0);
571 View *view_new(Text *text, ViewEvent *events) {
572 if (!text)
573 return NULL;
574 View *view = calloc(1, sizeof(View));
575 if (!view)
576 return NULL;
577 if (!view_cursors_new(view)) {
578 view_free(view);
579 return NULL;
582 view->text = text;
583 view->events = events;
584 view->tabwidth = 8;
585 view_options_set(view, 0);
587 if (!view_resize(view, 1, 1)) {
588 view_free(view);
589 return NULL;
592 view_cursor_to(view, 0);
594 return view;
597 void view_ui(View *view, UiWin* ui) {
598 view->ui = ui;
601 static size_t cursor_set(Cursor *cursor, Line *line, int col) {
602 int row = 0;
603 View *view = cursor->view;
604 size_t pos = view->start;
605 /* get row number and file offset at start of the given line */
606 for (Line *cur = view->topline; cur && cur != line; cur = cur->next) {
607 pos += cur->len;
608 row++;
611 /* for characters which use more than 1 column, make sure we are on the left most */
612 while (col > 0 && line->cells[col].len == 0)
613 col--;
614 while (col < line->width && line->cells[col].istab)
615 col++;
617 /* calculate offset within the line */
618 for (int i = 0; i < col; i++)
619 pos += line->cells[i].len;
621 cursor->col = col;
622 cursor->row = row;
623 cursor->line = line;
625 cursor_to(cursor, pos);
627 return pos;
630 bool view_viewport_down(View *view, int n) {
631 Line *line;
632 if (view->end == text_size(view->text))
633 return false;
634 if (n >= view->height) {
635 view->start = view->end;
636 } else {
637 for (line = view->topline; line && n > 0; line = line->next, n--)
638 view->start += line->len;
640 view_draw(view);
641 return true;
644 bool view_viewport_up(View *view, int n) {
645 /* scrolling up is somewhat tricky because we do not yet know where
646 * the lines start, therefore scan backwards but stop at a reasonable
647 * maximum in case we are dealing with a file without any newlines
649 if (view->start == 0)
650 return false;
651 size_t max = view->width * view->height;
652 char c;
653 Iterator it = text_iterator_get(view->text, view->start - 1);
655 if (!text_iterator_byte_get(&it, &c))
656 return false;
657 size_t off = 0;
658 /* skip newlines immediately before display area */
659 if (c == '\n' && text_iterator_byte_prev(&it, &c))
660 off++;
661 if (c == '\r' && text_iterator_byte_prev(&it, &c))
662 off++;
663 do {
664 if (c == '\n' && --n == 0)
665 break;
666 if (++off > max)
667 break;
668 } while (text_iterator_byte_prev(&it, &c));
669 if (c == '\r')
670 off++;
671 view->start -= off;
672 view_draw(view);
673 return true;
676 void view_redraw_top(View *view) {
677 Line *line = view->cursor->line;
678 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
679 view->start += cur->len;
680 view_draw(view);
681 view_cursor_to(view, view->cursor->pos);
684 void view_redraw_center(View *view) {
685 int center = view->height / 2;
686 size_t pos = view->cursor->pos;
687 for (int i = 0; i < 2; i++) {
688 int linenr = 0;
689 Line *line = view->cursor->line;
690 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
691 linenr++;
692 if (linenr < center) {
693 view_slide_down(view, center - linenr);
694 continue;
696 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
697 view->start += cur->len;
698 linenr--;
700 break;
702 view_draw(view);
703 view_cursor_to(view, pos);
706 void view_redraw_bottom(View *view) {
707 Line *line = view->cursor->line;
708 if (line == view->lastline)
709 return;
710 size_t pos = view->cursor->pos;
711 view_viewport_up(view, view->height);
712 while (pos > view->end && view_viewport_down(view, 1));
713 view_cursor_to(view, pos);
716 size_t view_slide_up(View *view, int lines) {
717 Cursor *cursor = view->cursor;
718 if (view_viewport_down(view, lines)) {
719 if (cursor->line == view->topline)
720 cursor_set(cursor, view->topline, cursor->col);
721 else
722 view_cursor_to(view, cursor->pos);
723 } else {
724 view_screenline_down(cursor);
726 return cursor->pos;
729 size_t view_slide_down(View *view, int lines) {
730 Cursor *cursor = view->cursor;
731 if (view_viewport_up(view, lines)) {
732 if (cursor->line == view->lastline)
733 cursor_set(cursor, view->lastline, cursor->col);
734 else
735 view_cursor_to(view, cursor->pos);
736 } else {
737 view_screenline_up(cursor);
739 return cursor->pos;
742 size_t view_scroll_up(View *view, int lines) {
743 Cursor *cursor = view->cursor;
744 if (view_viewport_up(view, lines)) {
745 Line *line = cursor->line < view->lastline ? cursor->line : view->lastline;
746 cursor_set(cursor, line, view->cursor->col);
747 } else {
748 view_cursor_to(view, 0);
750 return cursor->pos;
753 size_t view_scroll_down(View *view, int lines) {
754 Cursor *cursor = view->cursor;
755 if (view_viewport_down(view, lines)) {
756 Line *line = cursor->line > view->topline ? cursor->line : view->topline;
757 cursor_set(cursor, line, cursor->col);
758 } else {
759 view_cursor_to(view, text_size(view->text));
761 return cursor->pos;
764 size_t view_line_up(Cursor *cursor) {
765 if (cursor->line && cursor->line->prev && cursor->line->prev->prev &&
766 cursor->line->lineno != cursor->line->prev->lineno &&
767 cursor->line->prev->lineno != cursor->line->prev->prev->lineno)
768 return view_screenline_up(cursor);
769 size_t pos = text_line_up(cursor->view->text, cursor->pos);
770 view_cursors_to(cursor, pos);
771 return pos;
774 size_t view_line_down(Cursor *cursor) {
775 if (cursor->line && (!cursor->line->next || cursor->line->next->lineno != cursor->line->lineno))
776 return view_screenline_down(cursor);
777 size_t pos = text_line_down(cursor->view->text, cursor->pos);
778 view_cursors_to(cursor, pos);
779 return pos;
782 size_t view_screenline_up(Cursor *cursor) {
783 int lastcol = cursor->lastcol;
784 if (!lastcol)
785 lastcol = cursor->col;
786 if (!cursor->line->prev)
787 view_scroll_up(cursor->view, 1);
788 if (cursor->line->prev)
789 cursor_set(cursor, cursor->line->prev, lastcol);
790 cursor->lastcol = lastcol;
791 return cursor->pos;
794 size_t view_screenline_down(Cursor *cursor) {
795 int lastcol = cursor->lastcol;
796 if (!lastcol)
797 lastcol = cursor->col;
798 if (!cursor->line->next && cursor->line == cursor->view->bottomline)
799 view_scroll_down(cursor->view, 1);
800 if (cursor->line->next)
801 cursor_set(cursor, cursor->line->next, lastcol);
802 cursor->lastcol = lastcol;
803 return cursor->pos;
806 size_t view_screenline_begin(Cursor *cursor) {
807 if (!cursor->line)
808 return cursor->pos;
809 return cursor_set(cursor, cursor->line, 0);
812 size_t view_screenline_middle(Cursor *cursor) {
813 if (!cursor->line)
814 return cursor->pos;
815 return cursor_set(cursor, cursor->line, cursor->line->width / 2);
818 size_t view_screenline_end(Cursor *cursor) {
819 if (!cursor->line)
820 return cursor->pos;
821 int col = cursor->line->width - 1;
822 return cursor_set(cursor, cursor->line, col >= 0 ? col : 0);
825 size_t view_cursor_get(View *view) {
826 return view_cursors_pos(view->cursor);
829 const Line *view_lines_get(View *view) {
830 return view->topline;
833 void view_scroll_to(View *view, size_t pos) {
834 view_cursors_scroll_to(view->cursor, pos);
837 void view_syntax_set(View *view, Syntax *syntax) {
838 view->syntax = syntax;
839 for (int i = 0; i < LENGTH(view->symbols); i++) {
840 if (syntax && syntax->symbols[i].symbol)
841 view->symbols[i] = &syntax->symbols[i];
842 else
843 view->symbols[i] = &symbols_none[i];
845 if (syntax) {
846 for (const char **style = syntax->styles; *style; style++) {
847 view->ui->syntax_style(view->ui, style - syntax->styles, *style);
852 Syntax *view_syntax_get(View *view) {
853 return view->syntax;
856 void view_options_set(View *view, enum UiOption options) {
857 int mapping[] = {
858 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
859 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
860 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
861 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
862 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
864 for (int i = 0; i < LENGTH(mapping); i++) {
865 if (options & mapping[i]) {
866 if (view->syntax && view->syntax->symbols[i].symbol)
867 view->symbols[i] = &view->syntax->symbols[i];
868 else
869 view->symbols[i] = &symbols_default[i];
870 } else {
871 view->symbols[i] = &symbols_none[i];
874 if (view->ui)
875 view->ui->options_set(view->ui, options);
878 enum UiOption view_options_get(View *view) {
879 return view->ui ? view->ui->options_get(view->ui) : 0;
882 size_t view_screenline_goto(View *view, int n) {
883 size_t pos = view->start;
884 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
885 pos += line->len;
886 return pos;
889 Cursor *view_cursors_new(View *view) {
890 Cursor *c = calloc(1, sizeof(*c));
891 if (!c)
892 return NULL;
894 c->view = view;
895 c->next = view->cursors;
896 if (view->cursors)
897 view->cursors->prev = c;
898 view->cursors = c;
899 view->cursor = c;
900 return c;
903 int view_cursors_count(View *view) {
904 int i = 0;
905 for (Cursor *c = view_cursors(view); c; c = view_cursors_next(c))
906 i++;
907 return i;
910 static void view_cursors_free(Cursor *c) {
911 if (!c)
912 return;
913 register_release(&c->reg);
914 if (c->prev)
915 c->prev->next = c->next;
916 if (c->next)
917 c->next->prev = c->prev;
918 if (c->view->cursors == c)
919 c->view->cursors = c->next;
920 if (c->view->cursor == c)
921 c->view->cursor = c->next ? c->next : c->prev;
922 free(c);
925 void view_cursors_dispose(Cursor *c) {
926 if (!c)
927 return;
928 View *view = c->view;
929 if (view->cursors && view->cursors->next) {
930 view_selections_free(c->sel);
931 view_cursors_free(c);
932 view_draw(view);
936 Cursor *view_cursors(View *view) {
937 return view->cursors;
940 Cursor *view_cursor(View *view) {
941 return view->cursor;
944 Cursor *view_cursors_prev(Cursor *c) {
945 return c->prev;
948 Cursor *view_cursors_next(Cursor *c) {
949 return c->next;
952 size_t view_cursors_pos(Cursor *c) {
953 return text_mark_get(c->view->text, c->mark);
956 Register *view_cursors_register(Cursor *c) {
957 return &c->reg;
960 void view_cursors_scroll_to(Cursor *c, size_t pos) {
961 View *view = c->view;
962 if (view->cursor == c) {
963 while (pos < view->start && view_viewport_up(view, 1));
964 while (pos > view->end && view_viewport_down(view, 1));
966 view_cursors_to(c, pos);
969 void view_cursors_to(Cursor *c, size_t pos) {
970 View *view = c->view;
971 if (c->view->cursors == c) {
972 c->mark = text_mark_set(view->text, pos);
974 size_t max = text_size(view->text);
975 if (pos == max && view->end != max) {
976 /* do not display an empty screen when shoviewg the end of the file */
977 view->start = pos;
978 view_viewport_up(view, view->height / 2);
979 } else {
980 /* set the start of the viewable region to the start of the line on which
981 * the cursor should be placed. if this line requires more space than
982 * available in the view then simply start displaying text at the new
983 * cursor position */
984 for (int i = 0; i < 2 && (pos < view->start || pos > view->end); i++) {
985 view->start = i == 0 ? text_line_begin(view->text, pos) : pos;
986 view_draw(view);
991 cursor_to(c, pos);
994 void view_cursors_selection_start(Cursor *c) {
995 if (c->sel)
996 return;
997 size_t pos = view_cursors_pos(c);
998 if (pos == EPOS || !(c->sel = view_selections_new(c->view)))
999 return;
1000 Text *txt = c->view->text;
1001 c->sel->anchor = text_mark_set(txt, pos);
1002 c->sel->cursor = text_mark_set(txt, text_char_next(txt, pos));
1003 view_draw(c->view);
1006 void view_cursors_selection_restore(Cursor *c) {
1007 Text *txt = c->view->text;
1008 if (c->sel)
1009 return;
1010 Filerange sel = text_range_new(
1011 text_mark_get(txt, c->lastsel_anchor),
1012 text_mark_get(txt, c->lastsel_cursor)
1014 if (!text_range_valid(&sel))
1015 return;
1016 if (!(c->sel = view_selections_new(c->view)))
1017 return;
1018 view_selections_set(c->sel, &sel);
1019 view_cursors_selection_sync(c);
1020 view_draw(c->view);
1023 void view_cursors_selection_stop(Cursor *c) {
1024 c->sel = NULL;
1027 void view_cursors_selection_clear(Cursor *c) {
1028 view_selections_free(c->sel);
1029 view_draw(c->view);
1032 void view_cursors_selection_swap(Cursor *c) {
1033 if (!c->sel)
1034 return;
1035 view_selections_swap(c->sel);
1036 view_cursors_selection_sync(c);
1039 void view_cursors_selection_sync(Cursor *c) {
1040 if (!c->sel)
1041 return;
1042 Text *txt = c->view->text;
1043 size_t anchor = text_mark_get(txt, c->sel->anchor);
1044 size_t cursor = text_mark_get(txt, c->sel->cursor);
1045 bool right_extending = anchor < cursor;
1046 if (right_extending)
1047 cursor = text_char_prev(txt, cursor);
1048 view_cursors_to(c, cursor);
1051 Filerange view_cursors_selection_get(Cursor *c) {
1052 return view_selections_get(c->sel);
1055 void view_cursors_selection_set(Cursor *c, Filerange *r) {
1056 if (!text_range_valid(r))
1057 return;
1058 if (!c->sel)
1059 c->sel = view_selections_new(c->view);
1060 if (!c->sel)
1061 return;
1063 view_selections_set(c->sel, r);
1066 Selection *view_selections_new(View *view) {
1067 Selection *s = calloc(1, sizeof(*s));
1068 if (!s)
1069 return NULL;
1071 s->view = view;
1072 s->next = view->selections;
1073 if (view->selections)
1074 view->selections->prev = s;
1075 view->selections = s;
1076 return s;
1079 void view_selections_free(Selection *s) {
1080 if (!s)
1081 return;
1082 if (s->prev)
1083 s->prev->next = s->next;
1084 if (s->next)
1085 s->next->prev = s->prev;
1086 if (s->view->selections == s)
1087 s->view->selections = s->next;
1088 // XXX: add backlink Selection->Cursor?
1089 for (Cursor *c = s->view->cursors; c; c = c->next) {
1090 if (c->sel == s) {
1091 c->lastsel_anchor = s->anchor;
1092 c->lastsel_cursor = s->cursor;
1093 c->sel = NULL;
1096 free(s);
1099 void view_selections_clear(View *view) {
1100 while (view->selections)
1101 view_selections_free(view->selections);
1102 view_draw(view);
1105 void view_cursors_clear(View *view) {
1106 for (Cursor *c = view->cursors, *next; c; c = next) {
1107 next = c->next;
1108 if (c != view->cursor) {
1109 view_selections_free(c->sel);
1110 view_cursors_free(c);
1113 view_draw(view);
1116 void view_selections_swap(Selection *s) {
1117 Mark temp = s->anchor;
1118 s->anchor = s->cursor;
1119 s->cursor = temp;
1122 Selection *view_selections(View *view) {
1123 return view->selections;
1126 Selection *view_selections_prev(Selection *s) {
1127 return s->prev;
1130 Selection *view_selections_next(Selection *s) {
1131 return s->next;
1134 Filerange view_selections_get(Selection *s) {
1135 if (!s)
1136 return text_range_empty();
1137 Text *txt = s->view->text;
1138 size_t anchor = text_mark_get(txt, s->anchor);
1139 size_t cursor = text_mark_get(txt, s->cursor);
1140 return text_range_new(anchor, cursor);
1143 void view_selections_set(Selection *s, Filerange *r) {
1144 if (!text_range_valid(r))
1145 return;
1146 Text *txt = s->view->text;
1147 size_t anchor = text_mark_get(txt, s->anchor);
1148 size_t cursor = text_mark_get(txt, s->cursor);
1149 bool left_extending = anchor > cursor;
1150 if (left_extending) {
1151 s->anchor = text_mark_set(txt, r->end);
1152 s->cursor = text_mark_set(txt, r->start);
1153 } else {
1154 s->anchor = text_mark_set(txt, r->start);
1155 s->cursor = text_mark_set(txt, r->end);
1157 view_draw(s->view);