vis: use standard registers for macro recordings
[vis.git] / view.c
blob7f47f1ab086b6fa728f1612e98d6fe92a32e36f7
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) {
637 size_t lineno = 0;
638 int line_cols = 0; /* Track the number of columns we've passed on each line */
639 bool line_cc_set = false; /* Has the colorcolumn attribute been set for this line yet */
641 for (Line *l = view->topline; l; l = l->next) {
642 if (l->lineno != lineno) {
643 line_cols = 0;
644 line_cc_set = false;
647 if (!line_cc_set) {
648 line_cols += view->width;
650 /* This screen line contains the cell we want to highlight */
651 if (line_cols >= view->colorcolumn) {
652 l->cells[(view->colorcolumn - 1) % view->width].attr = UI_STYLE_COLOR_COLUMN;
653 line_cc_set = true;
657 lineno = l->lineno;
661 for (Line *l = view->lastline->next; l; l = l->next) {
662 strncpy(l->cells[0].data, view->symbols[SYNTAX_SYMBOL_EOF]->symbol, sizeof(l->cells[0].data));
663 l->cells[0].attr = view->symbols[SYNTAX_SYMBOL_EOF]->style;
664 for (int x = 1; x < view->width; x++)
665 l->cells[x] = cell_blank;
666 l->width = 1;
667 l->len = 0;
670 for (Selection *s = view->selections; s; s = s->next) {
671 Filerange sel = view_selections_get(s);
672 if (text_range_valid(&sel)) {
673 Line *start_line; int start_col;
674 Line *end_line; int end_col;
675 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
676 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
677 if (start_line || end_line) {
678 if (!start_line) {
679 start_line = view->topline;
680 start_col = 0;
682 if (!end_line) {
683 end_line = view->lastline;
684 end_col = end_line->width;
686 for (Line *l = start_line; l != end_line->next; l = l->next) {
687 int col = (l == start_line) ? start_col : 0;
688 int end = (l == end_line) ? end_col : l->width;
689 while (col < end) {
690 l->cells[col++].selected = true;
697 if (view->ui)
698 view->ui->draw(view->ui);
699 view->need_update = false;
702 bool view_resize(View *view, int width, int height) {
703 if (width <= 0)
704 width = 1;
705 if (height <= 0)
706 height = 1;
707 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
708 if (lines_size > view->lines_size) {
709 Line *lines = realloc(view->lines, lines_size);
710 if (!lines)
711 return false;
712 view->lines = lines;
713 view->lines_size = lines_size;
715 view->width = width;
716 view->height = height;
717 memset(view->lines, 0, view->lines_size);
718 view_draw(view);
719 return true;
722 int view_height_get(View *view) {
723 return view->height;
726 int view_width_get(View *view) {
727 return view->width;
730 void view_free(View *view) {
731 if (!view)
732 return;
733 while (view->cursors)
734 view_cursors_free(view->cursors);
735 while (view->selections)
736 view_selections_free(view->selections);
737 free(view->lines);
738 free(view->lexer_name);
739 free(view);
742 void view_reload(View *view, Text *text) {
743 view->text = text;
744 view_selections_clear(view);
745 view_cursor_to(view, 0);
748 View *view_new(Text *text, lua_State *lua) {
749 if (!text)
750 return NULL;
751 View *view = calloc(1, sizeof(View));
752 if (!view)
753 return NULL;
754 if (!view_cursors_new(view)) {
755 view_free(view);
756 return NULL;
759 view->text = text;
760 view->lua = lua;
761 view->tabwidth = 8;
762 view_options_set(view, 0);
764 if (!view_resize(view, 1, 1)) {
765 view_free(view);
766 return NULL;
769 view_cursor_to(view, 0);
771 return view;
774 void view_ui(View *view, UiWin* ui) {
775 view->ui = ui;
778 static size_t cursor_set(Cursor *cursor, Line *line, int col) {
779 int row = 0;
780 View *view = cursor->view;
781 size_t pos = view->start;
782 /* get row number and file offset at start of the given line */
783 for (Line *cur = view->topline; cur && cur != line; cur = cur->next) {
784 pos += cur->len;
785 row++;
788 /* for characters which use more than 1 column, make sure we are on the left most */
789 while (col > 0 && line->cells[col].len == 0)
790 col--;
791 /* calculate offset within the line */
792 for (int i = 0; i < col; i++)
793 pos += line->cells[i].len;
795 cursor->col = col;
796 cursor->row = row;
797 cursor->line = line;
799 cursor_to(cursor, pos);
801 return pos;
804 bool view_viewport_down(View *view, int n) {
805 Line *line;
806 if (view->end == text_size(view->text))
807 return false;
808 if (n >= view->height) {
809 view->start = view->end;
810 } else {
811 for (line = view->topline; line && n > 0; line = line->next, n--)
812 view->start += line->len;
814 view_draw(view);
815 return true;
818 bool view_viewport_up(View *view, int n) {
819 /* scrolling up is somewhat tricky because we do not yet know where
820 * the lines start, therefore scan backwards but stop at a reasonable
821 * maximum in case we are dealing with a file without any newlines
823 if (view->start == 0)
824 return false;
825 size_t max = view->width * view->height;
826 char c;
827 Iterator it = text_iterator_get(view->text, view->start - 1);
829 if (!text_iterator_byte_get(&it, &c))
830 return false;
831 size_t off = 0;
832 /* skip newlines immediately before display area */
833 if (c == '\n' && text_iterator_byte_prev(&it, &c))
834 off++;
835 if (c == '\r' && text_iterator_byte_prev(&it, &c))
836 off++;
837 do {
838 if (c == '\n' && --n == 0)
839 break;
840 if (++off > max)
841 break;
842 } while (text_iterator_byte_prev(&it, &c));
843 if (c == '\r')
844 off++;
845 view->start -= off;
846 view_draw(view);
847 return true;
850 void view_redraw_top(View *view) {
851 Line *line = view->cursor->line;
852 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
853 view->start += cur->len;
854 view_draw(view);
855 view_cursor_to(view, view->cursor->pos);
858 void view_redraw_center(View *view) {
859 int center = view->height / 2;
860 size_t pos = view->cursor->pos;
861 for (int i = 0; i < 2; i++) {
862 int linenr = 0;
863 Line *line = view->cursor->line;
864 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
865 linenr++;
866 if (linenr < center) {
867 view_slide_down(view, center - linenr);
868 continue;
870 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
871 view->start += cur->len;
872 linenr--;
874 break;
876 view_draw(view);
877 view_cursor_to(view, pos);
880 void view_redraw_bottom(View *view) {
881 Line *line = view->cursor->line;
882 if (line == view->lastline)
883 return;
884 size_t pos = view->cursor->pos;
885 view_viewport_up(view, view->height);
886 while (pos > view->end && view_viewport_down(view, 1));
887 view_cursor_to(view, pos);
890 size_t view_slide_up(View *view, int lines) {
891 Cursor *cursor = view->cursor;
892 if (view_viewport_down(view, lines)) {
893 if (cursor->line == view->topline)
894 cursor_set(cursor, view->topline, cursor->col);
895 else
896 view_cursor_to(view, cursor->pos);
897 } else {
898 view_screenline_down(cursor);
900 return cursor->pos;
903 size_t view_slide_down(View *view, int lines) {
904 Cursor *cursor = view->cursor;
905 if (view_viewport_up(view, lines)) {
906 if (cursor->line == view->lastline)
907 cursor_set(cursor, view->lastline, cursor->col);
908 else
909 view_cursor_to(view, cursor->pos);
910 } else {
911 view_screenline_up(cursor);
913 return cursor->pos;
916 size_t view_scroll_up(View *view, int lines) {
917 Cursor *cursor = view->cursor;
918 if (view_viewport_up(view, lines)) {
919 Line *line = cursor->line < view->lastline ? cursor->line : view->lastline;
920 cursor_set(cursor, line, view->cursor->col);
921 } else {
922 view_cursor_to(view, 0);
924 return cursor->pos;
927 size_t view_scroll_down(View *view, int lines) {
928 Cursor *cursor = view->cursor;
929 if (view_viewport_down(view, lines)) {
930 Line *line = cursor->line > view->topline ? cursor->line : view->topline;
931 cursor_set(cursor, line, cursor->col);
932 } else {
933 view_cursor_to(view, text_size(view->text));
935 return cursor->pos;
938 size_t view_line_up(Cursor *cursor) {
939 if (cursor->line && cursor->line->prev && cursor->line->prev->prev &&
940 cursor->line->lineno != cursor->line->prev->lineno &&
941 cursor->line->prev->lineno != cursor->line->prev->prev->lineno)
942 return view_screenline_up(cursor);
943 int lastcol = cursor->lastcol;
944 if (!lastcol)
945 lastcol = cursor->col;
946 view_cursors_to(cursor, text_line_up(cursor->view->text, cursor->pos));
947 if (cursor->line)
948 cursor_set(cursor, cursor->line, lastcol);
949 cursor->lastcol = lastcol;
950 return cursor->pos;
953 size_t view_line_down(Cursor *cursor) {
954 if (cursor->line && (!cursor->line->next || cursor->line->next->lineno != cursor->line->lineno))
955 return view_screenline_down(cursor);
956 int lastcol = cursor->lastcol;
957 if (!lastcol)
958 lastcol = cursor->col;
959 view_cursors_to(cursor, text_line_down(cursor->view->text, cursor->pos));
960 if (cursor->line)
961 cursor_set(cursor, cursor->line, lastcol);
962 cursor->lastcol = lastcol;
963 return cursor->pos;
966 size_t view_screenline_up(Cursor *cursor) {
967 int lastcol = cursor->lastcol;
968 if (!lastcol)
969 lastcol = cursor->col;
970 if (!cursor->line->prev)
971 view_scroll_up(cursor->view, 1);
972 if (cursor->line->prev)
973 cursor_set(cursor, cursor->line->prev, lastcol);
974 cursor->lastcol = lastcol;
975 return cursor->pos;
978 size_t view_screenline_down(Cursor *cursor) {
979 int lastcol = cursor->lastcol;
980 if (!lastcol)
981 lastcol = cursor->col;
982 if (!cursor->line->next && cursor->line == cursor->view->bottomline)
983 view_scroll_down(cursor->view, 1);
984 if (cursor->line->next)
985 cursor_set(cursor, cursor->line->next, lastcol);
986 cursor->lastcol = lastcol;
987 return cursor->pos;
990 size_t view_screenline_begin(Cursor *cursor) {
991 if (!cursor->line)
992 return cursor->pos;
993 return cursor_set(cursor, cursor->line, 0);
996 size_t view_screenline_middle(Cursor *cursor) {
997 if (!cursor->line)
998 return cursor->pos;
999 return cursor_set(cursor, cursor->line, cursor->line->width / 2);
1002 size_t view_screenline_end(Cursor *cursor) {
1003 if (!cursor->line)
1004 return cursor->pos;
1005 int col = cursor->line->width - 1;
1006 return cursor_set(cursor, cursor->line, col >= 0 ? col : 0);
1009 size_t view_cursor_get(View *view) {
1010 return view_cursors_pos(view->cursor);
1013 const Line *view_lines_get(View *view) {
1014 return view->topline;
1017 void view_scroll_to(View *view, size_t pos) {
1018 view_cursors_scroll_to(view->cursor, pos);
1021 const char *view_syntax_get(View *view) {
1022 return view->lexer_name;
1025 void view_options_set(View *view, enum UiOption options) {
1026 const int mapping[] = {
1027 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
1028 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
1029 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
1030 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
1031 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
1034 for (int i = 0; i < LENGTH(mapping); i++) {
1035 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
1036 &symbols_none[i];
1039 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
1040 options &= ~UI_OPTION_LARGE_FILE;
1042 view->large_file = (options & UI_OPTION_LARGE_FILE);
1044 if (view->ui)
1045 view->ui->options_set(view->ui, options);
1048 enum UiOption view_options_get(View *view) {
1049 return view->ui ? view->ui->options_get(view->ui) : 0;
1052 void view_colorcolumn_set(View *view, int col) {
1053 if (col >= 0)
1054 view->colorcolumn = col;
1057 int view_colorcolumn_get(View *view) {
1058 return view->colorcolumn;
1061 size_t view_screenline_goto(View *view, int n) {
1062 size_t pos = view->start;
1063 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
1064 pos += line->len;
1065 return pos;
1068 Cursor *view_cursors_new(View *view) {
1069 Cursor *c = calloc(1, sizeof(*c));
1070 if (!c)
1071 return NULL;
1073 c->view = view;
1074 c->next = view->cursors;
1075 if (view->cursors)
1076 view->cursors->prev = c;
1077 view->cursors = c;
1078 view->cursor = c;
1079 return c;
1082 int view_cursors_count(View *view) {
1083 int i = 0;
1084 for (Cursor *c = view_cursors(view); c; c = view_cursors_next(c))
1085 i++;
1086 return i;
1089 static void view_cursors_free(Cursor *c) {
1090 if (!c)
1091 return;
1092 register_release(&c->reg);
1093 if (c->prev)
1094 c->prev->next = c->next;
1095 if (c->next)
1096 c->next->prev = c->prev;
1097 if (c->view->cursors == c)
1098 c->view->cursors = c->next;
1099 if (c->view->cursor == c)
1100 c->view->cursor = c->next ? c->next : c->prev;
1101 free(c);
1104 void view_cursors_dispose(Cursor *c) {
1105 if (!c)
1106 return;
1107 View *view = c->view;
1108 if (view->cursors && view->cursors->next) {
1109 view_selections_free(c->sel);
1110 view_cursors_free(c);
1111 view_draw(view);
1115 Cursor *view_cursors(View *view) {
1116 return view->cursors;
1119 Cursor *view_cursor(View *view) {
1120 return view->cursor;
1123 Cursor *view_cursors_prev(Cursor *c) {
1124 return c->prev;
1127 Cursor *view_cursors_next(Cursor *c) {
1128 return c->next;
1131 size_t view_cursors_pos(Cursor *c) {
1132 return text_mark_get(c->view->text, c->mark);
1135 int view_cursors_cell_get(Cursor *c) {
1136 return c->line ? c->col : -1;
1139 int view_cursors_cell_set(Cursor *c, int cell) {
1140 if (!c->line || cell < 0)
1141 return -1;
1142 cursor_set(c, c->line, cell);
1143 return c->col;
1146 Register *view_cursors_register(Cursor *c) {
1147 return &c->reg;
1150 void view_cursors_scroll_to(Cursor *c, size_t pos) {
1151 View *view = c->view;
1152 if (view->cursor == c) {
1153 while (pos < view->start && view_viewport_up(view, 1));
1154 while (pos > view->end && view_viewport_down(view, 1));
1156 view_cursors_to(c, pos);
1159 void view_cursors_to(Cursor *c, size_t pos) {
1160 View *view = c->view;
1161 if (c->view->cursors == c) {
1162 c->mark = text_mark_set(view->text, pos);
1164 size_t max = text_size(view->text);
1165 if (pos == max && view->end < max) {
1166 /* do not display an empty screen when shoviewg the end of the file */
1167 view->start = pos;
1168 view_viewport_up(view, view->height / 2);
1169 } else {
1170 /* make sure we redraw changes to the very first character of the window */
1171 if (view->start == pos)
1172 view->start_last = 0;
1173 /* set the start of the viewable region to the start of the line on which
1174 * the cursor should be placed. if this line requires more space than
1175 * available in the view then simply start displaying text at the new
1176 * cursor position */
1177 for (int i = 0; i < 2 && (pos <= view->start || pos > view->end); i++) {
1178 view->start = i == 0 ? text_line_begin(view->text, pos) : pos;
1179 view_draw(view);
1184 cursor_to(c, pos);
1187 void view_cursors_selection_start(Cursor *c) {
1188 if (c->sel)
1189 return;
1190 size_t pos = view_cursors_pos(c);
1191 if (pos == EPOS || !(c->sel = view_selections_new(c->view)))
1192 return;
1193 Text *txt = c->view->text;
1194 c->sel->anchor = text_mark_set(txt, pos);
1195 c->sel->cursor = text_mark_set(txt, text_char_next(txt, pos));
1196 view_draw(c->view);
1199 void view_cursors_selection_restore(Cursor *c) {
1200 Text *txt = c->view->text;
1201 if (c->sel)
1202 return;
1203 Filerange sel = text_range_new(
1204 text_mark_get(txt, c->lastsel_anchor),
1205 text_mark_get(txt, c->lastsel_cursor)
1207 if (!text_range_valid(&sel))
1208 return;
1209 if (!(c->sel = view_selections_new(c->view)))
1210 return;
1211 view_selections_set(c->sel, &sel);
1212 view_cursors_selection_sync(c);
1213 view_draw(c->view);
1216 void view_cursors_selection_stop(Cursor *c) {
1217 c->sel = NULL;
1220 void view_cursors_selection_clear(Cursor *c) {
1221 view_selections_free(c->sel);
1222 view_draw(c->view);
1225 void view_cursors_selection_swap(Cursor *c) {
1226 if (!c->sel)
1227 return;
1228 view_selections_swap(c->sel);
1229 view_cursors_selection_sync(c);
1232 void view_cursors_selection_sync(Cursor *c) {
1233 if (!c->sel)
1234 return;
1235 Text *txt = c->view->text;
1236 size_t anchor = text_mark_get(txt, c->sel->anchor);
1237 size_t cursor = text_mark_get(txt, c->sel->cursor);
1238 bool right_extending = anchor < cursor;
1239 if (right_extending)
1240 cursor = text_char_prev(txt, cursor);
1241 view_cursors_to(c, cursor);
1244 Filerange view_cursors_selection_get(Cursor *c) {
1245 return view_selections_get(c->sel);
1248 void view_cursors_selection_set(Cursor *c, Filerange *r) {
1249 if (!text_range_valid(r))
1250 return;
1251 if (!c->sel)
1252 c->sel = view_selections_new(c->view);
1253 if (!c->sel)
1254 return;
1256 view_selections_set(c->sel, r);
1259 Selection *view_selections_new(View *view) {
1260 Selection *s = calloc(1, sizeof(*s));
1261 if (!s)
1262 return NULL;
1264 s->view = view;
1265 s->next = view->selections;
1266 if (view->selections)
1267 view->selections->prev = s;
1268 view->selections = s;
1269 return s;
1272 void view_selections_free(Selection *s) {
1273 if (!s)
1274 return;
1275 if (s->prev)
1276 s->prev->next = s->next;
1277 if (s->next)
1278 s->next->prev = s->prev;
1279 if (s->view->selections == s)
1280 s->view->selections = s->next;
1281 // XXX: add backlink Selection->Cursor?
1282 for (Cursor *c = s->view->cursors; c; c = c->next) {
1283 if (c->sel == s) {
1284 c->lastsel_anchor = s->anchor;
1285 c->lastsel_cursor = s->cursor;
1286 c->sel = NULL;
1289 free(s);
1292 void view_selections_clear(View *view) {
1293 while (view->selections)
1294 view_selections_free(view->selections);
1295 view_draw(view);
1298 void view_cursors_clear(View *view) {
1299 for (Cursor *c = view->cursors, *next; c; c = next) {
1300 next = c->next;
1301 if (c != view->cursor) {
1302 view_selections_free(c->sel);
1303 view_cursors_free(c);
1306 view_draw(view);
1309 void view_selections_swap(Selection *s) {
1310 Mark temp = s->anchor;
1311 s->anchor = s->cursor;
1312 s->cursor = temp;
1315 Selection *view_selections(View *view) {
1316 return view->selections;
1319 Selection *view_selections_prev(Selection *s) {
1320 return s->prev;
1323 Selection *view_selections_next(Selection *s) {
1324 return s->next;
1327 Filerange view_selection_get(View *view) {
1328 return view_selections_get(view->cursor->sel);
1331 Filerange view_selections_get(Selection *s) {
1332 if (!s)
1333 return text_range_empty();
1334 Text *txt = s->view->text;
1335 size_t anchor = text_mark_get(txt, s->anchor);
1336 size_t cursor = text_mark_get(txt, s->cursor);
1337 return text_range_new(anchor, cursor);
1340 void view_selections_set(Selection *s, Filerange *r) {
1341 if (!text_range_valid(r))
1342 return;
1343 Text *txt = s->view->text;
1344 size_t anchor = text_mark_get(txt, s->anchor);
1345 size_t cursor = text_mark_get(txt, s->cursor);
1346 bool left_extending = anchor > cursor;
1347 if (left_extending) {
1348 s->anchor = text_mark_set(txt, r->end);
1349 s->cursor = text_mark_set(txt, r->start);
1350 } else {
1351 s->anchor = text_mark_set(txt, r->start);
1352 s->cursor = text_mark_set(txt, r->end);
1354 view_draw(s->view);
1357 Text *view_text(View *view) {
1358 return view->text;