build: skip -pie for static build
[vis.git] / view.c
blob06f2365536e9de1b6815afdd7b4d786586750639
1 #include <string.h>
2 #include <stdlib.h>
3 #include <wchar.h>
4 #include <ctype.h>
5 #include <errno.h>
6 #include <regex.h>
7 #include <limits.h>
8 #include "vis-lua.h"
9 #include "view.h"
10 #include "text.h"
11 #include "text-motions.h"
12 #include "text-util.h"
13 #include "util.h"
15 typedef struct {
16 char *symbol;
17 int style;
18 } SyntaxSymbol;
20 enum {
21 SYNTAX_SYMBOL_SPACE,
22 SYNTAX_SYMBOL_TAB,
23 SYNTAX_SYMBOL_TAB_FILL,
24 SYNTAX_SYMBOL_EOL,
25 SYNTAX_SYMBOL_EOF,
26 SYNTAX_SYMBOL_LAST,
29 /* A selection is made up of two marks named cursor and anchor.
30 * While the anchor remains fixed the cursor mark follows cursor motions.
31 * For a selection (indicated by []), the marks (^) are placed as follows:
33 * [some text] [!]
34 * ^ ^ ^
35 * ^
37 * That is the marks point to the *start* of the first and last character
38 * of the selection. In particular for a single character selection (as
39 * depicted on the right above) both marks point to the same location.
41 * The view_selections_{get,set} functions take care of adding/removing
42 * the necessary offset for the last character.
44 struct Selection {
45 Mark anchor; /* position where the selection was created */
46 Mark cursor; /* other selection endpoint where it changes */
47 View *view; /* associated view to which this selection belongs */
48 Selection *prev, *next; /* previsous/next selections in no particular order */
51 struct Cursor { /* cursor position */
52 Filepos pos; /* in bytes from the start of the file */
53 int row, col; /* in terms of zero based screen coordinates */
54 int lastcol; /* remembered column used when moving across lines */
55 Line *line; /* screen line on which cursor currently resides */
56 Mark mark; /* mark used to keep track of current cursor position */
57 Selection *sel; /* selection (if any) which folows the cursor upon movement */
58 Mark lastsel_anchor;/* previously used selection data, */
59 Mark lastsel_cursor;/* used to restore it */
60 Register reg; /* per cursor register to support yank/put operation */
61 int generation; /* used to filter out newly created cursors during iteration */
62 int number; /* how many cursors are located before this one */
63 View *view; /* associated view to which this cursor belongs */
64 Cursor *prev, *next;/* previous/next cursors ordered by location at creation time */
67 /* Viewable area, showing part of a file. Keeps track of cursors and selections.
68 * At all times there exists at least one cursor, which is placed in the visible viewport.
69 * Additional cursors can be created and positioned anywhere in the file. */
70 struct View {
71 Text *text; /* underlying text management */
72 UiWin *ui;
73 int width, height; /* size of display area */
74 Filepos start, end; /* currently displayed area [start, end] in bytes from the start of the file */
75 Filepos start_last; /* previously used start of visible area, used to update the mark */
76 Mark start_mark; /* mark to keep track of the start of the visible area */
77 size_t lines_size; /* number of allocated bytes for lines (grows only) */
78 Line *lines; /* view->height number of lines representing view content */
79 Line *topline; /* top of the view, first line currently shown */
80 Line *lastline; /* last currently used line, always <= bottomline */
81 Line *bottomline; /* bottom of view, might be unused if lastline < bottomline */
82 Cursor *cursor; /* main cursor, always placed within the visible viewport */
83 int cursor_count; /* how many cursors do currently exist */
84 Line *line; /* used while drawing view content, line where next char will be drawn */
85 int col; /* used while drawing view content, column where next char will be drawn */
86 const SyntaxSymbol *symbols[SYNTAX_SYMBOL_LAST]; /* symbols to use for white spaces etc */
87 int tabwidth; /* how many spaces should be used to display a tab character */
88 Cursor *cursors; /* all cursors currently active */
89 Selection *selections; /* all selected regions */
90 lua_State *lua; /* lua state used for syntax highlighting */
91 int cursor_generation; /* used to filter out newly created cursors during iteration */
92 char *lexer_name;
93 size_t horizon; /* maximal number of bytes to consider for syntax highlighting
94 * before the visible area */
95 bool need_update; /* whether view has been redrawn */
96 bool large_file; /* optimize for displaying large files */
97 int colorcolumn;
100 static const SyntaxSymbol symbols_none[] = {
101 [SYNTAX_SYMBOL_SPACE] = { " " },
102 [SYNTAX_SYMBOL_TAB] = { " " },
103 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
104 [SYNTAX_SYMBOL_EOL] = { " " },
105 [SYNTAX_SYMBOL_EOF] = { "~" },
108 static const SyntaxSymbol symbols_default[] = {
109 [SYNTAX_SYMBOL_SPACE] = { "\xC2\xB7" },
110 [SYNTAX_SYMBOL_TAB] = { "\xE2\x96\xB6" },
111 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
112 [SYNTAX_SYMBOL_EOL] = { "\xE2\x8F\x8E" },
113 [SYNTAX_SYMBOL_EOF] = { "~" },
116 static Cell cell_unused;
117 static Cell cell_blank = { .data = " " };
119 static void view_clear(View *view);
120 static bool view_addch(View *view, Cell *cell);
121 static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol);
122 static void view_cursors_free(Cursor *c);
123 /* set/move current cursor position to a given (line, column) pair */
124 static size_t cursor_set(Cursor *cursor, Line *line, int col);
126 #if !CONFIG_LUA
128 static void view_syntax_color(View *view) { }
129 bool view_syntax_set(View *view, const char *name) { return false; }
131 #else
133 static void view_syntax_color(View *view) {
134 lua_State *L = view->lua;
135 if (!L || !view->lexer_name)
136 return;
137 lua_getglobal(L, "vis");
138 lua_getfield(L, -1, "lexers");
139 if (lua_isnil(L, -1))
140 return;
142 /* absolute position to start syntax highlighting */
143 const size_t lexer_start = view->start >= view->horizon ?
144 view->start - view->horizon : 0;
145 /* number of bytes used for syntax highlighting before visible are */
146 size_t lexer_before = view->start - lexer_start;
147 /* number of bytes to read in one go */
148 const size_t text_size = lexer_before + (view->end - view->start);
149 /* current buffer to work with */
150 char text[text_size+1];
151 /* bytes to process */
152 const size_t text_len = text_bytes_get(view->text, lexer_start, text_size, text);
153 /* NUL terminate text section */
154 text[text_len] = '\0';
155 if (text_len < lexer_before)
156 lexer_before = text_len;
158 lua_getfield(L, -1, "load");
159 lua_pushstring(L, view->lexer_name);
160 lua_pcall(L, 1, 1, 0);
162 lua_getfield(L, -1, "_TOKENSTYLES");
163 lua_getfield(L, -2, "lex");
165 lua_pushvalue(L, -3); /* lexer obj */
167 const char *lex_text = text;
168 if (lexer_start > 0) {
169 /* try to start lexing at a line boundry */
170 /* TODO: start at known state, handle nested lexers */
171 const char *newline = memchr(text, '\n', lexer_before);
172 if (newline)
173 lex_text = newline;
176 lua_pushlstring(L, lex_text, text_len - (lex_text - text));
177 lua_pushinteger(L, 1 /* inital style: whitespace */);
179 int token_count;
181 if (lua_isfunction(L, -4) && !lua_pcall(L, 3, 1, 0) && lua_istable(L, -1) &&
182 (token_count = lua_objlen(L, -1)) > 0) {
184 size_t pos = lexer_before - (lex_text - text);
185 Line *line = view->topline;
186 int col = 0;
188 for (int i = 1; i < token_count; i += 2) {
189 lua_rawgeti(L, -1, i);
190 //const char *name = lua_tostring(L, -1);
191 lua_gettable(L, -3); /* _TOKENSTYLES[token] */
192 size_t token_style = lua_tointeger(L, -1);
193 lua_pop(L, 1); /* style */
194 lua_rawgeti(L, -1, i + 1);
195 size_t token_end = lua_tointeger(L, -1) - 1;
196 lua_pop(L, 1); /* pos */
198 for (bool token_next = false; line; line = line->next, col = 0) {
199 for (; col < line->width; col++) {
200 if (pos < token_end) {
201 line->cells[col].style = token_style;
202 pos += line->cells[col].len;
203 } else {
204 token_next = true;
205 break;
208 if (token_next)
209 break;
212 lua_pop(L, 1);
215 lua_pop(L, 3); /* _TOKENSTYLES, language specific lexer, lexers global */
218 bool view_syntax_set(View *view, const char *name) {
219 lua_State *L = view->lua;
220 if (!L)
221 return name == NULL;
223 lua_getglobal(L, "vis");
224 lua_getfield(L, -1, "lexers");
226 static const struct {
227 enum UiStyles id;
228 const char *name;
229 } styles[] = {
230 { UI_STYLE_DEFAULT, "STYLE_DEFAULT" },
231 { UI_STYLE_CURSOR, "STYLE_CURSOR" },
232 { UI_STYLE_CURSOR_PRIMARY, "STYLE_CURSOR_PRIMARY" },
233 { UI_STYLE_CURSOR_LINE, "STYLE_CURSOR_LINE" },
234 { UI_STYLE_SELECTION, "STYLE_SELECTION" },
235 { UI_STYLE_LINENUMBER, "STYLE_LINENUMBER" },
236 { UI_STYLE_COLOR_COLUMN, "STYLE_COLOR_COLUMN" },
239 for (size_t i = 0; i < LENGTH(styles); i++) {
240 lua_getfield(L, -1, styles[i].name);
241 view->ui->syntax_style(view->ui, styles[i].id, lua_tostring(L, -1));
242 lua_pop(L, 1);
245 if (!name) {
246 free(view->lexer_name);
247 view->lexer_name = NULL;
248 return true;
251 /* Try to load the specified lexer and parse its token styles.
252 * Roughly equivalent to the following lua code:
254 * lang = vis.lexers.load(name)
255 * for token_name, id in pairs(lang._TOKENSTYLES) do
256 * ui->syntax_style(id, vis.lexers:get_style(lang, token_name);
258 lua_getfield(L, -1, "load");
259 lua_pushstring(L, name);
261 if (lua_pcall(L, 1, 1, 0))
262 return false;
264 if (!lua_istable(L, -1)) {
265 lua_pop(L, 2);
266 return false;
269 view->lexer_name = strdup(name);
270 /* loop through all _TOKENSTYLES and parse them */
271 lua_getfield(L, -1, "_TOKENSTYLES");
272 lua_pushnil(L); /* first key */
274 while (lua_next(L, -2)) {
275 size_t id = lua_tointeger(L, -1);
276 //const char *name = lua_tostring(L, -2);
277 lua_pop(L, 1); /* remove value (=id), keep key (=name) */
278 lua_getfield(L, -4, "get_style");
279 lua_pushvalue(L, -5); /* lexer */
280 lua_pushvalue(L, -5); /* lang */
281 lua_pushvalue(L, -4); /* token_name */
282 if (lua_pcall(L, 3, 1, 0))
283 return false;
284 const char *style = lua_tostring(L, -1);
285 //printf("%s\t%d\t%s\n", name, id, style);
286 view->ui->syntax_style(view->ui, id, style);
287 lua_pop(L, 1); /* style */
290 lua_pop(L, 4); /* _TOKENSTYLES, grammar, lexers, vis */
292 return true;
295 #endif
298 void view_tabwidth_set(View *view, int tabwidth) {
299 view->tabwidth = tabwidth;
300 view_draw(view);
303 /* reset internal view data structures (cell matrix, line offsets etc.) */
304 static void view_clear(View *view) {
305 memset(view->lines, 0, view->lines_size);
306 if (view->start != view->start_last) {
307 view->start_mark = text_mark_set(view->text, view->start);
308 } else {
309 size_t start = text_mark_get(view->text, view->start_mark);
310 if (start != EPOS)
311 view->start = start;
314 view->start_last = view->start;
315 view->topline = view->lines;
316 view->topline->lineno = view->large_file ? 1 : text_lineno_by_pos(view->text, view->start);
317 view->lastline = view->topline;
319 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
320 size_t end = view->height * line_size;
321 Line *prev = NULL;
322 for (size_t i = 0; i < end; i += line_size) {
323 Line *line = (Line*)(((char*)view->lines) + i);
324 line->prev = prev;
325 if (prev)
326 prev->next = line;
327 prev = line;
329 view->bottomline = prev ? prev : view->topline;
330 view->bottomline->next = NULL;
331 view->line = view->topline;
332 view->col = 0;
335 Filerange view_viewport_get(View *view) {
336 return (Filerange){ .start = view->start, .end = view->end };
339 /* try to add another character to the view, return whether there was space left */
340 static bool view_addch(View *view, Cell *cell) {
341 if (!view->line)
342 return false;
344 int width;
345 size_t lineno = view->line->lineno;
346 unsigned char ch = (unsigned char)cell->data[0];
348 switch (ch) {
349 case '\t':
350 cell->width = 1;
351 width = view->tabwidth - (view->col % view->tabwidth);
352 for (int w = 0; w < width; w++) {
353 if (view->col + 1 > view->width) {
354 view->line = view->line->next;
355 view->col = 0;
356 if (!view->line)
357 return false;
358 view->line->lineno = lineno;
361 cell->len = w == 0 ? 1 : 0;
362 int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
363 strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
364 cell->style = view->symbols[t]->style;
365 view->line->cells[view->col] = *cell;
366 view->line->len += cell->len;
367 view->line->width += cell->width;
368 view->col++;
370 cell->len = 1;
371 return true;
372 case '\n':
373 cell->width = 1;
374 if (view->col + cell->width > view->width) {
375 view->line = view->line->next;
376 view->col = 0;
377 if (!view->line)
378 return false;
379 view->line->lineno = lineno;
382 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
383 cell->style = view->symbols[SYNTAX_SYMBOL_EOL]->style;
385 view->line->cells[view->col] = *cell;
386 view->line->len += cell->len;
387 view->line->width += cell->width;
388 for (int i = view->col + 1; i < view->width; i++)
389 view->line->cells[i] = cell_blank;
391 view->line = view->line->next;
392 if (view->line)
393 view->line->lineno = lineno + 1;
394 view->col = 0;
395 return true;
396 default:
397 if (ch < 128 && !isprint(ch)) {
398 /* non-printable ascii char, represent it as ^(char + 64) */
399 *cell = (Cell) {
400 .data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
401 .len = 1,
402 .width = 2,
403 .style = cell->style,
407 if (ch == ' ') {
408 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
409 cell->style = view->symbols[SYNTAX_SYMBOL_SPACE]->style;
413 if (view->col + cell->width > view->width) {
414 for (int i = view->col; i < view->width; i++)
415 view->line->cells[i] = cell_blank;
416 view->line = view->line->next;
417 view->col = 0;
420 if (view->line) {
421 view->line->width += cell->width;
422 view->line->len += cell->len;
423 view->line->lineno = lineno;
424 view->line->cells[view->col] = *cell;
425 view->col++;
426 /* set cells of a character which uses multiple columns */
427 for (int i = 1; i < cell->width; i++)
428 view->line->cells[view->col++] = cell_unused;
429 return true;
431 return false;
435 CursorPos view_cursor_getpos(View *view) {
436 Cursor *cursor = view->cursor;
437 Line *line = cursor->line;
438 CursorPos pos = { .line = line->lineno, .col = cursor->col };
439 while (line->prev && line->prev->lineno == pos.line) {
440 line = line->prev;
441 pos.col += line->width;
443 pos.col++;
444 return pos;
447 static void cursor_to(Cursor *c, size_t pos) {
448 Text *txt = c->view->text;
449 c->mark = text_mark_set(txt, pos);
450 if (pos != c->pos)
451 c->lastcol = 0;
452 c->pos = pos;
453 if (c->sel) {
454 c->sel->cursor = text_mark_set(txt, pos);
456 if (!view_coord_get(c->view, pos, &c->line, &c->row, &c->col)) {
457 if (c->view->cursor == c) {
458 c->line = c->view->topline;
459 c->row = 0;
460 c->col = 0;
462 return;
464 // TODO: minimize number of redraws
465 view_draw(c->view);
468 static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
469 int row = 0, col = 0;
470 size_t cur = view->start;
471 Line *line = view->topline;
473 if (pos < view->start || pos > view->end) {
474 if (retline) *retline = NULL;
475 if (retrow) *retrow = -1;
476 if (retcol) *retcol = -1;
477 return false;
480 while (line && line != view->lastline && cur < pos) {
481 if (cur + line->len > pos)
482 break;
483 cur += line->len;
484 line = line->next;
485 row++;
488 if (line) {
489 int max_col = MIN(view->width, line->width);
490 while (cur < pos && col < max_col) {
491 cur += line->cells[col].len;
492 /* skip over columns occupied by the same character */
493 while (++col < max_col && line->cells[col].len == 0);
495 } else {
496 line = view->bottomline;
497 row = view->height - 1;
500 if (retline) *retline = line;
501 if (retrow) *retrow = row;
502 if (retcol) *retcol = col;
503 return true;
506 /* move the cursor to the character at pos bytes from the begining of the file.
507 * if pos is not in the current viewport, redraw the view to make it visible */
508 void view_cursor_to(View *view, size_t pos) {
509 view_cursors_to(view->cursor, pos);
512 /* redraw the complete with data starting from view->start bytes into the file.
513 * stop once the screen is full, update view->end, view->lastline */
514 void view_draw(View *view) {
515 view_clear(view);
516 /* read a screenful of text considering each character as 4-byte UTF character*/
517 const size_t text_size = view->width * view->height * 4;
518 /* current buffer to work with */
519 char text[text_size+1];
520 /* remaining bytes to process in buffer */
521 size_t rem = text_bytes_get(view->text, view->start, text_size, text);
522 /* NUL terminate text section */
523 text[rem] = '\0';
524 /* absolute position of character currently being added to display */
525 size_t pos = view->start;
526 /* current position into buffer from which to interpret a character */
527 char *cur = text;
528 /* start from known multibyte state */
529 mbstate_t mbstate = { 0 };
531 Cell cell = { 0 }, prev_cell = { 0 };
533 while (rem > 0) {
535 /* current 'parsed' character' */
536 wchar_t wchar;
538 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
539 if (len == (size_t)-1 && errno == EILSEQ) {
540 /* ok, we encountered an invalid multibyte sequence,
541 * replace it with the Unicode Replacement Character
542 * (FFFD) and skip until the start of the next utf8 char */
543 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
544 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
545 } else if (len == (size_t)-2) {
546 /* not enough bytes available to convert to a
547 * wide character. advance file position and read
548 * another junk into buffer.
550 rem = text_bytes_get(view->text, pos, text_size, text);
551 text[rem] = '\0';
552 cur = text;
553 continue;
554 } else if (len == 0) {
555 /* NUL byte encountered, store it and continue */
556 cell = (Cell){ .data = "\x00", .len = 1, .width = 2 };
557 } else {
558 for (size_t i = 0; i < len; i++)
559 cell.data[i] = cur[i];
560 cell.data[len] = '\0';
561 cell.len = len;
562 cell.width = wcwidth(wchar);
563 if (cell.width == -1)
564 cell.width = 1;
567 if (cur[0] == '\r' && rem > 1 && cur[1] == '\n') {
568 /* convert views style newline \r\n into a single char with len = 2 */
569 cell = (Cell){ .data = "\n", .len = 2, .width = 1 };
572 if (cell.width == 0 && prev_cell.len + cell.len < sizeof(cell.len)) {
573 prev_cell.len += cell.len;
574 strcat(prev_cell.data, cell.data);
575 } else {
576 if (prev_cell.len && !view_addch(view, &prev_cell))
577 break;
578 pos += prev_cell.len;
579 prev_cell = cell;
582 rem -= cell.len;
583 cur += cell.len;
585 memset(&cell, 0, sizeof cell);
588 if (prev_cell.len && view_addch(view, &prev_cell))
589 pos += prev_cell.len;
591 /* set end of vieviewg region */
592 view->end = pos;
593 view->lastline = view->line ? view->line : view->bottomline;
595 /* clear remaining of line, important to show cursor at end of file */
596 if (view->line) {
597 for (int x = view->col; x < view->width; x++)
598 view->line->cells[x] = cell_blank;
601 /* resync position of cursors within visible area */
602 for (Cursor *c = view->cursors; c; c = c->next) {
603 size_t pos = view_cursors_pos(c);
604 if (view_coord_get(view, pos, &c->line, &c->row, &c->col)) {
605 c->line->cells[c->col].cursor = true;
606 c->line->cells[c->col].cursor_primary = (c == view->cursor);
607 if (view->ui && !c->sel) {
608 Line *line_match; int col_match;
609 size_t pos_match = text_bracket_match_symbol(view->text, pos, "(){}[]\"'`");
610 if (pos != pos_match && view_coord_get(view, pos_match, &line_match, NULL, &col_match)) {
611 line_match->cells[col_match].selected = true;
614 } else if (c == view->cursor) {
615 c->line = view->topline;
616 c->row = 0;
617 c->col = 0;
621 view->need_update = true;
624 void view_update(View *view) {
625 if (!view->need_update)
626 return;
628 view_syntax_color(view);
630 if (view->colorcolumn > 0) {
631 size_t lineno = 0;
632 int line_cols = 0; /* Track the number of columns we've passed on each line */
633 bool line_cc_set = false; /* Has the colorcolumn attribute been set for this line yet */
635 for (Line *l = view->topline; l; l = l->next) {
636 if (l->lineno != lineno) {
637 line_cols = 0;
638 line_cc_set = false;
641 if (!line_cc_set) {
642 line_cols += view->width;
644 /* This screen line contains the cell we want to highlight */
645 if (line_cols >= view->colorcolumn) {
646 l->cells[(view->colorcolumn - 1) % view->width].style = UI_STYLE_COLOR_COLUMN;
647 line_cc_set = true;
651 lineno = l->lineno;
655 for (Line *l = view->lastline->next; l; l = l->next) {
656 strncpy(l->cells[0].data, view->symbols[SYNTAX_SYMBOL_EOF]->symbol, sizeof(l->cells[0].data));
657 l->cells[0].style = view->symbols[SYNTAX_SYMBOL_EOF]->style;
658 for (int x = 1; x < view->width; x++)
659 l->cells[x] = cell_blank;
660 l->width = 1;
661 l->len = 0;
664 for (Selection *s = view->selections; s; s = s->next) {
665 Filerange sel = view_selections_get(s);
666 if (text_range_valid(&sel)) {
667 Line *start_line; int start_col;
668 Line *end_line; int end_col;
669 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
670 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
671 if (start_line || end_line) {
672 if (!start_line) {
673 start_line = view->topline;
674 start_col = 0;
676 if (!end_line) {
677 end_line = view->lastline;
678 end_col = end_line->width;
680 for (Line *l = start_line; l != end_line->next; l = l->next) {
681 int col = (l == start_line) ? start_col : 0;
682 int end = (l == end_line) ? end_col : l->width;
683 while (col < end) {
684 l->cells[col++].selected = true;
691 if (view->ui)
692 view->ui->draw(view->ui);
693 view->need_update = false;
696 bool view_resize(View *view, int width, int height) {
697 if (width <= 0)
698 width = 1;
699 if (height <= 0)
700 height = 1;
701 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
702 if (lines_size > view->lines_size) {
703 Line *lines = realloc(view->lines, lines_size);
704 if (!lines)
705 return false;
706 view->lines = lines;
707 view->lines_size = lines_size;
709 view->width = width;
710 view->height = height;
711 memset(view->lines, 0, view->lines_size);
712 view_draw(view);
713 return true;
716 int view_height_get(View *view) {
717 return view->height;
720 int view_width_get(View *view) {
721 return view->width;
724 void view_free(View *view) {
725 if (!view)
726 return;
727 while (view->cursors)
728 view_cursors_free(view->cursors);
729 while (view->selections)
730 view_selections_free(view->selections);
731 free(view->lines);
732 free(view->lexer_name);
733 free(view);
736 void view_reload(View *view, Text *text) {
737 view->text = text;
738 view_selections_clear(view);
739 view_cursor_to(view, 0);
742 View *view_new(Text *text, lua_State *lua) {
743 if (!text)
744 return NULL;
745 View *view = calloc(1, sizeof(View));
746 if (!view)
747 return NULL;
748 if (!view_cursors_new(view, 0)) {
749 view_free(view);
750 return NULL;
753 view->text = text;
754 view->lua = lua;
755 view->tabwidth = 8;
756 view->horizon = 1 << 15;
757 view_options_set(view, 0);
759 if (!view_resize(view, 1, 1)) {
760 view_free(view);
761 return NULL;
764 view_cursor_to(view, 0);
766 return view;
769 void view_ui(View *view, UiWin* ui) {
770 view->ui = ui;
773 static size_t cursor_set(Cursor *cursor, Line *line, int col) {
774 int row = 0;
775 View *view = cursor->view;
776 size_t pos = view->start;
777 /* get row number and file offset at start of the given line */
778 for (Line *cur = view->topline; cur && cur != line; cur = cur->next) {
779 pos += cur->len;
780 row++;
783 /* for characters which use more than 1 column, make sure we are on the left most */
784 while (col > 0 && line->cells[col].len == 0)
785 col--;
786 /* calculate offset within the line */
787 for (int i = 0; i < col; i++)
788 pos += line->cells[i].len;
790 cursor->col = col;
791 cursor->row = row;
792 cursor->line = line;
794 cursor_to(cursor, pos);
796 return pos;
799 bool view_viewport_down(View *view, int n) {
800 Line *line;
801 if (view->end == text_size(view->text))
802 return false;
803 if (n >= view->height) {
804 view->start = view->end;
805 } else {
806 for (line = view->topline; line && n > 0; line = line->next, n--)
807 view->start += line->len;
809 view_draw(view);
810 return true;
813 bool view_viewport_up(View *view, int n) {
814 /* scrolling up is somewhat tricky because we do not yet know where
815 * the lines start, therefore scan backwards but stop at a reasonable
816 * maximum in case we are dealing with a file without any newlines
818 if (view->start == 0)
819 return false;
820 size_t max = view->width * view->height;
821 char c;
822 Iterator it = text_iterator_get(view->text, view->start - 1);
824 if (!text_iterator_byte_get(&it, &c))
825 return false;
826 size_t off = 0;
827 /* skip newlines immediately before display area */
828 if (c == '\n' && text_iterator_byte_prev(&it, &c))
829 off++;
830 if (c == '\r' && text_iterator_byte_prev(&it, &c))
831 off++;
832 do {
833 if (c == '\n' && --n == 0)
834 break;
835 if (++off > max)
836 break;
837 } while (text_iterator_byte_prev(&it, &c));
838 if (c == '\r')
839 off++;
840 view->start -= off;
841 view_draw(view);
842 return true;
845 void view_redraw_top(View *view) {
846 Line *line = view->cursor->line;
847 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
848 view->start += cur->len;
849 view_draw(view);
850 view_cursor_to(view, view->cursor->pos);
853 void view_redraw_center(View *view) {
854 int center = view->height / 2;
855 size_t pos = view->cursor->pos;
856 for (int i = 0; i < 2; i++) {
857 int linenr = 0;
858 Line *line = view->cursor->line;
859 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
860 linenr++;
861 if (linenr < center) {
862 view_slide_down(view, center - linenr);
863 continue;
865 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
866 view->start += cur->len;
867 linenr--;
869 break;
871 view_draw(view);
872 view_cursor_to(view, pos);
875 void view_redraw_bottom(View *view) {
876 Line *line = view->cursor->line;
877 if (line == view->lastline)
878 return;
879 size_t pos = view->cursor->pos;
880 view_viewport_up(view, view->height);
881 while (pos > view->end && view_viewport_down(view, 1));
882 view_cursor_to(view, pos);
885 size_t view_slide_up(View *view, int lines) {
886 Cursor *cursor = view->cursor;
887 if (view_viewport_down(view, lines)) {
888 if (cursor->line == view->topline)
889 cursor_set(cursor, view->topline, cursor->col);
890 else
891 view_cursor_to(view, cursor->pos);
892 } else {
893 view_screenline_down(cursor);
895 return cursor->pos;
898 size_t view_slide_down(View *view, int lines) {
899 Cursor *cursor = view->cursor;
900 if (view_viewport_up(view, lines)) {
901 if (cursor->line == view->lastline)
902 cursor_set(cursor, view->lastline, cursor->col);
903 else
904 view_cursor_to(view, cursor->pos);
905 } else {
906 view_screenline_up(cursor);
908 return cursor->pos;
911 size_t view_scroll_up(View *view, int lines) {
912 Cursor *cursor = view->cursor;
913 if (view_viewport_up(view, lines)) {
914 Line *line = cursor->line < view->lastline ? cursor->line : view->lastline;
915 cursor_set(cursor, line, view->cursor->col);
916 } else {
917 view_cursor_to(view, 0);
919 return cursor->pos;
922 size_t view_scroll_down(View *view, int lines) {
923 Cursor *cursor = view->cursor;
924 if (view_viewport_down(view, lines)) {
925 Line *line = cursor->line > view->topline ? cursor->line : view->topline;
926 cursor_set(cursor, line, cursor->col);
927 } else {
928 view_cursor_to(view, text_size(view->text));
930 return cursor->pos;
933 size_t view_line_up(Cursor *cursor) {
934 if (cursor->line && cursor->line->prev && cursor->line->prev->prev &&
935 cursor->line->lineno != cursor->line->prev->lineno &&
936 cursor->line->prev->lineno != cursor->line->prev->prev->lineno)
937 return view_screenline_up(cursor);
938 int lastcol = cursor->lastcol;
939 if (!lastcol)
940 lastcol = cursor->col;
941 view_cursors_to(cursor, text_line_up(cursor->view->text, cursor->pos));
942 if (cursor->line)
943 cursor_set(cursor, cursor->line, lastcol);
944 cursor->lastcol = lastcol;
945 return cursor->pos;
948 size_t view_line_down(Cursor *cursor) {
949 if (cursor->line && (!cursor->line->next || cursor->line->next->lineno != cursor->line->lineno))
950 return view_screenline_down(cursor);
951 int lastcol = cursor->lastcol;
952 if (!lastcol)
953 lastcol = cursor->col;
954 view_cursors_to(cursor, text_line_down(cursor->view->text, cursor->pos));
955 if (cursor->line)
956 cursor_set(cursor, cursor->line, lastcol);
957 cursor->lastcol = lastcol;
958 return cursor->pos;
961 size_t view_screenline_up(Cursor *cursor) {
962 int lastcol = cursor->lastcol;
963 if (!lastcol)
964 lastcol = cursor->col;
965 if (!cursor->line->prev)
966 view_scroll_up(cursor->view, 1);
967 if (cursor->line->prev)
968 cursor_set(cursor, cursor->line->prev, lastcol);
969 cursor->lastcol = lastcol;
970 return cursor->pos;
973 size_t view_screenline_down(Cursor *cursor) {
974 int lastcol = cursor->lastcol;
975 if (!lastcol)
976 lastcol = cursor->col;
977 if (!cursor->line->next && cursor->line == cursor->view->bottomline)
978 view_scroll_down(cursor->view, 1);
979 if (cursor->line->next)
980 cursor_set(cursor, cursor->line->next, lastcol);
981 cursor->lastcol = lastcol;
982 return cursor->pos;
985 size_t view_screenline_begin(Cursor *cursor) {
986 if (!cursor->line)
987 return cursor->pos;
988 return cursor_set(cursor, cursor->line, 0);
991 size_t view_screenline_middle(Cursor *cursor) {
992 if (!cursor->line)
993 return cursor->pos;
994 return cursor_set(cursor, cursor->line, cursor->line->width / 2);
997 size_t view_screenline_end(Cursor *cursor) {
998 if (!cursor->line)
999 return cursor->pos;
1000 int col = cursor->line->width - 1;
1001 return cursor_set(cursor, cursor->line, col >= 0 ? col : 0);
1004 size_t view_cursor_get(View *view) {
1005 return view_cursors_pos(view->cursor);
1008 const Line *view_lines_get(View *view) {
1009 return view->topline;
1012 void view_scroll_to(View *view, size_t pos) {
1013 view_cursors_scroll_to(view->cursor, pos);
1016 const char *view_syntax_get(View *view) {
1017 return view->lexer_name;
1020 void view_options_set(View *view, enum UiOption options) {
1021 const int mapping[] = {
1022 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
1023 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
1024 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
1025 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
1026 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
1029 for (int i = 0; i < LENGTH(mapping); i++) {
1030 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
1031 &symbols_none[i];
1034 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
1035 options &= ~UI_OPTION_LARGE_FILE;
1037 view->large_file = (options & UI_OPTION_LARGE_FILE);
1039 if (view->ui)
1040 view->ui->options_set(view->ui, options);
1043 enum UiOption view_options_get(View *view) {
1044 return view->ui ? view->ui->options_get(view->ui) : 0;
1047 void view_colorcolumn_set(View *view, int col) {
1048 if (col >= 0)
1049 view->colorcolumn = col;
1052 int view_colorcolumn_get(View *view) {
1053 return view->colorcolumn;
1056 void view_horizon_set(View *view, size_t bytes) {
1057 view->horizon = bytes;
1060 size_t view_horizon_get(View *view) {
1061 return view->horizon;
1064 size_t view_screenline_goto(View *view, int n) {
1065 size_t pos = view->start;
1066 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
1067 pos += line->len;
1068 return pos;
1071 static Cursor *cursors_new(View *view, size_t pos, bool force) {
1072 Cursor *c = calloc(1, sizeof(*c));
1073 if (!c)
1074 return NULL;
1075 c->view = view;
1076 c->generation = view->cursor_generation;
1077 if (!view->cursors) {
1078 view->cursor = c;
1079 view->cursors = c;
1080 view->cursor_count = 1;
1081 return c;
1084 Cursor *prev = NULL, *next = NULL;
1085 size_t cur = view_cursors_pos(view->cursor);
1086 if (pos >= cur) {
1087 prev = view->cursor;
1088 for (next = prev->next; next; prev = next, next = next->next) {
1089 cur = view_cursors_pos(next);
1090 if (pos <= cur)
1091 break;
1093 } else if (pos < cur) {
1094 next = view->cursor;
1095 for (prev = next->prev; prev; next = prev, prev = prev->prev) {
1096 cur = view_cursors_pos(prev);
1097 if (pos >= cur)
1098 break;
1102 if (pos == cur && !force)
1103 goto err;
1105 for (Cursor *after = next; after; after = after->next)
1106 after->number++;
1108 c->prev = prev;
1109 c->next = next;
1110 if (next)
1111 next->prev = c;
1112 if (prev) {
1113 prev->next = c;
1114 c->number = prev->number + 1;
1115 } else {
1116 view->cursors = c;
1118 view->cursor = c;
1119 view->cursor_count++;;
1120 view_cursors_to(c, pos);
1121 return c;
1122 err:
1123 free(c);
1124 return NULL;
1127 Cursor *view_cursors_new(View *view, size_t pos) {
1128 return cursors_new(view, pos, false);
1131 Cursor *view_cursors_new_force(View *view, size_t pos) {
1132 return cursors_new(view, pos, true);
1135 int view_cursors_count(View *view) {
1136 return view->cursor_count;
1139 int view_cursors_number(Cursor *c) {
1140 return c->number;
1143 int view_cursors_column_count(View *view) {
1144 Text *txt = view->text;
1145 int cpl_max = 0, cpl = 0; /* cursors per line */
1146 size_t line_prev = 0;
1147 for (Cursor *c = view->cursors; c; c = c->next) {
1148 size_t pos = view_cursors_pos(c);
1149 size_t line = text_lineno_by_pos(txt, pos);
1150 if (line == line_prev)
1151 cpl++;
1152 else
1153 cpl = 1;
1154 line_prev = line;
1155 if (cpl > cpl_max)
1156 cpl_max = cpl;
1158 return cpl_max;
1161 static Cursor *cursors_column_next(View *view, Cursor *c, int column) {
1162 size_t line_cur = 0;
1163 int column_cur = 0;
1164 Text *txt = view->text;
1165 if (c) {
1166 size_t pos = view_cursors_pos(c);
1167 line_cur = text_lineno_by_pos(txt, pos);
1168 column_cur = INT_MIN;
1169 } else {
1170 c = view->cursors;
1173 for (; c; c = c->next) {
1174 size_t pos = view_cursors_pos(c);
1175 size_t line = text_lineno_by_pos(txt, pos);
1176 if (line != line_cur) {
1177 line_cur = line;
1178 column_cur = 0;
1179 } else {
1180 column_cur++;
1182 if (column == column_cur)
1183 return c;
1185 return NULL;
1188 Cursor *view_cursors_column(View *view, int column) {
1189 return cursors_column_next(view, NULL, column);
1192 Cursor *view_cursors_column_next(Cursor *c, int column) {
1193 return cursors_column_next(c->view, c, column);
1196 bool view_cursors_multiple(View *view) {
1197 return view->cursors && view->cursors->next;
1200 static void view_cursors_free(Cursor *c) {
1201 if (!c)
1202 return;
1203 register_release(&c->reg);
1204 for (Cursor *after = c->next; after; after = after->next)
1205 after->number--;
1206 if (c->prev)
1207 c->prev->next = c->next;
1208 if (c->next)
1209 c->next->prev = c->prev;
1210 if (c->view->cursors == c)
1211 c->view->cursors = c->next;
1212 if (c->view->cursor == c)
1213 c->view->cursor = c->next ? c->next : c->prev;
1214 c->view->cursor_count--;
1215 free(c);
1218 bool view_cursors_dispose(Cursor *c) {
1219 if (!c)
1220 return false;
1221 View *view = c->view;
1222 if (!view->cursors || !view->cursors->next)
1223 return false;
1224 view_selections_free(c->sel);
1225 view_cursors_free(c);
1226 view_cursors_primary_set(view->cursor);
1227 return true;
1230 Cursor *view_cursors(View *view) {
1231 view->cursor_generation++;
1232 return view->cursors;
1235 Cursor *view_cursors_primary_get(View *view) {
1236 view->cursor_generation++;
1237 return view->cursor;
1240 void view_cursors_primary_set(Cursor *c) {
1241 if (!c)
1242 return;
1243 View *view = c->view;
1244 view->cursor = c;
1245 Filerange sel = view_cursors_selection_get(c);
1246 view_cursors_to(c, view_cursors_pos(c));
1247 view_cursors_selection_set(c, &sel);
1250 Cursor *view_cursors_prev(Cursor *c) {
1251 View *view = c->view;
1252 for (c = c->prev; c; c = c->prev) {
1253 if (c->generation != view->cursor_generation)
1254 return c;
1256 view->cursor_generation++;
1257 return NULL;
1260 Cursor *view_cursors_next(Cursor *c) {
1261 View *view = c->view;
1262 for (c = c->next; c; c = c->next) {
1263 if (c->generation != view->cursor_generation)
1264 return c;
1266 view->cursor_generation++;
1267 return NULL;
1270 size_t view_cursors_pos(Cursor *c) {
1271 return text_mark_get(c->view->text, c->mark);
1274 size_t view_cursors_line(Cursor *c) {
1275 size_t pos = view_cursors_pos(c);
1276 return text_lineno_by_pos(c->view->text, pos);
1279 size_t view_cursors_col(Cursor *c) {
1280 size_t pos = view_cursors_pos(c);
1281 return text_line_char_get(c->view->text, pos) + 1;
1284 int view_cursors_cell_get(Cursor *c) {
1285 return c->line ? c->col : -1;
1288 int view_cursors_cell_set(Cursor *c, int cell) {
1289 if (!c->line || cell < 0)
1290 return -1;
1291 cursor_set(c, c->line, cell);
1292 return c->col;
1295 Register *view_cursors_register(Cursor *c) {
1296 return &c->reg;
1299 void view_cursors_scroll_to(Cursor *c, size_t pos) {
1300 View *view = c->view;
1301 if (view->cursor == c) {
1302 while (pos < view->start && view_viewport_up(view, 1));
1303 while (pos > view->end && view_viewport_down(view, 1));
1305 view_cursors_to(c, pos);
1308 void view_cursors_to(Cursor *c, size_t pos) {
1309 View *view = c->view;
1310 if (c->view->cursor == c) {
1311 c->mark = text_mark_set(view->text, pos);
1313 size_t max = text_size(view->text);
1314 if (pos == max && view->end < max) {
1315 /* do not display an empty screen when shoviewg the end of the file */
1316 view->start = pos;
1317 view_viewport_up(view, view->height / 2);
1318 } else {
1319 /* make sure we redraw changes to the very first character of the window */
1320 if (view->start == pos)
1321 view->start_last = 0;
1322 /* set the start of the viewable region to the start of the line on which
1323 * the cursor should be placed. if this line requires more space than
1324 * available in the view then simply start displaying text at the new
1325 * cursor position */
1326 for (int i = 0; i < 2 && (pos <= view->start || pos > view->end); i++) {
1327 view->start = i == 0 ? text_line_begin(view->text, pos) : pos;
1328 view_draw(view);
1333 cursor_to(c, pos);
1336 void view_cursors_place(Cursor *c, size_t line, size_t col) {
1337 Text *txt = c->view->text;
1338 size_t pos = text_pos_by_lineno(txt, line);
1339 pos = text_line_char_set(txt, pos, col);
1340 view_cursors_to(c, pos);
1343 void view_cursors_selection_start(Cursor *c) {
1344 if (c->sel)
1345 return;
1346 size_t pos = view_cursors_pos(c);
1347 if (pos == EPOS || !(c->sel = view_selections_new(c->view)))
1348 return;
1349 Text *txt = c->view->text;
1350 c->sel->anchor = text_mark_set(txt, pos);
1351 c->sel->cursor = c->sel->anchor;
1352 view_draw(c->view);
1355 void view_cursors_selection_restore(Cursor *c) {
1356 Text *txt = c->view->text;
1357 if (c->sel)
1358 return;
1359 Filerange sel = text_range_new(
1360 text_mark_get(txt, c->lastsel_anchor),
1361 text_mark_get(txt, c->lastsel_cursor)
1363 if (!text_range_valid(&sel))
1364 return;
1365 sel.end = text_char_next(txt, sel.end);
1366 if (!(c->sel = view_selections_new(c->view)))
1367 return;
1368 view_selections_set(c->sel, &sel);
1369 view_cursors_selection_sync(c);
1370 view_draw(c->view);
1373 void view_cursors_selection_stop(Cursor *c) {
1374 c->sel = NULL;
1377 void view_cursors_selection_clear(Cursor *c) {
1378 view_selections_free(c->sel);
1379 view_draw(c->view);
1382 void view_cursors_selection_swap(Cursor *c) {
1383 if (!c->sel)
1384 return;
1385 view_selections_swap(c->sel);
1386 view_cursors_selection_sync(c);
1389 void view_cursors_selection_sync(Cursor *c) {
1390 if (!c->sel)
1391 return;
1392 Text *txt = c->view->text;
1393 size_t cursor = text_mark_get(txt, c->sel->cursor);
1394 view_cursors_to(c, cursor);
1397 Filerange view_cursors_selection_get(Cursor *c) {
1398 return view_selections_get(c->sel);
1401 void view_cursors_selection_set(Cursor *c, const Filerange *r) {
1402 if (!text_range_valid(r))
1403 return;
1404 if (!c->sel)
1405 c->sel = view_selections_new(c->view);
1406 if (!c->sel)
1407 return;
1409 view_selections_set(c->sel, r);
1412 Selection *view_selections_new(View *view) {
1413 Selection *s = calloc(1, sizeof(*s));
1414 if (!s)
1415 return NULL;
1417 s->view = view;
1418 s->next = view->selections;
1419 if (view->selections)
1420 view->selections->prev = s;
1421 view->selections = s;
1422 return s;
1425 void view_selections_free(Selection *s) {
1426 if (!s)
1427 return;
1428 if (s->prev)
1429 s->prev->next = s->next;
1430 if (s->next)
1431 s->next->prev = s->prev;
1432 if (s->view->selections == s)
1433 s->view->selections = s->next;
1434 // XXX: add backlink Selection->Cursor?
1435 for (Cursor *c = s->view->cursors; c; c = c->next) {
1436 if (c->sel == s) {
1437 c->lastsel_anchor = s->anchor;
1438 c->lastsel_cursor = s->cursor;
1439 c->sel = NULL;
1442 free(s);
1445 void view_selections_clear(View *view) {
1446 while (view->selections)
1447 view_selections_free(view->selections);
1448 view_draw(view);
1451 void view_cursors_clear(View *view) {
1452 for (Cursor *c = view->cursors, *next; c; c = next) {
1453 next = c->next;
1454 if (c != view->cursor) {
1455 view_selections_free(c->sel);
1456 view_cursors_free(c);
1459 view_draw(view);
1462 void view_selections_swap(Selection *s) {
1463 Mark temp = s->anchor;
1464 s->anchor = s->cursor;
1465 s->cursor = temp;
1468 Selection *view_selections(View *view) {
1469 return view->selections;
1472 Selection *view_selections_prev(Selection *s) {
1473 return s->prev;
1476 Selection *view_selections_next(Selection *s) {
1477 return s->next;
1480 Filerange view_selection_get(View *view) {
1481 return view_selections_get(view->cursor->sel);
1484 Filerange view_selections_get(Selection *s) {
1485 if (!s)
1486 return text_range_empty();
1487 Text *txt = s->view->text;
1488 size_t anchor = text_mark_get(txt, s->anchor);
1489 size_t cursor = text_mark_get(txt, s->cursor);
1490 Filerange sel = text_range_new(anchor, cursor);
1491 if (text_range_valid(&sel))
1492 sel.end = text_char_next(txt, sel.end);
1493 return sel;
1496 void view_selections_set(Selection *s, const Filerange *r) {
1497 if (!text_range_valid(r))
1498 return;
1499 Text *txt = s->view->text;
1500 size_t anchor = text_mark_get(txt, s->anchor);
1501 size_t cursor = text_mark_get(txt, s->cursor);
1502 bool left_extending = anchor != EPOS && anchor > cursor;
1503 size_t end = r->end;
1504 if (r->start != end)
1505 end = text_char_prev(txt, end);
1506 if (left_extending) {
1507 s->anchor = text_mark_set(txt, end);
1508 s->cursor = text_mark_set(txt, r->start);
1509 } else {
1510 s->anchor = text_mark_set(txt, r->start);
1511 s->cursor = text_mark_set(txt, end);
1513 view_draw(s->view);
1516 Text *view_text(View *view) {
1517 return view->text;