configure: probe for size optimizing flags (disabled for now)
[vis.git] / view.c
blob086fc6b1f911f3c32e9eae2607c8b752ea602d12
1 /*
2 * Copyright (c) 2014-2015 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-lua.h"
23 #include "view.h"
24 #include "text.h"
25 #include "text-motions.h"
26 #include "text-util.h"
27 #include "util.h"
29 typedef struct {
30 char *symbol;
31 int style;
32 } SyntaxSymbol;
34 enum {
35 SYNTAX_SYMBOL_SPACE,
36 SYNTAX_SYMBOL_TAB,
37 SYNTAX_SYMBOL_TAB_FILL,
38 SYNTAX_SYMBOL_EOL,
39 SYNTAX_SYMBOL_EOF,
40 SYNTAX_SYMBOL_LAST,
43 struct Selection {
44 Mark anchor; /* position where the selection was created */
45 Mark cursor; /* other selection endpoint where it changes */
46 View *view; /* associated view to which this selection belongs */
47 Selection *prev, *next; /* previsous/next selections in no particular order */
50 struct Cursor { /* cursor position */
51 Filepos pos; /* in bytes from the start of the file */
52 int row, col; /* in terms of zero based screen coordinates */
53 int lastcol; /* remembered column used when moving across lines */
54 Line *line; /* screen line on which cursor currently resides */
55 Mark mark; /* mark used to keep track of current cursor position */
56 Selection *sel; /* selection (if any) which folows the cursor upon movement */
57 Mark lastsel_anchor;/* previously used selection data, */
58 Mark lastsel_cursor;/* used to restore it */
59 Register reg; /* per cursor register to support yank/put operation */
60 View *view; /* associated view to which this cursor belongs */
61 Cursor *prev, *next;/* previous/next cursors in no particular order */
64 /* Viewable area, showing part of a file. Keeps track of cursors and selections.
65 * At all times there exists at least one cursor, which is placed in the visible viewport.
66 * Additional cursors can be created and positioned anywhere in the file. */
67 struct View {
68 Text *text; /* underlying text management */
69 UiWin *ui;
70 int width, height; /* size of display area */
71 Filepos start, end; /* currently displayed area [start, end] in bytes from the start of the file */
72 Filepos start_last; /* previously used start of visible area, used to update the mark */
73 Mark start_mark; /* mark to keep track of the start of the visible area */
74 size_t lines_size; /* number of allocated bytes for lines (grows only) */
75 Line *lines; /* view->height number of lines representing view content */
76 Line *topline; /* top of the view, first line currently shown */
77 Line *lastline; /* last currently used line, always <= bottomline */
78 Line *bottomline; /* bottom of view, might be unused if lastline < bottomline */
79 Cursor *cursor; /* main cursor, always placed within the visible viewport */
80 Line *line; /* used while drawing view content, line where next char will be drawn */
81 int col; /* used while drawing view content, column where next char will be drawn */
82 const SyntaxSymbol *symbols[SYNTAX_SYMBOL_LAST]; /* symbols to use for white spaces etc */
83 int tabwidth; /* how many spaces should be used to display a tab character */
84 Cursor *cursors; /* all cursors currently active */
85 Selection *selections; /* all selected regions */
86 lua_State *lua; /* lua state used for syntax highlighting */
87 char *lexer_name;
88 bool need_update; /* whether view has been redrawn */
89 bool large_file; /* optimize for displaying large files */
90 int colorcolumn;
93 static const SyntaxSymbol symbols_none[] = {
94 [SYNTAX_SYMBOL_SPACE] = { " " },
95 [SYNTAX_SYMBOL_TAB] = { " " },
96 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
97 [SYNTAX_SYMBOL_EOL] = { " " },
98 [SYNTAX_SYMBOL_EOF] = { "~" },
101 static const SyntaxSymbol symbols_default[] = {
102 [SYNTAX_SYMBOL_SPACE] = { "\xC2\xB7" },
103 [SYNTAX_SYMBOL_TAB] = { "\xE2\x96\xB6" },
104 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
105 [SYNTAX_SYMBOL_EOL] = { "\xE2\x8F\x8E" },
106 [SYNTAX_SYMBOL_EOF] = { "~" },
109 static Cell cell_unused;
110 static Cell cell_blank = { .data = " " };
112 static void view_clear(View *view);
113 static bool view_addch(View *view, Cell *cell);
114 static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol);
115 static void view_cursors_free(Cursor *c);
116 /* set/move current cursor position to a given (line, column) pair */
117 static size_t cursor_set(Cursor *cursor, Line *line, int col);
119 #if !CONFIG_LUA
121 static void view_syntax_color(View *view) { }
122 bool view_syntax_set(View *view, const char *name) { return false; }
124 #else
126 static void view_syntax_color(View *view) {
127 lua_State *L = view->lua;
128 if (!L || !view->lexer_name)
129 return;
130 lua_getglobal(L, "vis");
131 lua_getfield(L, -1, "lexers");
132 if (lua_isnil(L, -1))
133 return;
135 /* maximal number of bytes to consider for syntax highlighting before
136 * the visible area */
137 const size_t lexer_before_max = 16384;
138 /* absolute position to start syntax highlighting */
139 const size_t lexer_start = view->start >= lexer_before_max ? view->start - lexer_before_max : 0;
140 /* number of bytes used for syntax highlighting before visible are */
141 const size_t lexer_before = view->start - lexer_start;
142 /* number of bytes to read in one go */
143 const size_t text_size = lexer_before + (view->end - view->start);
144 /* current buffer to work with */
145 char text[text_size+1];
146 /* bytes to process */
147 const size_t text_len = text_bytes_get(view->text, lexer_start, text_size, text);
148 /* NUL terminate text section */
149 text[text_len] = '\0';
151 lua_getfield(L, -1, "load");
152 lua_pushstring(L, view->lexer_name);
153 lua_pcall(L, 1, 1, 0);
155 lua_getfield(L, -1, "_TOKENSTYLES");
156 lua_getfield(L, -2, "lex");
158 lua_pushvalue(L, -3); /* lexer obj */
160 const char *lex_text = text;
161 if (lexer_start > 0) {
162 /* try to start lexing at a line boundry */
163 /* TODO: start at known state, handle nested lexers */
164 const char *newline = memchr(text, '\n', lexer_before);
165 if (newline)
166 lex_text = newline;
169 lua_pushlstring(L, lex_text, text_len - (lex_text - text));
170 lua_pushinteger(L, 1 /* inital style: whitespace */);
172 int token_count;
174 if (lua_isfunction(L, -4) && !lua_pcall(L, 3, 1, 0) && lua_istable(L, -1) &&
175 (token_count = lua_objlen(L, -1)) > 0) {
177 size_t pos = lexer_before - (lex_text - text);
178 Line *line = view->topline;
179 int col = 0;
181 for (int i = 1; i < token_count; i += 2) {
182 lua_rawgeti(L, -1, i);
183 //const char *name = lua_tostring(L, -1);
184 lua_gettable(L, -3); /* _TOKENSTYLES[token] */
185 size_t token_style = lua_tointeger(L, -1);
186 lua_pop(L, 1); /* style */
187 lua_rawgeti(L, -1, i + 1);
188 size_t token_end = lua_tointeger(L, -1) - 1;
189 lua_pop(L, 1); /* pos */
191 for (bool token_next = false; line; line = line->next, col = 0) {
192 for (; col < line->width; col++) {
193 if (pos < token_end) {
194 line->cells[col].style = token_style;
195 pos += line->cells[col].len;
196 } else {
197 token_next = true;
198 break;
201 if (token_next)
202 break;
205 lua_pop(L, 1);
208 lua_pop(L, 3); /* _TOKENSTYLES, language specific lexer, lexers global */
211 bool view_syntax_set(View *view, const char *name) {
212 lua_State *L = view->lua;
213 if (!L)
214 return name == NULL;
216 lua_getglobal(L, "vis");
217 lua_getfield(L, -1, "lexers");
219 static const struct {
220 enum UiStyles id;
221 const char *name;
222 } styles[] = {
223 { UI_STYLE_DEFAULT, "STYLE_DEFAULT" },
224 { UI_STYLE_CURSOR, "STYLE_CURSOR" },
225 { UI_STYLE_CURSOR_PRIMARY, "STYLE_CURSOR_PRIMARY" },
226 { UI_STYLE_CURSOR_LINE, "STYLE_CURSOR_LINE" },
227 { UI_STYLE_SELECTION, "STYLE_SELECTION" },
228 { UI_STYLE_LINENUMBER, "STYLE_LINENUMBER" },
229 { UI_STYLE_COLOR_COLUMN, "STYLE_COLOR_COLUMN" },
232 for (size_t i = 0; i < LENGTH(styles); i++) {
233 lua_getfield(L, -1, styles[i].name);
234 view->ui->syntax_style(view->ui, styles[i].id, lua_tostring(L, -1));
235 lua_pop(L, 1);
238 if (!name) {
239 free(view->lexer_name);
240 view->lexer_name = NULL;
241 return true;
244 /* Try to load the specified lexer and parse its token styles.
245 * Roughly equivalent to the following lua code:
247 * lang = vis.lexers.load(name)
248 * for token_name, id in pairs(lang._TOKENSTYLES) do
249 * ui->syntax_style(id, vis.lexers:get_style(lang, token_name);
251 lua_getfield(L, -1, "load");
252 lua_pushstring(L, name);
254 if (lua_pcall(L, 1, 1, 0))
255 return false;
257 if (!lua_istable(L, -1)) {
258 lua_pop(L, 2);
259 return false;
262 view->lexer_name = strdup(name);
263 /* loop through all _TOKENSTYLES and parse them */
264 lua_getfield(L, -1, "_TOKENSTYLES");
265 lua_pushnil(L); /* first key */
267 while (lua_next(L, -2)) {
268 size_t id = lua_tointeger(L, -1);
269 //const char *name = lua_tostring(L, -2);
270 lua_pop(L, 1); /* remove value (=id), keep key (=name) */
271 lua_getfield(L, -4, "get_style");
272 lua_pushvalue(L, -5); /* lexer */
273 lua_pushvalue(L, -5); /* lang */
274 lua_pushvalue(L, -4); /* token_name */
275 if (lua_pcall(L, 3, 1, 0))
276 return false;
277 const char *style = lua_tostring(L, -1);
278 //printf("%s\t%d\t%s\n", name, id, style);
279 view->ui->syntax_style(view->ui, id, style);
280 lua_pop(L, 1); /* style */
283 lua_pop(L, 4); /* _TOKENSTYLES, grammar, lexers, vis */
285 return true;
288 #endif
291 void view_tabwidth_set(View *view, int tabwidth) {
292 view->tabwidth = tabwidth;
293 view_draw(view);
296 /* reset internal view data structures (cell matrix, line offsets etc.) */
297 static void view_clear(View *view) {
298 memset(view->lines, 0, view->lines_size);
299 if (view->start != view->start_last) {
300 view->start_mark = text_mark_set(view->text, view->start);
301 } else {
302 size_t start = text_mark_get(view->text, view->start_mark);
303 if (start != EPOS)
304 view->start = start;
307 view->start_last = view->start;
308 view->topline = view->lines;
309 view->topline->lineno = view->large_file ? 1 : text_lineno_by_pos(view->text, view->start);
310 view->lastline = view->topline;
312 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
313 size_t end = view->height * line_size;
314 Line *prev = NULL;
315 for (size_t i = 0; i < end; i += line_size) {
316 Line *line = (Line*)(((char*)view->lines) + i);
317 line->prev = prev;
318 if (prev)
319 prev->next = line;
320 prev = line;
322 view->bottomline = prev ? prev : view->topline;
323 view->bottomline->next = NULL;
324 view->line = view->topline;
325 view->col = 0;
328 Filerange view_viewport_get(View *view) {
329 return (Filerange){ .start = view->start, .end = view->end };
332 /* try to add another character to the view, return whether there was space left */
333 static bool view_addch(View *view, Cell *cell) {
334 if (!view->line)
335 return false;
337 int width;
338 size_t lineno = view->line->lineno;
339 unsigned char ch = (unsigned char)cell->data[0];
341 switch (ch) {
342 case '\t':
343 cell->width = 1;
344 width = view->tabwidth - (view->col % view->tabwidth);
345 for (int w = 0; w < width; w++) {
346 if (view->col + 1 > view->width) {
347 view->line = view->line->next;
348 view->col = 0;
349 if (!view->line)
350 return false;
351 view->line->lineno = lineno;
354 cell->len = w == 0 ? 1 : 0;
355 int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
356 strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
357 cell->style = view->symbols[t]->style;
358 view->line->cells[view->col] = *cell;
359 view->line->len += cell->len;
360 view->line->width += cell->width;
361 view->col++;
363 cell->len = 1;
364 return true;
365 case '\n':
366 cell->width = 1;
367 if (view->col + cell->width > view->width) {
368 view->line = view->line->next;
369 view->col = 0;
370 if (!view->line)
371 return false;
372 view->line->lineno = lineno;
375 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
376 cell->style = view->symbols[SYNTAX_SYMBOL_EOL]->style;
378 view->line->cells[view->col] = *cell;
379 view->line->len += cell->len;
380 view->line->width += cell->width;
381 for (int i = view->col + 1; i < view->width; i++)
382 view->line->cells[i] = cell_blank;
384 view->line = view->line->next;
385 if (view->line)
386 view->line->lineno = lineno + 1;
387 view->col = 0;
388 return true;
389 default:
390 if (ch < 128 && !isprint(ch)) {
391 /* non-printable ascii char, represent it as ^(char + 64) */
392 *cell = (Cell) {
393 .data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
394 .len = 1,
395 .width = 2,
396 .style = cell->style,
400 if (ch == ' ') {
401 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
402 cell->style = view->symbols[SYNTAX_SYMBOL_SPACE]->style;
406 if (view->col + cell->width > view->width) {
407 for (int i = view->col; i < view->width; i++)
408 view->line->cells[i] = cell_blank;
409 view->line = view->line->next;
410 view->col = 0;
413 if (view->line) {
414 view->line->width += cell->width;
415 view->line->len += cell->len;
416 view->line->lineno = lineno;
417 view->line->cells[view->col] = *cell;
418 view->col++;
419 /* set cells of a character which uses multiple columns */
420 for (int i = 1; i < cell->width; i++)
421 view->line->cells[view->col++] = cell_unused;
422 return true;
424 return false;
428 CursorPos view_cursor_getpos(View *view) {
429 Cursor *cursor = view->cursor;
430 Line *line = cursor->line;
431 CursorPos pos = { .line = line->lineno, .col = cursor->col };
432 while (line->prev && line->prev->lineno == pos.line) {
433 line = line->prev;
434 pos.col += line->width;
436 pos.col++;
437 return pos;
440 static void cursor_to(Cursor *c, size_t pos) {
441 Text *txt = c->view->text;
442 c->mark = text_mark_set(txt, pos);
443 if (pos != c->pos)
444 c->lastcol = 0;
445 c->pos = pos;
446 if (c->sel) {
447 size_t anchor = text_mark_get(txt, c->sel->anchor);
448 size_t cursor = text_mark_get(txt, c->sel->cursor);
449 /* do we have to change the orientation of the selection? */
450 if (pos < anchor && anchor < cursor) {
451 /* right extend -> left extend */
452 anchor = text_char_next(txt, anchor);
453 c->sel->anchor = text_mark_set(txt, anchor);
454 } else if (cursor < anchor && anchor <= pos) {
455 /* left extend -> right extend */
456 anchor = text_char_prev(txt, anchor);
457 c->sel->anchor = text_mark_set(txt, anchor);
459 if (anchor <= pos)
460 pos = text_char_next(txt, pos);
461 c->sel->cursor = text_mark_set(txt, pos);
463 if (!view_coord_get(c->view, pos, &c->line, &c->row, &c->col)) {
464 if (c->view->cursor == c) {
465 c->line = c->view->topline;
466 c->row = 0;
467 c->col = 0;
469 return;
471 // TODO: minimize number of redraws
472 view_draw(c->view);
475 static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
476 int row = 0, col = 0;
477 size_t cur = view->start;
478 Line *line = view->topline;
480 if (pos < view->start || pos > view->end) {
481 if (retline) *retline = NULL;
482 if (retrow) *retrow = -1;
483 if (retcol) *retcol = -1;
484 return false;
487 while (line && line != view->lastline && cur < pos) {
488 if (cur + line->len > pos)
489 break;
490 cur += line->len;
491 line = line->next;
492 row++;
495 if (line) {
496 int max_col = MIN(view->width, line->width);
497 while (cur < pos && col < max_col) {
498 cur += line->cells[col].len;
499 /* skip over columns occupied by the same character */
500 while (++col < max_col && line->cells[col].len == 0);
502 } else {
503 line = view->bottomline;
504 row = view->height - 1;
507 if (retline) *retline = line;
508 if (retrow) *retrow = row;
509 if (retcol) *retcol = col;
510 return true;
513 /* move the cursor to the character at pos bytes from the begining of the file.
514 * if pos is not in the current viewport, redraw the view to make it visible */
515 void view_cursor_to(View *view, size_t pos) {
516 view_cursors_to(view->cursor, pos);
519 /* redraw the complete with data starting from view->start bytes into the file.
520 * stop once the screen is full, update view->end, view->lastline */
521 void view_draw(View *view) {
522 view_clear(view);
523 /* read a screenful of text considering each character as 4-byte UTF character*/
524 const size_t text_size = view->width * view->height * 4;
525 /* current buffer to work with */
526 char text[text_size+1];
527 /* remaining bytes to process in buffer */
528 size_t rem = text_bytes_get(view->text, view->start, text_size, text);
529 /* NUL terminate text section */
530 text[rem] = '\0';
531 /* absolute position of character currently being added to display */
532 size_t pos = view->start;
533 /* current position into buffer from which to interpret a character */
534 char *cur = text;
535 /* start from known multibyte state */
536 mbstate_t mbstate = { 0 };
538 Cell cell = { 0 }, prev_cell = { 0 };
540 while (rem > 0) {
542 /* current 'parsed' character' */
543 wchar_t wchar;
545 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
546 if (len == (size_t)-1 && errno == EILSEQ) {
547 /* ok, we encountered an invalid multibyte sequence,
548 * replace it with the Unicode Replacement Character
549 * (FFFD) and skip until the start of the next utf8 char */
550 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
551 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
552 } else if (len == (size_t)-2) {
553 /* not enough bytes available to convert to a
554 * wide character. advance file position and read
555 * another junk into buffer.
557 rem = text_bytes_get(view->text, pos, text_size, text);
558 text[rem] = '\0';
559 cur = text;
560 continue;
561 } else if (len == 0) {
562 /* NUL byte encountered, store it and continue */
563 cell = (Cell){ .data = "\x00", .len = 1, .width = 2 };
564 } else {
565 for (size_t i = 0; i < len; i++)
566 cell.data[i] = cur[i];
567 cell.data[len] = '\0';
568 cell.len = len;
569 cell.width = wcwidth(wchar);
570 if (cell.width == -1)
571 cell.width = 1;
574 if (cur[0] == '\r' && rem > 1 && cur[1] == '\n') {
575 /* convert views style newline \r\n into a single char with len = 2 */
576 cell = (Cell){ .data = "\n", .len = 2, .width = 1 };
579 if (cell.width == 0 && prev_cell.len + cell.len < sizeof(cell.len)) {
580 prev_cell.len += cell.len;
581 strcat(prev_cell.data, cell.data);
582 } else {
583 if (prev_cell.len && !view_addch(view, &prev_cell))
584 break;
585 pos += prev_cell.len;
586 prev_cell = cell;
589 rem -= cell.len;
590 cur += cell.len;
592 memset(&cell, 0, sizeof cell);
595 if (prev_cell.len && view_addch(view, &prev_cell))
596 pos += prev_cell.len;
598 /* set end of vieviewg region */
599 view->end = pos;
600 view->lastline = view->line ? view->line : view->bottomline;
602 /* clear remaining of line, important to show cursor at end of file */
603 if (view->line) {
604 for (int x = view->col; x < view->width; x++)
605 view->line->cells[x] = cell_blank;
608 /* resync position of cursors within visible area */
609 for (Cursor *c = view->cursors; c; c = c->next) {
610 size_t pos = view_cursors_pos(c);
611 if (view_coord_get(view, pos, &c->line, &c->row, &c->col)) {
612 c->line->cells[c->col].cursor = true;
613 c->line->cells[c->col].cursor_primary = (c == view->cursor);
614 if (view->ui && !c->sel) {
615 Line *line_match; int col_match;
616 size_t pos_match = text_bracket_match_symbol(view->text, pos, "(){}[]\"'`");
617 if (pos != pos_match && view_coord_get(view, pos_match, &line_match, NULL, &col_match)) {
618 line_match->cells[col_match].selected = true;
621 } else if (c == view->cursor) {
622 c->line = view->topline;
623 c->row = 0;
624 c->col = 0;
628 view->need_update = true;
631 void view_update(View *view) {
632 if (!view->need_update)
633 return;
635 view_syntax_color(view);
637 if (view->colorcolumn > 0) {
638 size_t lineno = 0;
639 int line_cols = 0; /* Track the number of columns we've passed on each line */
640 bool line_cc_set = false; /* Has the colorcolumn attribute been set for this line yet */
642 for (Line *l = view->topline; l; l = l->next) {
643 if (l->lineno != lineno) {
644 line_cols = 0;
645 line_cc_set = false;
648 if (!line_cc_set) {
649 line_cols += view->width;
651 /* This screen line contains the cell we want to highlight */
652 if (line_cols >= view->colorcolumn) {
653 l->cells[(view->colorcolumn - 1) % view->width].style = UI_STYLE_COLOR_COLUMN;
654 line_cc_set = true;
658 lineno = l->lineno;
662 for (Line *l = view->lastline->next; l; l = l->next) {
663 strncpy(l->cells[0].data, view->symbols[SYNTAX_SYMBOL_EOF]->symbol, sizeof(l->cells[0].data));
664 l->cells[0].style = view->symbols[SYNTAX_SYMBOL_EOF]->style;
665 for (int x = 1; x < view->width; x++)
666 l->cells[x] = cell_blank;
667 l->width = 1;
668 l->len = 0;
671 for (Selection *s = view->selections; s; s = s->next) {
672 Filerange sel = view_selections_get(s);
673 if (text_range_valid(&sel)) {
674 Line *start_line; int start_col;
675 Line *end_line; int end_col;
676 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
677 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
678 if (start_line || end_line) {
679 if (!start_line) {
680 start_line = view->topline;
681 start_col = 0;
683 if (!end_line) {
684 end_line = view->lastline;
685 end_col = end_line->width;
687 for (Line *l = start_line; l != end_line->next; l = l->next) {
688 int col = (l == start_line) ? start_col : 0;
689 int end = (l == end_line) ? end_col : l->width;
690 while (col < end) {
691 l->cells[col++].selected = true;
698 if (view->ui)
699 view->ui->draw(view->ui);
700 view->need_update = false;
703 bool view_resize(View *view, int width, int height) {
704 if (width <= 0)
705 width = 1;
706 if (height <= 0)
707 height = 1;
708 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
709 if (lines_size > view->lines_size) {
710 Line *lines = realloc(view->lines, lines_size);
711 if (!lines)
712 return false;
713 view->lines = lines;
714 view->lines_size = lines_size;
716 view->width = width;
717 view->height = height;
718 memset(view->lines, 0, view->lines_size);
719 view_draw(view);
720 return true;
723 int view_height_get(View *view) {
724 return view->height;
727 int view_width_get(View *view) {
728 return view->width;
731 void view_free(View *view) {
732 if (!view)
733 return;
734 while (view->cursors)
735 view_cursors_free(view->cursors);
736 while (view->selections)
737 view_selections_free(view->selections);
738 free(view->lines);
739 free(view->lexer_name);
740 free(view);
743 void view_reload(View *view, Text *text) {
744 view->text = text;
745 view_selections_clear(view);
746 view_cursor_to(view, 0);
749 View *view_new(Text *text, lua_State *lua) {
750 if (!text)
751 return NULL;
752 View *view = calloc(1, sizeof(View));
753 if (!view)
754 return NULL;
755 if (!view_cursors_new(view)) {
756 view_free(view);
757 return NULL;
760 view->text = text;
761 view->lua = lua;
762 view->tabwidth = 8;
763 view_options_set(view, 0);
765 if (!view_resize(view, 1, 1)) {
766 view_free(view);
767 return NULL;
770 view_cursor_to(view, 0);
772 return view;
775 void view_ui(View *view, UiWin* ui) {
776 view->ui = ui;
779 static size_t cursor_set(Cursor *cursor, Line *line, int col) {
780 int row = 0;
781 View *view = cursor->view;
782 size_t pos = view->start;
783 /* get row number and file offset at start of the given line */
784 for (Line *cur = view->topline; cur && cur != line; cur = cur->next) {
785 pos += cur->len;
786 row++;
789 /* for characters which use more than 1 column, make sure we are on the left most */
790 while (col > 0 && line->cells[col].len == 0)
791 col--;
792 /* calculate offset within the line */
793 for (int i = 0; i < col; i++)
794 pos += line->cells[i].len;
796 cursor->col = col;
797 cursor->row = row;
798 cursor->line = line;
800 cursor_to(cursor, pos);
802 return pos;
805 bool view_viewport_down(View *view, int n) {
806 Line *line;
807 if (view->end == text_size(view->text))
808 return false;
809 if (n >= view->height) {
810 view->start = view->end;
811 } else {
812 for (line = view->topline; line && n > 0; line = line->next, n--)
813 view->start += line->len;
815 view_draw(view);
816 return true;
819 bool view_viewport_up(View *view, int n) {
820 /* scrolling up is somewhat tricky because we do not yet know where
821 * the lines start, therefore scan backwards but stop at a reasonable
822 * maximum in case we are dealing with a file without any newlines
824 if (view->start == 0)
825 return false;
826 size_t max = view->width * view->height;
827 char c;
828 Iterator it = text_iterator_get(view->text, view->start - 1);
830 if (!text_iterator_byte_get(&it, &c))
831 return false;
832 size_t off = 0;
833 /* skip newlines immediately before display area */
834 if (c == '\n' && text_iterator_byte_prev(&it, &c))
835 off++;
836 if (c == '\r' && text_iterator_byte_prev(&it, &c))
837 off++;
838 do {
839 if (c == '\n' && --n == 0)
840 break;
841 if (++off > max)
842 break;
843 } while (text_iterator_byte_prev(&it, &c));
844 if (c == '\r')
845 off++;
846 view->start -= off;
847 view_draw(view);
848 return true;
851 void view_redraw_top(View *view) {
852 Line *line = view->cursor->line;
853 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
854 view->start += cur->len;
855 view_draw(view);
856 view_cursor_to(view, view->cursor->pos);
859 void view_redraw_center(View *view) {
860 int center = view->height / 2;
861 size_t pos = view->cursor->pos;
862 for (int i = 0; i < 2; i++) {
863 int linenr = 0;
864 Line *line = view->cursor->line;
865 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
866 linenr++;
867 if (linenr < center) {
868 view_slide_down(view, center - linenr);
869 continue;
871 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
872 view->start += cur->len;
873 linenr--;
875 break;
877 view_draw(view);
878 view_cursor_to(view, pos);
881 void view_redraw_bottom(View *view) {
882 Line *line = view->cursor->line;
883 if (line == view->lastline)
884 return;
885 size_t pos = view->cursor->pos;
886 view_viewport_up(view, view->height);
887 while (pos > view->end && view_viewport_down(view, 1));
888 view_cursor_to(view, pos);
891 size_t view_slide_up(View *view, int lines) {
892 Cursor *cursor = view->cursor;
893 if (view_viewport_down(view, lines)) {
894 if (cursor->line == view->topline)
895 cursor_set(cursor, view->topline, cursor->col);
896 else
897 view_cursor_to(view, cursor->pos);
898 } else {
899 view_screenline_down(cursor);
901 return cursor->pos;
904 size_t view_slide_down(View *view, int lines) {
905 Cursor *cursor = view->cursor;
906 if (view_viewport_up(view, lines)) {
907 if (cursor->line == view->lastline)
908 cursor_set(cursor, view->lastline, cursor->col);
909 else
910 view_cursor_to(view, cursor->pos);
911 } else {
912 view_screenline_up(cursor);
914 return cursor->pos;
917 size_t view_scroll_up(View *view, int lines) {
918 Cursor *cursor = view->cursor;
919 if (view_viewport_up(view, lines)) {
920 Line *line = cursor->line < view->lastline ? cursor->line : view->lastline;
921 cursor_set(cursor, line, view->cursor->col);
922 } else {
923 view_cursor_to(view, 0);
925 return cursor->pos;
928 size_t view_scroll_down(View *view, int lines) {
929 Cursor *cursor = view->cursor;
930 if (view_viewport_down(view, lines)) {
931 Line *line = cursor->line > view->topline ? cursor->line : view->topline;
932 cursor_set(cursor, line, cursor->col);
933 } else {
934 view_cursor_to(view, text_size(view->text));
936 return cursor->pos;
939 size_t view_line_up(Cursor *cursor) {
940 if (cursor->line && cursor->line->prev && cursor->line->prev->prev &&
941 cursor->line->lineno != cursor->line->prev->lineno &&
942 cursor->line->prev->lineno != cursor->line->prev->prev->lineno)
943 return view_screenline_up(cursor);
944 int lastcol = cursor->lastcol;
945 if (!lastcol)
946 lastcol = cursor->col;
947 view_cursors_to(cursor, text_line_up(cursor->view->text, cursor->pos));
948 if (cursor->line)
949 cursor_set(cursor, cursor->line, lastcol);
950 cursor->lastcol = lastcol;
951 return cursor->pos;
954 size_t view_line_down(Cursor *cursor) {
955 if (cursor->line && (!cursor->line->next || cursor->line->next->lineno != cursor->line->lineno))
956 return view_screenline_down(cursor);
957 int lastcol = cursor->lastcol;
958 if (!lastcol)
959 lastcol = cursor->col;
960 view_cursors_to(cursor, text_line_down(cursor->view->text, cursor->pos));
961 if (cursor->line)
962 cursor_set(cursor, cursor->line, lastcol);
963 cursor->lastcol = lastcol;
964 return cursor->pos;
967 size_t view_screenline_up(Cursor *cursor) {
968 int lastcol = cursor->lastcol;
969 if (!lastcol)
970 lastcol = cursor->col;
971 if (!cursor->line->prev)
972 view_scroll_up(cursor->view, 1);
973 if (cursor->line->prev)
974 cursor_set(cursor, cursor->line->prev, lastcol);
975 cursor->lastcol = lastcol;
976 return cursor->pos;
979 size_t view_screenline_down(Cursor *cursor) {
980 int lastcol = cursor->lastcol;
981 if (!lastcol)
982 lastcol = cursor->col;
983 if (!cursor->line->next && cursor->line == cursor->view->bottomline)
984 view_scroll_down(cursor->view, 1);
985 if (cursor->line->next)
986 cursor_set(cursor, cursor->line->next, lastcol);
987 cursor->lastcol = lastcol;
988 return cursor->pos;
991 size_t view_screenline_begin(Cursor *cursor) {
992 if (!cursor->line)
993 return cursor->pos;
994 return cursor_set(cursor, cursor->line, 0);
997 size_t view_screenline_middle(Cursor *cursor) {
998 if (!cursor->line)
999 return cursor->pos;
1000 return cursor_set(cursor, cursor->line, cursor->line->width / 2);
1003 size_t view_screenline_end(Cursor *cursor) {
1004 if (!cursor->line)
1005 return cursor->pos;
1006 int col = cursor->line->width - 1;
1007 return cursor_set(cursor, cursor->line, col >= 0 ? col : 0);
1010 size_t view_cursor_get(View *view) {
1011 return view_cursors_pos(view->cursor);
1014 const Line *view_lines_get(View *view) {
1015 return view->topline;
1018 void view_scroll_to(View *view, size_t pos) {
1019 view_cursors_scroll_to(view->cursor, pos);
1022 const char *view_syntax_get(View *view) {
1023 return view->lexer_name;
1026 void view_options_set(View *view, enum UiOption options) {
1027 const int mapping[] = {
1028 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
1029 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
1030 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
1031 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
1032 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
1035 for (int i = 0; i < LENGTH(mapping); i++) {
1036 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
1037 &symbols_none[i];
1040 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
1041 options &= ~UI_OPTION_LARGE_FILE;
1043 view->large_file = (options & UI_OPTION_LARGE_FILE);
1045 if (view->ui)
1046 view->ui->options_set(view->ui, options);
1049 enum UiOption view_options_get(View *view) {
1050 return view->ui ? view->ui->options_get(view->ui) : 0;
1053 void view_colorcolumn_set(View *view, int col) {
1054 if (col >= 0)
1055 view->colorcolumn = col;
1058 int view_colorcolumn_get(View *view) {
1059 return view->colorcolumn;
1062 size_t view_screenline_goto(View *view, int n) {
1063 size_t pos = view->start;
1064 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
1065 pos += line->len;
1066 return pos;
1069 Cursor *view_cursors_new(View *view) {
1070 Cursor *c = calloc(1, sizeof(*c));
1071 if (!c)
1072 return NULL;
1074 c->view = view;
1075 c->next = view->cursors;
1076 if (view->cursors)
1077 view->cursors->prev = c;
1078 view->cursors = c;
1079 view->cursor = c;
1080 return c;
1083 int view_cursors_count(View *view) {
1084 int i = 0;
1085 for (Cursor *c = view_cursors(view); c; c = view_cursors_next(c))
1086 i++;
1087 return i;
1090 bool view_cursors_multiple(View *view) {
1091 return view->cursors && view->cursors->next;
1094 static void view_cursors_free(Cursor *c) {
1095 if (!c)
1096 return;
1097 register_release(&c->reg);
1098 if (c->prev)
1099 c->prev->next = c->next;
1100 if (c->next)
1101 c->next->prev = c->prev;
1102 if (c->view->cursors == c)
1103 c->view->cursors = c->next;
1104 if (c->view->cursor == c)
1105 c->view->cursor = c->next ? c->next : c->prev;
1106 free(c);
1109 void view_cursors_dispose(Cursor *c) {
1110 if (!c)
1111 return;
1112 View *view = c->view;
1113 if (view->cursors && view->cursors->next) {
1114 view_selections_free(c->sel);
1115 view_cursors_free(c);
1116 view_draw(view);
1120 Cursor *view_cursors(View *view) {
1121 return view->cursors;
1124 Cursor *view_cursors_primary_get(View *view) {
1125 return view->cursor;
1128 void view_cursors_primary_set(Cursor *c) {
1129 if (!c)
1130 return;
1131 View *view = c->view;
1132 view->cursor = c;
1133 Filerange sel = view_cursors_selection_get(c);
1134 view_cursors_to(c, view_cursors_pos(c));
1135 view_cursors_selection_set(c, &sel);
1138 Cursor *view_cursors_prev(Cursor *c) {
1139 return c->prev;
1142 Cursor *view_cursors_next(Cursor *c) {
1143 return c->next;
1146 size_t view_cursors_pos(Cursor *c) {
1147 return text_mark_get(c->view->text, c->mark);
1150 int view_cursors_cell_get(Cursor *c) {
1151 return c->line ? c->col : -1;
1154 int view_cursors_cell_set(Cursor *c, int cell) {
1155 if (!c->line || cell < 0)
1156 return -1;
1157 cursor_set(c, c->line, cell);
1158 return c->col;
1161 Register *view_cursors_register(Cursor *c) {
1162 return &c->reg;
1165 void view_cursors_scroll_to(Cursor *c, size_t pos) {
1166 View *view = c->view;
1167 if (view->cursor == c) {
1168 while (pos < view->start && view_viewport_up(view, 1));
1169 while (pos > view->end && view_viewport_down(view, 1));
1171 view_cursors_to(c, pos);
1174 void view_cursors_to(Cursor *c, size_t pos) {
1175 View *view = c->view;
1176 if (c->view->cursor == c) {
1177 c->mark = text_mark_set(view->text, pos);
1179 size_t max = text_size(view->text);
1180 if (pos == max && view->end < max) {
1181 /* do not display an empty screen when shoviewg the end of the file */
1182 view->start = pos;
1183 view_viewport_up(view, view->height / 2);
1184 } else {
1185 /* make sure we redraw changes to the very first character of the window */
1186 if (view->start == pos)
1187 view->start_last = 0;
1188 /* set the start of the viewable region to the start of the line on which
1189 * the cursor should be placed. if this line requires more space than
1190 * available in the view then simply start displaying text at the new
1191 * cursor position */
1192 for (int i = 0; i < 2 && (pos <= view->start || pos > view->end); i++) {
1193 view->start = i == 0 ? text_line_begin(view->text, pos) : pos;
1194 view_draw(view);
1199 cursor_to(c, pos);
1202 void view_cursors_selection_start(Cursor *c) {
1203 if (c->sel)
1204 return;
1205 size_t pos = view_cursors_pos(c);
1206 if (pos == EPOS || !(c->sel = view_selections_new(c->view)))
1207 return;
1208 Text *txt = c->view->text;
1209 c->sel->anchor = text_mark_set(txt, pos);
1210 c->sel->cursor = text_mark_set(txt, text_char_next(txt, pos));
1211 view_draw(c->view);
1214 void view_cursors_selection_restore(Cursor *c) {
1215 Text *txt = c->view->text;
1216 if (c->sel)
1217 return;
1218 Filerange sel = text_range_new(
1219 text_mark_get(txt, c->lastsel_anchor),
1220 text_mark_get(txt, c->lastsel_cursor)
1222 if (!text_range_valid(&sel))
1223 return;
1224 if (!(c->sel = view_selections_new(c->view)))
1225 return;
1226 view_selections_set(c->sel, &sel);
1227 view_cursors_selection_sync(c);
1228 view_draw(c->view);
1231 void view_cursors_selection_stop(Cursor *c) {
1232 c->sel = NULL;
1235 void view_cursors_selection_clear(Cursor *c) {
1236 view_selections_free(c->sel);
1237 view_draw(c->view);
1240 void view_cursors_selection_swap(Cursor *c) {
1241 if (!c->sel)
1242 return;
1243 view_selections_swap(c->sel);
1244 view_cursors_selection_sync(c);
1247 void view_cursors_selection_sync(Cursor *c) {
1248 if (!c->sel)
1249 return;
1250 Text *txt = c->view->text;
1251 size_t anchor = text_mark_get(txt, c->sel->anchor);
1252 size_t cursor = text_mark_get(txt, c->sel->cursor);
1253 bool right_extending = anchor < cursor;
1254 if (right_extending)
1255 cursor = text_char_prev(txt, cursor);
1256 view_cursors_to(c, cursor);
1259 Filerange view_cursors_selection_get(Cursor *c) {
1260 return view_selections_get(c->sel);
1263 void view_cursors_selection_set(Cursor *c, Filerange *r) {
1264 if (!text_range_valid(r))
1265 return;
1266 if (!c->sel)
1267 c->sel = view_selections_new(c->view);
1268 if (!c->sel)
1269 return;
1271 view_selections_set(c->sel, r);
1274 Selection *view_selections_new(View *view) {
1275 Selection *s = calloc(1, sizeof(*s));
1276 if (!s)
1277 return NULL;
1279 s->view = view;
1280 s->next = view->selections;
1281 if (view->selections)
1282 view->selections->prev = s;
1283 view->selections = s;
1284 return s;
1287 void view_selections_free(Selection *s) {
1288 if (!s)
1289 return;
1290 if (s->prev)
1291 s->prev->next = s->next;
1292 if (s->next)
1293 s->next->prev = s->prev;
1294 if (s->view->selections == s)
1295 s->view->selections = s->next;
1296 // XXX: add backlink Selection->Cursor?
1297 for (Cursor *c = s->view->cursors; c; c = c->next) {
1298 if (c->sel == s) {
1299 c->lastsel_anchor = s->anchor;
1300 c->lastsel_cursor = s->cursor;
1301 c->sel = NULL;
1304 free(s);
1307 void view_selections_clear(View *view) {
1308 while (view->selections)
1309 view_selections_free(view->selections);
1310 view_draw(view);
1313 void view_cursors_clear(View *view) {
1314 for (Cursor *c = view->cursors, *next; c; c = next) {
1315 next = c->next;
1316 if (c != view->cursor) {
1317 view_selections_free(c->sel);
1318 view_cursors_free(c);
1321 view_draw(view);
1324 void view_selections_swap(Selection *s) {
1325 Mark temp = s->anchor;
1326 s->anchor = s->cursor;
1327 s->cursor = temp;
1330 Selection *view_selections(View *view) {
1331 return view->selections;
1334 Selection *view_selections_prev(Selection *s) {
1335 return s->prev;
1338 Selection *view_selections_next(Selection *s) {
1339 return s->next;
1342 Filerange view_selection_get(View *view) {
1343 return view_selections_get(view->cursor->sel);
1346 Filerange view_selections_get(Selection *s) {
1347 if (!s)
1348 return text_range_empty();
1349 Text *txt = s->view->text;
1350 size_t anchor = text_mark_get(txt, s->anchor);
1351 size_t cursor = text_mark_get(txt, s->cursor);
1352 return text_range_new(anchor, cursor);
1355 void view_selections_set(Selection *s, Filerange *r) {
1356 if (!text_range_valid(r))
1357 return;
1358 Text *txt = s->view->text;
1359 size_t anchor = text_mark_get(txt, s->anchor);
1360 size_t cursor = text_mark_get(txt, s->cursor);
1361 bool left_extending = anchor > cursor;
1362 if (left_extending) {
1363 s->anchor = text_mark_set(txt, r->end);
1364 s->cursor = text_mark_set(txt, r->start);
1365 } else {
1366 s->anchor = text_mark_set(txt, r->start);
1367 s->cursor = text_mark_set(txt, r->end);
1369 view_draw(s->view);
1372 Text *view_text(View *view) {
1373 return view->text;