vis: allow :-commands containing a hyphen
[vis.git] / view.c
blobdb39de7af6b8a752c458c4681092a9a952f4fd49
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 int colorcolumn;
92 static const SyntaxSymbol symbols_none[] = {
93 [SYNTAX_SYMBOL_SPACE] = { " " },
94 [SYNTAX_SYMBOL_TAB] = { " " },
95 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
96 [SYNTAX_SYMBOL_EOL] = { " " },
97 [SYNTAX_SYMBOL_EOF] = { "~" },
100 static const SyntaxSymbol symbols_default[] = {
101 [SYNTAX_SYMBOL_SPACE] = { "\xC2\xB7" },
102 [SYNTAX_SYMBOL_TAB] = { "\xE2\x96\xB6" },
103 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
104 [SYNTAX_SYMBOL_EOL] = { "\xE2\x8F\x8E" },
105 [SYNTAX_SYMBOL_EOF] = { "~" },
108 static Cell cell_unused;
109 static Cell cell_blank = { .data = " " };
111 static void view_clear(View *view);
112 static bool view_addch(View *view, Cell *cell);
113 static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol);
114 static void view_cursors_free(Cursor *c);
115 /* set/move current cursor position to a given (line, column) pair */
116 static size_t cursor_set(Cursor *cursor, Line *line, int col);
118 #if !CONFIG_LUA
120 static void view_syntax_color(View *view) { }
121 bool view_syntax_set(View *view, const char *name) { return false; }
123 #else
125 static void view_syntax_color(View *view) {
126 lua_State *L = view->lua;
127 if (!L || !view->lexer_name)
128 return;
129 lua_getglobal(L, "vis");
130 lua_getfield(L, -1, "lexers");
131 if (lua_isnil(L, -1))
132 return;
134 /* maximal number of bytes to consider for syntax highlighting before
135 * the visible area */
136 const size_t lexer_before_max = 16384;
137 /* absolute position to start syntax highlighting */
138 const size_t lexer_start = view->start >= lexer_before_max ? view->start - lexer_before_max : 0;
139 /* number of bytes used for syntax highlighting before visible are */
140 const size_t lexer_before = view->start - lexer_start;
141 /* number of bytes to read in one go */
142 const size_t text_size = lexer_before + (view->end - view->start);
143 /* current buffer to work with */
144 char text[text_size+1];
145 /* bytes to process */
146 const size_t text_len = text_bytes_get(view->text, lexer_start, text_size, text);
147 /* NUL terminate text section */
148 text[text_len] = '\0';
150 lua_getfield(L, -1, "load");
151 lua_pushstring(L, view->lexer_name);
152 lua_pcall(L, 1, 1, 0);
154 lua_getfield(L, -1, "_TOKENSTYLES");
155 lua_getfield(L, -2, "lex");
157 lua_pushvalue(L, -3); /* lexer obj */
159 const char *lex_text = text;
160 if (lexer_start > 0) {
161 /* try to start lexing at a line boundry */
162 /* TODO: start at known state, handle nested lexers */
163 const char *newline = memchr(text, '\n', lexer_before);
164 if (newline)
165 lex_text = newline;
168 lua_pushlstring(L, lex_text, text_len - (lex_text - text));
169 lua_pushinteger(L, 1 /* inital style: whitespace */);
171 int token_count;
173 if (lua_isfunction(L, -4) && !lua_pcall(L, 3, 1, 0) && lua_istable(L, -1) &&
174 (token_count = lua_objlen(L, -1)) > 0) {
176 size_t pos = lexer_before - (lex_text - text);
177 Line *line = view->topline;
178 int col = 0;
180 for (int i = 1; i < token_count; i += 2) {
181 lua_rawgeti(L, -1, i);
182 //const char *name = lua_tostring(L, -1);
183 lua_gettable(L, -3); /* _TOKENSTYLES[token] */
184 size_t token_style = lua_tointeger(L, -1);
185 lua_pop(L, 1); /* style */
186 lua_rawgeti(L, -1, i + 1);
187 size_t token_end = lua_tointeger(L, -1) - 1;
188 lua_pop(L, 1); /* pos */
190 for (bool token_next = false; line; line = line->next, col = 0) {
191 for (; col < line->width; col++) {
192 if (pos < token_end) {
193 line->cells[col].attr = token_style;
194 pos += line->cells[col].len;
195 } else {
196 token_next = true;
197 break;
200 if (token_next)
201 break;
204 lua_pop(L, 1);
207 lua_pop(L, 3); /* _TOKENSTYLES, language specific lexer, lexers global */
210 bool view_syntax_set(View *view, const char *name) {
211 if (!name) {
212 free(view->lexer_name);
213 view->lexer_name = NULL;
214 return true;
217 lua_State *L = view->lua;
218 if (!L)
219 return false;
221 /* Try to load the specified lexer and parse its token styles.
222 * Roughly equivalent to the following lua code:
224 * lang = vis.lexers.load(name)
225 * for token_name, id in pairs(lang._TOKENSTYLES) do
226 * ui->syntax_style(id, vis.lexers:get_style(lang, token_name);
228 lua_getglobal(L, "vis");
229 lua_getfield(L, -1, "lexers");
231 lua_getfield(L, -1, "STYLE_DEFAULT");
232 view->ui->syntax_style(view->ui, UI_STYLE_DEFAULT, lua_tostring(L, -1));
233 lua_pop(L, 1);
234 lua_getfield(L, -1, "STYLE_CURSOR");
235 view->ui->syntax_style(view->ui, UI_STYLE_CURSOR, lua_tostring(L, -1));
236 lua_pop(L, 1);
237 lua_getfield(L, -1, "STYLE_CURSOR_LINE");
238 view->ui->syntax_style(view->ui, UI_STYLE_CURSOR_LINE, lua_tostring(L, -1));
239 lua_pop(L, 1);
240 lua_getfield(L, -1, "STYLE_SELECTION");
241 view->ui->syntax_style(view->ui, UI_STYLE_SELECTION, lua_tostring(L, -1));
242 lua_pop(L, 1);
243 lua_getfield(L, -1, "STYLE_LINENUMBER");
244 view->ui->syntax_style(view->ui, UI_STYLE_LINENUMBER, lua_tostring(L, -1));
245 lua_pop(L, 1);
246 lua_getfield(L, -1, "STYLE_COLOR_COLUMN");
247 view->ui->syntax_style(view->ui, UI_STYLE_COLOR_COLUMN, lua_tostring(L, -1));
248 lua_pop(L, 1);
250 lua_getfield(L, -1, "load");
251 lua_pushstring(L, name);
253 if (lua_pcall(L, 1, 1, 0))
254 return false;
256 if (!lua_istable(L, -1)) {
257 lua_pop(L, 2);
258 return false;
261 view->lexer_name = strdup(name);
262 /* loop through all _TOKENSTYLES and parse them */
263 lua_getfield(L, -1, "_TOKENSTYLES");
264 lua_pushnil(L); /* first key */
266 while (lua_next(L, -2)) {
267 size_t id = lua_tointeger(L, -1);
268 //const char *name = lua_tostring(L, -2);
269 lua_pop(L, 1); /* remove value (=id), keep key (=name) */
270 lua_getfield(L, -4, "get_style");
271 lua_pushvalue(L, -5); /* lexer */
272 lua_pushvalue(L, -5); /* lang */
273 lua_pushvalue(L, -4); /* token_name */
274 if (lua_pcall(L, 3, 1, 0))
275 return false;
276 const char *style = lua_tostring(L, -1);
277 //printf("%s\t%d\t%s\n", name, id, style);
278 view->ui->syntax_style(view->ui, id, style);
279 lua_pop(L, 1); /* style */
282 lua_pop(L, 4); /* _TOKENSTYLES, grammar, lexers, vis */
284 return true;
287 #endif
290 void view_tabwidth_set(View *view, int tabwidth) {
291 view->tabwidth = tabwidth;
292 view_draw(view);
295 /* reset internal view data structures (cell matrix, line offsets etc.) */
296 static void view_clear(View *view) {
297 memset(view->lines, 0, view->lines_size);
298 if (view->start != view->start_last) {
299 view->start_mark = text_mark_set(view->text, view->start);
300 } else {
301 size_t start = text_mark_get(view->text, view->start_mark);
302 if (start != EPOS)
303 view->start = start;
306 view->start_last = view->start;
307 view->topline = view->lines;
308 view->topline->lineno = text_lineno_by_pos(view->text, view->start);
309 view->lastline = view->topline;
311 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
312 size_t end = view->height * line_size;
313 Line *prev = NULL;
314 for (size_t i = 0; i < end; i += line_size) {
315 Line *line = (Line*)(((char*)view->lines) + i);
316 line->prev = prev;
317 if (prev)
318 prev->next = line;
319 prev = line;
321 view->bottomline = prev ? prev : view->topline;
322 view->bottomline->next = NULL;
323 view->line = view->topline;
324 view->col = 0;
327 Filerange view_viewport_get(View *view) {
328 return (Filerange){ .start = view->start, .end = view->end };
331 /* try to add another character to the view, return whether there was space left */
332 static bool view_addch(View *view, Cell *cell) {
333 if (!view->line)
334 return false;
336 int width;
337 size_t lineno = view->line->lineno;
339 switch (cell->data[0]) {
340 case '\t':
341 cell->width = 1;
342 width = view->tabwidth - (view->col % view->tabwidth);
343 for (int w = 0; w < width; w++) {
344 if (view->col + 1 > view->width) {
345 view->line = view->line->next;
346 view->col = 0;
347 if (!view->line)
348 return false;
349 view->line->lineno = lineno;
352 cell->len = w == 0 ? 1 : 0;
353 int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
354 strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
355 cell->attr = view->symbols[t]->style;
356 view->line->cells[view->col] = *cell;
357 view->line->len += cell->len;
358 view->line->width += cell->width;
359 view->col++;
361 cell->len = 1;
362 return true;
363 case '\n':
364 cell->width = 1;
365 if (view->col + cell->width > view->width) {
366 view->line = view->line->next;
367 view->col = 0;
368 if (!view->line)
369 return false;
370 view->line->lineno = lineno;
373 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
374 cell->attr = view->symbols[SYNTAX_SYMBOL_EOL]->style;
376 view->line->cells[view->col] = *cell;
377 view->line->len += cell->len;
378 view->line->width += cell->width;
379 for (int i = view->col + 1; i < view->width; i++)
380 view->line->cells[i] = cell_blank;
382 view->line = view->line->next;
383 if (view->line)
384 view->line->lineno = lineno + 1;
385 view->col = 0;
386 return true;
387 default:
388 if ((unsigned char)cell->data[0] < 128 && !isprint((unsigned char)cell->data[0])) {
389 /* non-printable ascii char, represent it as ^(char + 64) */
390 *cell = (Cell) {
391 .data = { '^', cell->data[0] + 64, '\0' },
392 .len = 1,
393 .width = 2,
394 .attr = cell->attr,
398 if (cell->data[0] == ' ') {
399 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
400 cell->attr = view->symbols[SYNTAX_SYMBOL_SPACE]->style;
404 if (view->col + cell->width > view->width) {
405 for (int i = view->col; i < view->width; i++)
406 view->line->cells[i] = cell_blank;
407 view->line = view->line->next;
408 view->col = 0;
411 if (view->line) {
412 view->line->width += cell->width;
413 view->line->len += cell->len;
414 view->line->lineno = lineno;
415 view->line->cells[view->col] = *cell;
416 view->col++;
417 /* set cells of a character which uses multiple columns */
418 for (int i = 1; i < cell->width; i++)
419 view->line->cells[view->col++] = cell_unused;
420 return true;
422 return false;
426 CursorPos view_cursor_getpos(View *view) {
427 Cursor *cursor = view->cursor;
428 Line *line = cursor->line;
429 CursorPos pos = { .line = line->lineno, .col = cursor->col };
430 while (line->prev && line->prev->lineno == pos.line) {
431 line = line->prev;
432 pos.col += line->width;
434 pos.col++;
435 return pos;
438 static void cursor_to(Cursor *c, size_t pos) {
439 Text *txt = c->view->text;
440 c->mark = text_mark_set(txt, pos);
441 if (pos != c->pos)
442 c->lastcol = 0;
443 c->pos = pos;
444 if (c->sel) {
445 size_t anchor = text_mark_get(txt, c->sel->anchor);
446 size_t cursor = text_mark_get(txt, c->sel->cursor);
447 /* do we have to change the orientation of the selection? */
448 if (pos < anchor && anchor < cursor) {
449 /* right extend -> left extend */
450 anchor = text_char_next(txt, anchor);
451 c->sel->anchor = text_mark_set(txt, anchor);
452 } else if (cursor < anchor && anchor <= pos) {
453 /* left extend -> right extend */
454 anchor = text_char_prev(txt, anchor);
455 c->sel->anchor = text_mark_set(txt, anchor);
457 if (anchor <= pos)
458 pos = text_char_next(txt, pos);
459 c->sel->cursor = text_mark_set(txt, pos);
461 if (!view_coord_get(c->view, pos, &c->line, &c->row, &c->col)) {
462 if (c->view->cursor == c) {
463 c->line = c->view->topline;
464 c->row = 0;
465 c->col = 0;
467 return;
469 // TODO: minimize number of redraws
470 view_draw(c->view);
473 static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
474 int row = 0, col = 0;
475 size_t cur = view->start;
476 Line *line = view->topline;
478 if (pos < view->start || pos > view->end) {
479 if (retline) *retline = NULL;
480 if (retrow) *retrow = -1;
481 if (retcol) *retcol = -1;
482 return false;
485 while (line && line != view->lastline && cur < pos) {
486 if (cur + line->len > pos)
487 break;
488 cur += line->len;
489 line = line->next;
490 row++;
493 if (line) {
494 int max_col = MIN(view->width, line->width);
495 while (cur < pos && col < max_col) {
496 cur += line->cells[col].len;
497 /* skip over columns occupied by the same character */
498 while (++col < max_col && line->cells[col].len == 0);
500 } else {
501 line = view->bottomline;
502 row = view->height - 1;
505 if (retline) *retline = line;
506 if (retrow) *retrow = row;
507 if (retcol) *retcol = col;
508 return true;
511 /* move the cursor to the character at pos bytes from the begining of the file.
512 * if pos is not in the current viewport, redraw the view to make it visible */
513 void view_cursor_to(View *view, size_t pos) {
514 view_cursors_to(view->cursor, pos);
517 /* redraw the complete with data starting from view->start bytes into the file.
518 * stop once the screen is full, update view->end, view->lastline */
519 void view_draw(View *view) {
520 view_clear(view);
521 /* read a screenful of text */
522 const size_t text_size = view->width * view->height;
523 /* current buffer to work with */
524 char text[text_size+1];
525 /* remaining bytes to process in buffer */
526 size_t rem = text_bytes_get(view->text, view->start, text_size, text);
527 /* NUL terminate text section */
528 text[rem] = '\0';
529 /* absolute position of character currently being added to display */
530 size_t pos = view->start;
531 /* current position into buffer from which to interpret a character */
532 char *cur = text;
533 /* start from known multibyte state */
534 mbstate_t mbstate = { 0 };
536 Cell cell = { 0 }, prev_cell = { 0 };
538 while (rem > 0) {
540 /* current 'parsed' character' */
541 wchar_t wchar;
543 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
544 if (len == (size_t)-1 && errno == EILSEQ) {
545 /* ok, we encountered an invalid multibyte sequence,
546 * replace it with the Unicode Replacement Character
547 * (FFFD) and skip until the start of the next utf8 char */
548 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
549 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
550 } else if (len == (size_t)-2) {
551 /* not enough bytes available to convert to a
552 * wide character. advance file position and read
553 * another junk into buffer.
555 rem = text_bytes_get(view->text, pos, text_size, text);
556 text[rem] = '\0';
557 cur = text;
558 continue;
559 } else if (len == 0) {
560 /* NUL byte encountered, store it and continue */
561 cell = (Cell){ .data = "\x00", .len = 1, .width = 0 };
562 } else {
563 for (size_t i = 0; i < len; i++)
564 cell.data[i] = cur[i];
565 cell.data[len] = '\0';
566 cell.len = len;
567 cell.width = wcwidth(wchar);
568 if (cell.width == -1)
569 cell.width = 1;
572 if (cur[0] == '\r' && rem > 1 && cur[1] == '\n') {
573 /* convert views style newline \r\n into a single char with len = 2 */
574 cell = (Cell){ .data = "\n", .len = 2, .width = 1 };
577 if (cell.width == 0 && prev_cell.len + cell.len < sizeof(cell.len)) {
578 prev_cell.len += cell.len;
579 strcat(prev_cell.data, cell.data);
580 } else {
581 if (prev_cell.len && !view_addch(view, &prev_cell))
582 break;
583 pos += prev_cell.len;
584 prev_cell = cell;
587 rem -= cell.len;
588 cur += cell.len;
590 memset(&cell, 0, sizeof cell);
593 if (prev_cell.len && view_addch(view, &prev_cell))
594 pos += prev_cell.len;
596 /* set end of vieviewg region */
597 view->end = pos;
598 view->lastline = view->line ? view->line : view->bottomline;
600 /* clear remaining of line, important to show cursor at end of file */
601 if (view->line) {
602 for (int x = view->col; x < view->width; x++)
603 view->line->cells[x] = cell_blank;
606 /* resync position of cursors within visible area */
607 for (Cursor *c = view->cursors; c; c = c->next) {
608 size_t pos = view_cursors_pos(c);
609 if (view_coord_get(view, pos, &c->line, &c->row, &c->col)) {
610 c->line->cells[c->col].cursor = true;
611 if (view->ui && !c->sel) {
612 Line *line_match; int col_match;
613 size_t pos_match = text_bracket_match_except(view->text, pos, "<>");
614 if (pos != pos_match && view_coord_get(view, pos_match, &line_match, NULL, &col_match)) {
615 line_match->cells[col_match].selected = true;
618 } else if (c == view->cursor) {
619 c->line = view->topline;
620 c->row = 0;
621 c->col = 0;
625 view->need_update = true;
628 void view_update(View *view) {
629 if (!view->need_update)
630 return;
632 view_syntax_color(view);
634 if (view->colorcolumn > 0 && view->colorcolumn <= view->width) {
635 size_t lineno = 0;
636 for (Line *l = view->topline; l; l = l->next) {
637 if (l->lineno != lineno)
638 l->cells[view->colorcolumn-1].attr = UI_STYLE_COLOR_COLUMN;
639 lineno = l->lineno;
643 for (Line *l = view->lastline->next; l; l = l->next) {
644 strncpy(l->cells[0].data, view->symbols[SYNTAX_SYMBOL_EOF]->symbol, sizeof(l->cells[0].data));
645 l->cells[0].attr = view->symbols[SYNTAX_SYMBOL_EOF]->style;
646 for (int x = 1; x < view->width; x++)
647 l->cells[x] = cell_blank;
648 l->width = 1;
649 l->len = 0;
652 for (Selection *s = view->selections; s; s = s->next) {
653 Filerange sel = view_selections_get(s);
654 if (text_range_valid(&sel)) {
655 Line *start_line; int start_col;
656 Line *end_line; int end_col;
657 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
658 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
659 if (start_line || end_line) {
660 if (!start_line) {
661 start_line = view->topline;
662 start_col = 0;
664 if (!end_line) {
665 end_line = view->lastline;
666 end_col = end_line->width;
668 for (Line *l = start_line; l != end_line->next; l = l->next) {
669 int col = (l == start_line) ? start_col : 0;
670 int end = (l == end_line) ? end_col : l->width;
671 while (col < end) {
672 l->cells[col++].selected = true;
679 if (view->ui)
680 view->ui->draw(view->ui);
681 view->need_update = false;
684 bool view_resize(View *view, int width, int height) {
685 if (width <= 0)
686 width = 1;
687 if (height <= 0)
688 height = 1;
689 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
690 if (lines_size > view->lines_size) {
691 Line *lines = realloc(view->lines, lines_size);
692 if (!lines)
693 return false;
694 view->lines = lines;
695 view->lines_size = lines_size;
697 view->width = width;
698 view->height = height;
699 memset(view->lines, 0, view->lines_size);
700 view_draw(view);
701 return true;
704 int view_height_get(View *view) {
705 return view->height;
708 int view_width_get(View *view) {
709 return view->width;
712 void view_free(View *view) {
713 if (!view)
714 return;
715 while (view->cursors)
716 view_cursors_free(view->cursors);
717 while (view->selections)
718 view_selections_free(view->selections);
719 free(view->lines);
720 free(view->lexer_name);
721 free(view);
724 void view_reload(View *view, Text *text) {
725 view->text = text;
726 view_selections_clear(view);
727 view_cursor_to(view, 0);
730 View *view_new(Text *text, lua_State *lua) {
731 if (!text)
732 return NULL;
733 View *view = calloc(1, sizeof(View));
734 if (!view)
735 return NULL;
736 if (!view_cursors_new(view)) {
737 view_free(view);
738 return NULL;
741 view->text = text;
742 view->lua = lua;
743 view->tabwidth = 8;
744 view_options_set(view, 0);
746 if (!view_resize(view, 1, 1)) {
747 view_free(view);
748 return NULL;
751 view_cursor_to(view, 0);
753 return view;
756 void view_ui(View *view, UiWin* ui) {
757 view->ui = ui;
760 static size_t cursor_set(Cursor *cursor, Line *line, int col) {
761 int row = 0;
762 View *view = cursor->view;
763 size_t pos = view->start;
764 /* get row number and file offset at start of the given line */
765 for (Line *cur = view->topline; cur && cur != line; cur = cur->next) {
766 pos += cur->len;
767 row++;
770 /* for characters which use more than 1 column, make sure we are on the left most */
771 while (col > 0 && line->cells[col].len == 0)
772 col--;
773 /* calculate offset within the line */
774 for (int i = 0; i < col; i++)
775 pos += line->cells[i].len;
777 cursor->col = col;
778 cursor->row = row;
779 cursor->line = line;
781 cursor_to(cursor, pos);
783 return pos;
786 bool view_viewport_down(View *view, int n) {
787 Line *line;
788 if (view->end == text_size(view->text))
789 return false;
790 if (n >= view->height) {
791 view->start = view->end;
792 } else {
793 for (line = view->topline; line && n > 0; line = line->next, n--)
794 view->start += line->len;
796 view_draw(view);
797 return true;
800 bool view_viewport_up(View *view, int n) {
801 /* scrolling up is somewhat tricky because we do not yet know where
802 * the lines start, therefore scan backwards but stop at a reasonable
803 * maximum in case we are dealing with a file without any newlines
805 if (view->start == 0)
806 return false;
807 size_t max = view->width * view->height;
808 char c;
809 Iterator it = text_iterator_get(view->text, view->start - 1);
811 if (!text_iterator_byte_get(&it, &c))
812 return false;
813 size_t off = 0;
814 /* skip newlines immediately before display area */
815 if (c == '\n' && text_iterator_byte_prev(&it, &c))
816 off++;
817 if (c == '\r' && text_iterator_byte_prev(&it, &c))
818 off++;
819 do {
820 if (c == '\n' && --n == 0)
821 break;
822 if (++off > max)
823 break;
824 } while (text_iterator_byte_prev(&it, &c));
825 if (c == '\r')
826 off++;
827 view->start -= off;
828 view_draw(view);
829 return true;
832 void view_redraw_top(View *view) {
833 Line *line = view->cursor->line;
834 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
835 view->start += cur->len;
836 view_draw(view);
837 view_cursor_to(view, view->cursor->pos);
840 void view_redraw_center(View *view) {
841 int center = view->height / 2;
842 size_t pos = view->cursor->pos;
843 for (int i = 0; i < 2; i++) {
844 int linenr = 0;
845 Line *line = view->cursor->line;
846 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
847 linenr++;
848 if (linenr < center) {
849 view_slide_down(view, center - linenr);
850 continue;
852 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
853 view->start += cur->len;
854 linenr--;
856 break;
858 view_draw(view);
859 view_cursor_to(view, pos);
862 void view_redraw_bottom(View *view) {
863 Line *line = view->cursor->line;
864 if (line == view->lastline)
865 return;
866 size_t pos = view->cursor->pos;
867 view_viewport_up(view, view->height);
868 while (pos > view->end && view_viewport_down(view, 1));
869 view_cursor_to(view, pos);
872 size_t view_slide_up(View *view, int lines) {
873 Cursor *cursor = view->cursor;
874 if (view_viewport_down(view, lines)) {
875 if (cursor->line == view->topline)
876 cursor_set(cursor, view->topline, cursor->col);
877 else
878 view_cursor_to(view, cursor->pos);
879 } else {
880 view_screenline_down(cursor);
882 return cursor->pos;
885 size_t view_slide_down(View *view, int lines) {
886 Cursor *cursor = view->cursor;
887 if (view_viewport_up(view, lines)) {
888 if (cursor->line == view->lastline)
889 cursor_set(cursor, view->lastline, cursor->col);
890 else
891 view_cursor_to(view, cursor->pos);
892 } else {
893 view_screenline_up(cursor);
895 return cursor->pos;
898 size_t view_scroll_up(View *view, int lines) {
899 Cursor *cursor = view->cursor;
900 if (view_viewport_up(view, lines)) {
901 Line *line = cursor->line < view->lastline ? cursor->line : view->lastline;
902 cursor_set(cursor, line, view->cursor->col);
903 } else {
904 view_cursor_to(view, 0);
906 return cursor->pos;
909 size_t view_scroll_down(View *view, int lines) {
910 Cursor *cursor = view->cursor;
911 if (view_viewport_down(view, lines)) {
912 Line *line = cursor->line > view->topline ? cursor->line : view->topline;
913 cursor_set(cursor, line, cursor->col);
914 } else {
915 view_cursor_to(view, text_size(view->text));
917 return cursor->pos;
920 size_t view_line_up(Cursor *cursor) {
921 if (cursor->line && cursor->line->prev && cursor->line->prev->prev &&
922 cursor->line->lineno != cursor->line->prev->lineno &&
923 cursor->line->prev->lineno != cursor->line->prev->prev->lineno)
924 return view_screenline_up(cursor);
925 int lastcol = cursor->lastcol;
926 if (!lastcol)
927 lastcol = cursor->col;
928 view_cursors_to(cursor, text_line_up(cursor->view->text, cursor->pos));
929 if (cursor->line)
930 cursor_set(cursor, cursor->line, lastcol);
931 cursor->lastcol = lastcol;
932 return cursor->pos;
935 size_t view_line_down(Cursor *cursor) {
936 if (cursor->line && (!cursor->line->next || cursor->line->next->lineno != cursor->line->lineno))
937 return view_screenline_down(cursor);
938 int lastcol = cursor->lastcol;
939 if (!lastcol)
940 lastcol = cursor->col;
941 view_cursors_to(cursor, text_line_down(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_screenline_up(Cursor *cursor) {
949 int lastcol = cursor->lastcol;
950 if (!lastcol)
951 lastcol = cursor->col;
952 if (!cursor->line->prev)
953 view_scroll_up(cursor->view, 1);
954 if (cursor->line->prev)
955 cursor_set(cursor, cursor->line->prev, lastcol);
956 cursor->lastcol = lastcol;
957 return cursor->pos;
960 size_t view_screenline_down(Cursor *cursor) {
961 int lastcol = cursor->lastcol;
962 if (!lastcol)
963 lastcol = cursor->col;
964 if (!cursor->line->next && cursor->line == cursor->view->bottomline)
965 view_scroll_down(cursor->view, 1);
966 if (cursor->line->next)
967 cursor_set(cursor, cursor->line->next, lastcol);
968 cursor->lastcol = lastcol;
969 return cursor->pos;
972 size_t view_screenline_begin(Cursor *cursor) {
973 if (!cursor->line)
974 return cursor->pos;
975 return cursor_set(cursor, cursor->line, 0);
978 size_t view_screenline_middle(Cursor *cursor) {
979 if (!cursor->line)
980 return cursor->pos;
981 return cursor_set(cursor, cursor->line, cursor->line->width / 2);
984 size_t view_screenline_end(Cursor *cursor) {
985 if (!cursor->line)
986 return cursor->pos;
987 int col = cursor->line->width - 1;
988 return cursor_set(cursor, cursor->line, col >= 0 ? col : 0);
991 size_t view_cursor_get(View *view) {
992 return view_cursors_pos(view->cursor);
995 const Line *view_lines_get(View *view) {
996 return view->topline;
999 void view_scroll_to(View *view, size_t pos) {
1000 view_cursors_scroll_to(view->cursor, pos);
1003 const char *view_syntax_get(View *view) {
1004 return view->lexer_name;
1007 void view_options_set(View *view, enum UiOption options) {
1008 int mapping[] = {
1009 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
1010 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
1011 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
1012 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
1013 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
1015 for (int i = 0; i < LENGTH(mapping); i++) {
1016 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
1017 &symbols_none[i];
1019 if (view->ui)
1020 view->ui->options_set(view->ui, options);
1023 enum UiOption view_options_get(View *view) {
1024 return view->ui ? view->ui->options_get(view->ui) : 0;
1027 void view_colorcolumn_set(View *view, int col) {
1028 if (col >= 0)
1029 view->colorcolumn = col;
1032 int view_colorcolumn_get(View *view) {
1033 return view->colorcolumn;
1036 size_t view_screenline_goto(View *view, int n) {
1037 size_t pos = view->start;
1038 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
1039 pos += line->len;
1040 return pos;
1043 Cursor *view_cursors_new(View *view) {
1044 Cursor *c = calloc(1, sizeof(*c));
1045 if (!c)
1046 return NULL;
1048 c->view = view;
1049 c->next = view->cursors;
1050 if (view->cursors)
1051 view->cursors->prev = c;
1052 view->cursors = c;
1053 view->cursor = c;
1054 return c;
1057 int view_cursors_count(View *view) {
1058 int i = 0;
1059 for (Cursor *c = view_cursors(view); c; c = view_cursors_next(c))
1060 i++;
1061 return i;
1064 static void view_cursors_free(Cursor *c) {
1065 if (!c)
1066 return;
1067 register_release(&c->reg);
1068 if (c->prev)
1069 c->prev->next = c->next;
1070 if (c->next)
1071 c->next->prev = c->prev;
1072 if (c->view->cursors == c)
1073 c->view->cursors = c->next;
1074 if (c->view->cursor == c)
1075 c->view->cursor = c->next ? c->next : c->prev;
1076 free(c);
1079 void view_cursors_dispose(Cursor *c) {
1080 if (!c)
1081 return;
1082 View *view = c->view;
1083 if (view->cursors && view->cursors->next) {
1084 view_selections_free(c->sel);
1085 view_cursors_free(c);
1086 view_draw(view);
1090 Cursor *view_cursors(View *view) {
1091 return view->cursors;
1094 Cursor *view_cursor(View *view) {
1095 return view->cursor;
1098 Cursor *view_cursors_prev(Cursor *c) {
1099 return c->prev;
1102 Cursor *view_cursors_next(Cursor *c) {
1103 return c->next;
1106 size_t view_cursors_pos(Cursor *c) {
1107 return text_mark_get(c->view->text, c->mark);
1110 int view_cursors_cell_get(Cursor *c) {
1111 return c->line ? c->col : -1;
1114 int view_cursors_cell_set(Cursor *c, int cell) {
1115 if (!c->line || cell < 0)
1116 return -1;
1117 cursor_set(c, c->line, cell);
1118 return c->col;
1121 Register *view_cursors_register(Cursor *c) {
1122 return &c->reg;
1125 void view_cursors_scroll_to(Cursor *c, size_t pos) {
1126 View *view = c->view;
1127 if (view->cursor == c) {
1128 while (pos < view->start && view_viewport_up(view, 1));
1129 while (pos > view->end && view_viewport_down(view, 1));
1131 view_cursors_to(c, pos);
1134 void view_cursors_to(Cursor *c, size_t pos) {
1135 View *view = c->view;
1136 if (c->view->cursors == c) {
1137 c->mark = text_mark_set(view->text, pos);
1139 size_t max = text_size(view->text);
1140 if (pos == max && view->end < max) {
1141 /* do not display an empty screen when shoviewg the end of the file */
1142 view->start = pos;
1143 view_viewport_up(view, view->height / 2);
1144 } else {
1145 /* make sure we redraw changes to the very first character of the window */
1146 if (view->start == pos)
1147 view->start_last = 0;
1148 if (view->end == pos)
1149 view_viewport_down(view, 1);
1150 /* set the start of the viewable region to the start of the line on which
1151 * the cursor should be placed. if this line requires more space than
1152 * available in the view then simply start displaying text at the new
1153 * cursor position */
1154 for (int i = 0; i < 2 && (pos <= view->start || pos > view->end); i++) {
1155 view->start = i == 0 ? text_line_begin(view->text, pos) : pos;
1156 view_draw(view);
1161 cursor_to(c, pos);
1164 void view_cursors_selection_start(Cursor *c) {
1165 if (c->sel)
1166 return;
1167 size_t pos = view_cursors_pos(c);
1168 if (pos == EPOS || !(c->sel = view_selections_new(c->view)))
1169 return;
1170 Text *txt = c->view->text;
1171 c->sel->anchor = text_mark_set(txt, pos);
1172 c->sel->cursor = text_mark_set(txt, text_char_next(txt, pos));
1173 view_draw(c->view);
1176 void view_cursors_selection_restore(Cursor *c) {
1177 Text *txt = c->view->text;
1178 if (c->sel)
1179 return;
1180 Filerange sel = text_range_new(
1181 text_mark_get(txt, c->lastsel_anchor),
1182 text_mark_get(txt, c->lastsel_cursor)
1184 if (!text_range_valid(&sel))
1185 return;
1186 if (!(c->sel = view_selections_new(c->view)))
1187 return;
1188 view_selections_set(c->sel, &sel);
1189 view_cursors_selection_sync(c);
1190 view_draw(c->view);
1193 void view_cursors_selection_stop(Cursor *c) {
1194 c->sel = NULL;
1197 void view_cursors_selection_clear(Cursor *c) {
1198 view_selections_free(c->sel);
1199 view_draw(c->view);
1202 void view_cursors_selection_swap(Cursor *c) {
1203 if (!c->sel)
1204 return;
1205 view_selections_swap(c->sel);
1206 view_cursors_selection_sync(c);
1209 void view_cursors_selection_sync(Cursor *c) {
1210 if (!c->sel)
1211 return;
1212 Text *txt = c->view->text;
1213 size_t anchor = text_mark_get(txt, c->sel->anchor);
1214 size_t cursor = text_mark_get(txt, c->sel->cursor);
1215 bool right_extending = anchor < cursor;
1216 if (right_extending)
1217 cursor = text_char_prev(txt, cursor);
1218 view_cursors_to(c, cursor);
1221 Filerange view_cursors_selection_get(Cursor *c) {
1222 return view_selections_get(c->sel);
1225 void view_cursors_selection_set(Cursor *c, Filerange *r) {
1226 if (!text_range_valid(r))
1227 return;
1228 if (!c->sel)
1229 c->sel = view_selections_new(c->view);
1230 if (!c->sel)
1231 return;
1233 view_selections_set(c->sel, r);
1236 Selection *view_selections_new(View *view) {
1237 Selection *s = calloc(1, sizeof(*s));
1238 if (!s)
1239 return NULL;
1241 s->view = view;
1242 s->next = view->selections;
1243 if (view->selections)
1244 view->selections->prev = s;
1245 view->selections = s;
1246 return s;
1249 void view_selections_free(Selection *s) {
1250 if (!s)
1251 return;
1252 if (s->prev)
1253 s->prev->next = s->next;
1254 if (s->next)
1255 s->next->prev = s->prev;
1256 if (s->view->selections == s)
1257 s->view->selections = s->next;
1258 // XXX: add backlink Selection->Cursor?
1259 for (Cursor *c = s->view->cursors; c; c = c->next) {
1260 if (c->sel == s) {
1261 c->lastsel_anchor = s->anchor;
1262 c->lastsel_cursor = s->cursor;
1263 c->sel = NULL;
1266 free(s);
1269 void view_selections_clear(View *view) {
1270 while (view->selections)
1271 view_selections_free(view->selections);
1272 view_draw(view);
1275 void view_cursors_clear(View *view) {
1276 for (Cursor *c = view->cursors, *next; c; c = next) {
1277 next = c->next;
1278 if (c != view->cursor) {
1279 view_selections_free(c->sel);
1280 view_cursors_free(c);
1283 view_draw(view);
1286 void view_selections_swap(Selection *s) {
1287 Mark temp = s->anchor;
1288 s->anchor = s->cursor;
1289 s->cursor = temp;
1292 Selection *view_selections(View *view) {
1293 return view->selections;
1296 Selection *view_selections_prev(Selection *s) {
1297 return s->prev;
1300 Selection *view_selections_next(Selection *s) {
1301 return s->next;
1304 Filerange view_selections_get(Selection *s) {
1305 if (!s)
1306 return text_range_empty();
1307 Text *txt = s->view->text;
1308 size_t anchor = text_mark_get(txt, s->anchor);
1309 size_t cursor = text_mark_get(txt, s->cursor);
1310 return text_range_new(anchor, cursor);
1313 void view_selections_set(Selection *s, Filerange *r) {
1314 if (!text_range_valid(r))
1315 return;
1316 Text *txt = s->view->text;
1317 size_t anchor = text_mark_get(txt, s->anchor);
1318 size_t cursor = text_mark_get(txt, s->cursor);
1319 bool left_extending = anchor > cursor;
1320 if (left_extending) {
1321 s->anchor = text_mark_set(txt, r->end);
1322 s->cursor = text_mark_set(txt, r->start);
1323 } else {
1324 s->anchor = text_mark_set(txt, r->start);
1325 s->cursor = text_mark_set(txt, r->end);
1327 view_draw(s->view);