vis-lua: expose win.syntax
[vis.git] / view.c
bloba5778267a217aa054ad0ecc8cac28a5d3ed2de21
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].attr = 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 if (!name) {
213 free(view->lexer_name);
214 view->lexer_name = NULL;
215 return true;
218 lua_State *L = view->lua;
219 if (!L)
220 return false;
222 /* Try to load the specified lexer and parse its token styles.
223 * Roughly equivalent to the following lua code:
225 * lang = vis.lexers.load(name)
226 * for token_name, id in pairs(lang._TOKENSTYLES) do
227 * ui->syntax_style(id, vis.lexers:get_style(lang, token_name);
229 lua_getglobal(L, "vis");
230 lua_getfield(L, -1, "lexers");
232 lua_getfield(L, -1, "STYLE_DEFAULT");
233 view->ui->syntax_style(view->ui, UI_STYLE_DEFAULT, lua_tostring(L, -1));
234 lua_pop(L, 1);
235 lua_getfield(L, -1, "STYLE_CURSOR");
236 view->ui->syntax_style(view->ui, UI_STYLE_CURSOR, lua_tostring(L, -1));
237 lua_pop(L, 1);
238 lua_getfield(L, -1, "STYLE_CURSOR_LINE");
239 view->ui->syntax_style(view->ui, UI_STYLE_CURSOR_LINE, lua_tostring(L, -1));
240 lua_pop(L, 1);
241 lua_getfield(L, -1, "STYLE_SELECTION");
242 view->ui->syntax_style(view->ui, UI_STYLE_SELECTION, lua_tostring(L, -1));
243 lua_pop(L, 1);
244 lua_getfield(L, -1, "STYLE_LINENUMBER");
245 view->ui->syntax_style(view->ui, UI_STYLE_LINENUMBER, lua_tostring(L, -1));
246 lua_pop(L, 1);
247 lua_getfield(L, -1, "STYLE_COLOR_COLUMN");
248 view->ui->syntax_style(view->ui, UI_STYLE_COLOR_COLUMN, lua_tostring(L, -1));
249 lua_pop(L, 1);
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->attr = 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->attr = 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 .attr = cell->attr,
400 if (ch == ' ') {
401 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
402 cell->attr = 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 */
524 const size_t text_size = view->width * view->height;
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 if (view->ui && !c->sel) {
614 Line *line_match; int col_match;
615 size_t pos_match = text_bracket_match_symbol(view->text, pos, "(){}[]\"'`");
616 if (pos != pos_match && view_coord_get(view, pos_match, &line_match, NULL, &col_match)) {
617 line_match->cells[col_match].selected = true;
620 } else if (c == view->cursor) {
621 c->line = view->topline;
622 c->row = 0;
623 c->col = 0;
627 view->need_update = true;
630 void view_update(View *view) {
631 if (!view->need_update)
632 return;
634 view_syntax_color(view);
636 if (view->colorcolumn > 0 && view->colorcolumn <= view->width) {
637 size_t lineno = 0;
638 for (Line *l = view->topline; l; l = l->next) {
639 if (l->lineno != lineno)
640 l->cells[view->colorcolumn-1].attr = UI_STYLE_COLOR_COLUMN;
641 lineno = l->lineno;
645 for (Line *l = view->lastline->next; l; l = l->next) {
646 strncpy(l->cells[0].data, view->symbols[SYNTAX_SYMBOL_EOF]->symbol, sizeof(l->cells[0].data));
647 l->cells[0].attr = view->symbols[SYNTAX_SYMBOL_EOF]->style;
648 for (int x = 1; x < view->width; x++)
649 l->cells[x] = cell_blank;
650 l->width = 1;
651 l->len = 0;
654 for (Selection *s = view->selections; s; s = s->next) {
655 Filerange sel = view_selections_get(s);
656 if (text_range_valid(&sel)) {
657 Line *start_line; int start_col;
658 Line *end_line; int end_col;
659 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
660 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
661 if (start_line || end_line) {
662 if (!start_line) {
663 start_line = view->topline;
664 start_col = 0;
666 if (!end_line) {
667 end_line = view->lastline;
668 end_col = end_line->width;
670 for (Line *l = start_line; l != end_line->next; l = l->next) {
671 int col = (l == start_line) ? start_col : 0;
672 int end = (l == end_line) ? end_col : l->width;
673 while (col < end) {
674 l->cells[col++].selected = true;
681 if (view->ui)
682 view->ui->draw(view->ui);
683 view->need_update = false;
686 bool view_resize(View *view, int width, int height) {
687 if (width <= 0)
688 width = 1;
689 if (height <= 0)
690 height = 1;
691 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
692 if (lines_size > view->lines_size) {
693 Line *lines = realloc(view->lines, lines_size);
694 if (!lines)
695 return false;
696 view->lines = lines;
697 view->lines_size = lines_size;
699 view->width = width;
700 view->height = height;
701 memset(view->lines, 0, view->lines_size);
702 view_draw(view);
703 return true;
706 int view_height_get(View *view) {
707 return view->height;
710 int view_width_get(View *view) {
711 return view->width;
714 void view_free(View *view) {
715 if (!view)
716 return;
717 while (view->cursors)
718 view_cursors_free(view->cursors);
719 while (view->selections)
720 view_selections_free(view->selections);
721 free(view->lines);
722 free(view->lexer_name);
723 free(view);
726 void view_reload(View *view, Text *text) {
727 view->text = text;
728 view_selections_clear(view);
729 view_cursor_to(view, 0);
732 View *view_new(Text *text, lua_State *lua) {
733 if (!text)
734 return NULL;
735 View *view = calloc(1, sizeof(View));
736 if (!view)
737 return NULL;
738 if (!view_cursors_new(view)) {
739 view_free(view);
740 return NULL;
743 view->text = text;
744 view->lua = lua;
745 view->tabwidth = 8;
746 view_options_set(view, 0);
748 if (!view_resize(view, 1, 1)) {
749 view_free(view);
750 return NULL;
753 view_cursor_to(view, 0);
755 return view;
758 void view_ui(View *view, UiWin* ui) {
759 view->ui = ui;
762 static size_t cursor_set(Cursor *cursor, Line *line, int col) {
763 int row = 0;
764 View *view = cursor->view;
765 size_t pos = view->start;
766 /* get row number and file offset at start of the given line */
767 for (Line *cur = view->topline; cur && cur != line; cur = cur->next) {
768 pos += cur->len;
769 row++;
772 /* for characters which use more than 1 column, make sure we are on the left most */
773 while (col > 0 && line->cells[col].len == 0)
774 col--;
775 /* calculate offset within the line */
776 for (int i = 0; i < col; i++)
777 pos += line->cells[i].len;
779 cursor->col = col;
780 cursor->row = row;
781 cursor->line = line;
783 cursor_to(cursor, pos);
785 return pos;
788 bool view_viewport_down(View *view, int n) {
789 Line *line;
790 if (view->end == text_size(view->text))
791 return false;
792 if (n >= view->height) {
793 view->start = view->end;
794 } else {
795 for (line = view->topline; line && n > 0; line = line->next, n--)
796 view->start += line->len;
798 view_draw(view);
799 return true;
802 bool view_viewport_up(View *view, int n) {
803 /* scrolling up is somewhat tricky because we do not yet know where
804 * the lines start, therefore scan backwards but stop at a reasonable
805 * maximum in case we are dealing with a file without any newlines
807 if (view->start == 0)
808 return false;
809 size_t max = view->width * view->height;
810 char c;
811 Iterator it = text_iterator_get(view->text, view->start - 1);
813 if (!text_iterator_byte_get(&it, &c))
814 return false;
815 size_t off = 0;
816 /* skip newlines immediately before display area */
817 if (c == '\n' && text_iterator_byte_prev(&it, &c))
818 off++;
819 if (c == '\r' && text_iterator_byte_prev(&it, &c))
820 off++;
821 do {
822 if (c == '\n' && --n == 0)
823 break;
824 if (++off > max)
825 break;
826 } while (text_iterator_byte_prev(&it, &c));
827 if (c == '\r')
828 off++;
829 view->start -= off;
830 view_draw(view);
831 return true;
834 void view_redraw_top(View *view) {
835 Line *line = view->cursor->line;
836 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
837 view->start += cur->len;
838 view_draw(view);
839 view_cursor_to(view, view->cursor->pos);
842 void view_redraw_center(View *view) {
843 int center = view->height / 2;
844 size_t pos = view->cursor->pos;
845 for (int i = 0; i < 2; i++) {
846 int linenr = 0;
847 Line *line = view->cursor->line;
848 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
849 linenr++;
850 if (linenr < center) {
851 view_slide_down(view, center - linenr);
852 continue;
854 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
855 view->start += cur->len;
856 linenr--;
858 break;
860 view_draw(view);
861 view_cursor_to(view, pos);
864 void view_redraw_bottom(View *view) {
865 Line *line = view->cursor->line;
866 if (line == view->lastline)
867 return;
868 size_t pos = view->cursor->pos;
869 view_viewport_up(view, view->height);
870 while (pos > view->end && view_viewport_down(view, 1));
871 view_cursor_to(view, pos);
874 size_t view_slide_up(View *view, int lines) {
875 Cursor *cursor = view->cursor;
876 if (view_viewport_down(view, lines)) {
877 if (cursor->line == view->topline)
878 cursor_set(cursor, view->topline, cursor->col);
879 else
880 view_cursor_to(view, cursor->pos);
881 } else {
882 view_screenline_down(cursor);
884 return cursor->pos;
887 size_t view_slide_down(View *view, int lines) {
888 Cursor *cursor = view->cursor;
889 if (view_viewport_up(view, lines)) {
890 if (cursor->line == view->lastline)
891 cursor_set(cursor, view->lastline, cursor->col);
892 else
893 view_cursor_to(view, cursor->pos);
894 } else {
895 view_screenline_up(cursor);
897 return cursor->pos;
900 size_t view_scroll_up(View *view, int lines) {
901 Cursor *cursor = view->cursor;
902 if (view_viewport_up(view, lines)) {
903 Line *line = cursor->line < view->lastline ? cursor->line : view->lastline;
904 cursor_set(cursor, line, view->cursor->col);
905 } else {
906 view_cursor_to(view, 0);
908 return cursor->pos;
911 size_t view_scroll_down(View *view, int lines) {
912 Cursor *cursor = view->cursor;
913 if (view_viewport_down(view, lines)) {
914 Line *line = cursor->line > view->topline ? cursor->line : view->topline;
915 cursor_set(cursor, line, cursor->col);
916 } else {
917 view_cursor_to(view, text_size(view->text));
919 return cursor->pos;
922 size_t view_line_up(Cursor *cursor) {
923 if (cursor->line && cursor->line->prev && cursor->line->prev->prev &&
924 cursor->line->lineno != cursor->line->prev->lineno &&
925 cursor->line->prev->lineno != cursor->line->prev->prev->lineno)
926 return view_screenline_up(cursor);
927 int lastcol = cursor->lastcol;
928 if (!lastcol)
929 lastcol = cursor->col;
930 view_cursors_to(cursor, text_line_up(cursor->view->text, cursor->pos));
931 if (cursor->line)
932 cursor_set(cursor, cursor->line, lastcol);
933 cursor->lastcol = lastcol;
934 return cursor->pos;
937 size_t view_line_down(Cursor *cursor) {
938 if (cursor->line && (!cursor->line->next || cursor->line->next->lineno != cursor->line->lineno))
939 return view_screenline_down(cursor);
940 int lastcol = cursor->lastcol;
941 if (!lastcol)
942 lastcol = cursor->col;
943 view_cursors_to(cursor, text_line_down(cursor->view->text, cursor->pos));
944 if (cursor->line)
945 cursor_set(cursor, cursor->line, lastcol);
946 cursor->lastcol = lastcol;
947 return cursor->pos;
950 size_t view_screenline_up(Cursor *cursor) {
951 int lastcol = cursor->lastcol;
952 if (!lastcol)
953 lastcol = cursor->col;
954 if (!cursor->line->prev)
955 view_scroll_up(cursor->view, 1);
956 if (cursor->line->prev)
957 cursor_set(cursor, cursor->line->prev, lastcol);
958 cursor->lastcol = lastcol;
959 return cursor->pos;
962 size_t view_screenline_down(Cursor *cursor) {
963 int lastcol = cursor->lastcol;
964 if (!lastcol)
965 lastcol = cursor->col;
966 if (!cursor->line->next && cursor->line == cursor->view->bottomline)
967 view_scroll_down(cursor->view, 1);
968 if (cursor->line->next)
969 cursor_set(cursor, cursor->line->next, lastcol);
970 cursor->lastcol = lastcol;
971 return cursor->pos;
974 size_t view_screenline_begin(Cursor *cursor) {
975 if (!cursor->line)
976 return cursor->pos;
977 return cursor_set(cursor, cursor->line, 0);
980 size_t view_screenline_middle(Cursor *cursor) {
981 if (!cursor->line)
982 return cursor->pos;
983 return cursor_set(cursor, cursor->line, cursor->line->width / 2);
986 size_t view_screenline_end(Cursor *cursor) {
987 if (!cursor->line)
988 return cursor->pos;
989 int col = cursor->line->width - 1;
990 return cursor_set(cursor, cursor->line, col >= 0 ? col : 0);
993 size_t view_cursor_get(View *view) {
994 return view_cursors_pos(view->cursor);
997 const Line *view_lines_get(View *view) {
998 return view->topline;
1001 void view_scroll_to(View *view, size_t pos) {
1002 view_cursors_scroll_to(view->cursor, pos);
1005 const char *view_syntax_get(View *view) {
1006 return view->lexer_name;
1009 void view_options_set(View *view, enum UiOption options) {
1010 const int mapping[] = {
1011 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
1012 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
1013 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
1014 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
1015 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
1018 for (int i = 0; i < LENGTH(mapping); i++) {
1019 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
1020 &symbols_none[i];
1023 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
1024 options &= ~UI_OPTION_LARGE_FILE;
1026 view->large_file = (options & UI_OPTION_LARGE_FILE);
1028 if (view->ui)
1029 view->ui->options_set(view->ui, options);
1032 enum UiOption view_options_get(View *view) {
1033 return view->ui ? view->ui->options_get(view->ui) : 0;
1036 void view_colorcolumn_set(View *view, int col) {
1037 if (col >= 0)
1038 view->colorcolumn = col;
1041 int view_colorcolumn_get(View *view) {
1042 return view->colorcolumn;
1045 size_t view_screenline_goto(View *view, int n) {
1046 size_t pos = view->start;
1047 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
1048 pos += line->len;
1049 return pos;
1052 Cursor *view_cursors_new(View *view) {
1053 Cursor *c = calloc(1, sizeof(*c));
1054 if (!c)
1055 return NULL;
1057 c->view = view;
1058 c->next = view->cursors;
1059 if (view->cursors)
1060 view->cursors->prev = c;
1061 view->cursors = c;
1062 view->cursor = c;
1063 return c;
1066 int view_cursors_count(View *view) {
1067 int i = 0;
1068 for (Cursor *c = view_cursors(view); c; c = view_cursors_next(c))
1069 i++;
1070 return i;
1073 static void view_cursors_free(Cursor *c) {
1074 if (!c)
1075 return;
1076 register_release(&c->reg);
1077 if (c->prev)
1078 c->prev->next = c->next;
1079 if (c->next)
1080 c->next->prev = c->prev;
1081 if (c->view->cursors == c)
1082 c->view->cursors = c->next;
1083 if (c->view->cursor == c)
1084 c->view->cursor = c->next ? c->next : c->prev;
1085 free(c);
1088 void view_cursors_dispose(Cursor *c) {
1089 if (!c)
1090 return;
1091 View *view = c->view;
1092 if (view->cursors && view->cursors->next) {
1093 view_selections_free(c->sel);
1094 view_cursors_free(c);
1095 view_draw(view);
1099 Cursor *view_cursors(View *view) {
1100 return view->cursors;
1103 Cursor *view_cursor(View *view) {
1104 return view->cursor;
1107 Cursor *view_cursors_prev(Cursor *c) {
1108 return c->prev;
1111 Cursor *view_cursors_next(Cursor *c) {
1112 return c->next;
1115 size_t view_cursors_pos(Cursor *c) {
1116 return text_mark_get(c->view->text, c->mark);
1119 int view_cursors_cell_get(Cursor *c) {
1120 return c->line ? c->col : -1;
1123 int view_cursors_cell_set(Cursor *c, int cell) {
1124 if (!c->line || cell < 0)
1125 return -1;
1126 cursor_set(c, c->line, cell);
1127 return c->col;
1130 Register *view_cursors_register(Cursor *c) {
1131 return &c->reg;
1134 void view_cursors_scroll_to(Cursor *c, size_t pos) {
1135 View *view = c->view;
1136 if (view->cursor == c) {
1137 while (pos < view->start && view_viewport_up(view, 1));
1138 while (pos > view->end && view_viewport_down(view, 1));
1140 view_cursors_to(c, pos);
1143 void view_cursors_to(Cursor *c, size_t pos) {
1144 View *view = c->view;
1145 if (c->view->cursors == c) {
1146 c->mark = text_mark_set(view->text, pos);
1148 size_t max = text_size(view->text);
1149 if (pos == max && view->end < max) {
1150 /* do not display an empty screen when shoviewg the end of the file */
1151 view->start = pos;
1152 view_viewport_up(view, view->height / 2);
1153 } else {
1154 /* make sure we redraw changes to the very first character of the window */
1155 if (view->start == pos)
1156 view->start_last = 0;
1157 /* set the start of the viewable region to the start of the line on which
1158 * the cursor should be placed. if this line requires more space than
1159 * available in the view then simply start displaying text at the new
1160 * cursor position */
1161 for (int i = 0; i < 2 && (pos <= view->start || pos > view->end); i++) {
1162 view->start = i == 0 ? text_line_begin(view->text, pos) : pos;
1163 view_draw(view);
1168 cursor_to(c, pos);
1171 void view_cursors_selection_start(Cursor *c) {
1172 if (c->sel)
1173 return;
1174 size_t pos = view_cursors_pos(c);
1175 if (pos == EPOS || !(c->sel = view_selections_new(c->view)))
1176 return;
1177 Text *txt = c->view->text;
1178 c->sel->anchor = text_mark_set(txt, pos);
1179 c->sel->cursor = text_mark_set(txt, text_char_next(txt, pos));
1180 view_draw(c->view);
1183 void view_cursors_selection_restore(Cursor *c) {
1184 Text *txt = c->view->text;
1185 if (c->sel)
1186 return;
1187 Filerange sel = text_range_new(
1188 text_mark_get(txt, c->lastsel_anchor),
1189 text_mark_get(txt, c->lastsel_cursor)
1191 if (!text_range_valid(&sel))
1192 return;
1193 if (!(c->sel = view_selections_new(c->view)))
1194 return;
1195 view_selections_set(c->sel, &sel);
1196 view_cursors_selection_sync(c);
1197 view_draw(c->view);
1200 void view_cursors_selection_stop(Cursor *c) {
1201 c->sel = NULL;
1204 void view_cursors_selection_clear(Cursor *c) {
1205 view_selections_free(c->sel);
1206 view_draw(c->view);
1209 void view_cursors_selection_swap(Cursor *c) {
1210 if (!c->sel)
1211 return;
1212 view_selections_swap(c->sel);
1213 view_cursors_selection_sync(c);
1216 void view_cursors_selection_sync(Cursor *c) {
1217 if (!c->sel)
1218 return;
1219 Text *txt = c->view->text;
1220 size_t anchor = text_mark_get(txt, c->sel->anchor);
1221 size_t cursor = text_mark_get(txt, c->sel->cursor);
1222 bool right_extending = anchor < cursor;
1223 if (right_extending)
1224 cursor = text_char_prev(txt, cursor);
1225 view_cursors_to(c, cursor);
1228 Filerange view_cursors_selection_get(Cursor *c) {
1229 return view_selections_get(c->sel);
1232 void view_cursors_selection_set(Cursor *c, Filerange *r) {
1233 if (!text_range_valid(r))
1234 return;
1235 if (!c->sel)
1236 c->sel = view_selections_new(c->view);
1237 if (!c->sel)
1238 return;
1240 view_selections_set(c->sel, r);
1243 Selection *view_selections_new(View *view) {
1244 Selection *s = calloc(1, sizeof(*s));
1245 if (!s)
1246 return NULL;
1248 s->view = view;
1249 s->next = view->selections;
1250 if (view->selections)
1251 view->selections->prev = s;
1252 view->selections = s;
1253 return s;
1256 void view_selections_free(Selection *s) {
1257 if (!s)
1258 return;
1259 if (s->prev)
1260 s->prev->next = s->next;
1261 if (s->next)
1262 s->next->prev = s->prev;
1263 if (s->view->selections == s)
1264 s->view->selections = s->next;
1265 // XXX: add backlink Selection->Cursor?
1266 for (Cursor *c = s->view->cursors; c; c = c->next) {
1267 if (c->sel == s) {
1268 c->lastsel_anchor = s->anchor;
1269 c->lastsel_cursor = s->cursor;
1270 c->sel = NULL;
1273 free(s);
1276 void view_selections_clear(View *view) {
1277 while (view->selections)
1278 view_selections_free(view->selections);
1279 view_draw(view);
1282 void view_cursors_clear(View *view) {
1283 for (Cursor *c = view->cursors, *next; c; c = next) {
1284 next = c->next;
1285 if (c != view->cursor) {
1286 view_selections_free(c->sel);
1287 view_cursors_free(c);
1290 view_draw(view);
1293 void view_selections_swap(Selection *s) {
1294 Mark temp = s->anchor;
1295 s->anchor = s->cursor;
1296 s->cursor = temp;
1299 Selection *view_selections(View *view) {
1300 return view->selections;
1303 Selection *view_selections_prev(Selection *s) {
1304 return s->prev;
1307 Selection *view_selections_next(Selection *s) {
1308 return s->next;
1311 Filerange view_selection_get(View *view) {
1312 return view_selections_get(view->cursor->sel);
1315 Filerange view_selections_get(Selection *s) {
1316 if (!s)
1317 return text_range_empty();
1318 Text *txt = s->view->text;
1319 size_t anchor = text_mark_get(txt, s->anchor);
1320 size_t cursor = text_mark_get(txt, s->cursor);
1321 return text_range_new(anchor, cursor);
1324 void view_selections_set(Selection *s, Filerange *r) {
1325 if (!text_range_valid(r))
1326 return;
1327 Text *txt = s->view->text;
1328 size_t anchor = text_mark_get(txt, s->anchor);
1329 size_t cursor = text_mark_get(txt, s->cursor);
1330 bool left_extending = anchor > cursor;
1331 if (left_extending) {
1332 s->anchor = text_mark_set(txt, r->end);
1333 s->cursor = text_mark_set(txt, r->start);
1334 } else {
1335 s->anchor = text_mark_set(txt, r->start);
1336 s->cursor = text_mark_set(txt, r->end);
1338 view_draw(s->view);
1341 Text *view_text(View *view) {
1342 return view->text;