travis: try to fix build by using local built dependencies
[vis.git] / view.c
blobb0209c6af8d508f0cbd11b11120637b372063585
1 /*
2 * Copyright (c) 2014 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.h"
23 #include "view.h"
24 #include "syntax.h"
25 #include "text.h"
26 #include "text-motions.h"
27 #include "text-util.h"
28 #include "util.h"
30 struct Selection {
31 Mark anchor; /* position where the selection was created */
32 Mark cursor; /* other selection endpoint where it changes */
33 View *view; /* associated view to which this selection belongs */
34 Selection *prev, *next; /* previsous/next selections in no particular order */
37 struct Cursor { /* cursor position */
38 Filepos pos; /* in bytes from the start of the file */
39 int row, col; /* in terms of zero based screen coordinates */
40 int lastcol; /* remembered column used when moving across lines */
41 Line *line; /* screen line on which cursor currently resides */
42 Mark mark; /* mark used to keep track of current cursor position */
43 Selection *sel; /* selection (if any) which folows the cursor upon movement */
44 Mark lastsel_anchor;/* previously used selection data, */
45 Mark lastsel_cursor;/* used to restore it */
46 Register reg; /* per cursor register to support yank/put operation */
47 View *view; /* associated view to which this cursor belongs */
48 Cursor *prev, *next;/* previous/next cursors in no particular order */
51 /* Viewable area, showing part of a file. Keeps track of cursors and selections.
52 * At all times there exists at least one cursor, which is placed in the visible viewport.
53 * Additional cursors can be created and positioned anywhere in the file. */
54 struct View {
55 Text *text; /* underlying text management */
56 UiWin *ui;
57 ViewEvent *events;
58 int width, height; /* size of display area */
59 Filepos start, end; /* currently displayed area [start, end] in bytes from the start of the file */
60 Filepos start_last; /* previously used start of visible area, used to update the mark */
61 Mark start_mark; /* mark to keep track of the start of the visible area */
62 size_t lines_size; /* number of allocated bytes for lines (grows only) */
63 Line *lines; /* view->height number of lines representing view content */
64 Line *topline; /* top of the view, first line currently shown */
65 Line *lastline; /* last currently used line, always <= bottomline */
66 Line *bottomline; /* bottom of view, might be unused if lastline < bottomline */
67 Cursor *cursor; /* main cursor, always placed within the visible viewport */
68 Line *line; /* used while drawing view content, line where next char will be drawn */
69 int col; /* used while drawing view content, column where next char will be drawn */
70 const SyntaxSymbol *symbols[SYNTAX_SYMBOL_LAST]; /* symbols to use for white spaces etc */
71 int tabwidth; /* how many spaces should be used to display a tab character */
72 Cursor *cursors; /* all cursors currently active */
73 Selection *selections; /* all selected regions */
74 lua_State *lua; /* lua state used for syntax highlighting */
75 char *lexer_name;
76 bool need_update; /* whether view has been redrawn */
79 static const SyntaxSymbol symbols_none[] = {
80 [SYNTAX_SYMBOL_SPACE] = { " " },
81 [SYNTAX_SYMBOL_TAB] = { " " },
82 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
83 [SYNTAX_SYMBOL_EOL] = { " " },
84 [SYNTAX_SYMBOL_EOF] = { "~" },
87 static const SyntaxSymbol symbols_default[] = {
88 [SYNTAX_SYMBOL_SPACE] = { "\xC2\xB7" },
89 [SYNTAX_SYMBOL_TAB] = { "\xE2\x96\xB6" },
90 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
91 [SYNTAX_SYMBOL_EOL] = { "\xE2\x8F\x8E" },
92 [SYNTAX_SYMBOL_EOF] = { "~" },
95 static Cell cell_unused;
96 static Cell cell_blank = { .data = " " };
98 static void view_clear(View *view);
99 static bool view_addch(View *view, Cell *cell);
100 static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol);
101 static void view_cursors_free(Cursor *c);
102 /* set/move current cursor position to a given (line, column) pair */
103 static size_t cursor_set(Cursor *cursor, Line *line, int col);
105 void view_tabwidth_set(View *view, int tabwidth) {
106 view->tabwidth = tabwidth;
107 view_draw(view);
110 /* reset internal view data structures (cell matrix, line offsets etc.) */
111 static void view_clear(View *view) {
112 if (view->start != view->start_last) {
113 view->start_mark = text_mark_set(view->text, view->start);
114 view->start_last = view->start;
115 } else {
116 size_t start = text_mark_get(view->text, view->start_mark);
117 if (start != EPOS)
118 view->start = start;
120 view->topline = view->lines;
121 view->topline->lineno = text_lineno_by_pos(view->text, view->start);
122 view->lastline = view->topline;
124 /* reset all other lines */
125 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
126 size_t end = view->height * line_size;
127 Line *prev = NULL;
128 for (size_t i = 0; i < end; i += line_size) {
129 Line *line = (Line*)(((char*)view->lines) + i);
130 line->width = 0;
131 line->len = 0;
132 line->prev = prev;
133 if (prev)
134 prev->next = line;
135 prev = line;
137 view->bottomline = prev ? prev : view->topline;
138 view->bottomline->next = NULL;
139 view->line = view->topline;
140 view->col = 0;
143 Filerange view_viewport_get(View *view) {
144 return (Filerange){ .start = view->start, .end = view->end };
147 /* try to add another character to the view, return whether there was space left */
148 static bool view_addch(View *view, Cell *cell) {
149 if (!view->line)
150 return false;
152 int width;
153 size_t lineno = view->line->lineno;
155 switch (cell->data[0]) {
156 case '\t':
157 cell->istab = true;
158 cell->width = 1;
159 width = view->tabwidth - (view->col % view->tabwidth);
160 for (int w = 0; w < width; w++) {
161 if (view->col + 1 > view->width) {
162 view->line = view->line->next;
163 view->col = 0;
164 if (!view->line)
165 return false;
166 view->line->lineno = lineno;
169 cell->len = w == 0 ? 1 : 0;
170 int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
171 strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
172 cell->attr = view->symbols[t]->style;
173 view->line->cells[view->col] = *cell;
174 view->line->len += cell->len;
175 view->line->width += cell->width;
176 view->col++;
178 cell->len = 1;
179 return true;
180 case '\n':
181 cell->width = 1;
182 if (view->col + cell->width > view->width) {
183 view->line = view->line->next;
184 view->col = 0;
185 if (!view->line)
186 return false;
187 view->line->lineno = lineno;
190 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
191 cell->attr = view->symbols[SYNTAX_SYMBOL_EOL]->style;
193 view->line->cells[view->col] = *cell;
194 view->line->len += cell->len;
195 view->line->width += cell->width;
196 for (int i = view->col + 1; i < view->width; i++)
197 view->line->cells[i] = cell_blank;
199 view->line = view->line->next;
200 if (view->line)
201 view->line->lineno = lineno + 1;
202 view->col = 0;
203 return true;
204 default:
205 if ((unsigned char)cell->data[0] < 128 && !isprint((unsigned char)cell->data[0])) {
206 /* non-printable ascii char, represent it as ^(char + 64) */
207 *cell = (Cell) {
208 .data = { '^', cell->data[0] + 64, '\0' },
209 .len = 1,
210 .width = 2,
211 .istab = false,
212 .attr = cell->attr,
216 if (cell->data[0] == ' ') {
217 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
218 cell->attr = view->symbols[SYNTAX_SYMBOL_SPACE]->style;
222 if (view->col + cell->width > view->width) {
223 for (int i = view->col; i < view->width; i++)
224 view->line->cells[i] = cell_blank;
225 view->line = view->line->next;
226 view->col = 0;
229 if (view->line) {
230 view->line->width += cell->width;
231 view->line->len += cell->len;
232 view->line->lineno = lineno;
233 view->line->cells[view->col] = *cell;
234 view->col++;
235 /* set cells of a character which uses multiple columns */
236 for (int i = 1; i < cell->width; i++)
237 view->line->cells[view->col++] = cell_unused;
238 return true;
240 return false;
244 CursorPos view_cursor_getpos(View *view) {
245 Cursor *cursor = view->cursor;
246 Line *line = cursor->line;
247 CursorPos pos = { .line = line->lineno, .col = cursor->col };
248 while (line->prev && line->prev->lineno == pos.line) {
249 line = line->prev;
250 pos.col += line->width;
252 pos.col++;
253 return pos;
256 static void cursor_to(Cursor *c, size_t pos) {
257 Text *txt = c->view->text;
258 c->mark = text_mark_set(txt, pos);
259 if (pos != c->pos)
260 c->lastcol = 0;
261 c->pos = pos;
262 if (c->sel) {
263 size_t anchor = text_mark_get(txt, c->sel->anchor);
264 size_t cursor = text_mark_get(txt, c->sel->cursor);
265 /* do we have to change the orientation of the selection? */
266 if (pos < anchor && anchor < cursor) {
267 /* right extend -> left extend */
268 anchor = text_char_next(txt, anchor);
269 c->sel->anchor = text_mark_set(txt, anchor);
270 } else if (cursor < anchor && anchor <= pos) {
271 /* left extend -> right extend */
272 anchor = text_char_prev(txt, anchor);
273 c->sel->anchor = text_mark_set(txt, anchor);
275 if (anchor <= pos)
276 pos = text_char_next(txt, pos);
277 c->sel->cursor = text_mark_set(txt, pos);
279 if (!view_coord_get(c->view, pos, &c->line, &c->row, &c->col)) {
280 if (c->view->cursor == c) {
281 c->line = c->view->topline;
282 c->row = 0;
283 c->col = 0;
285 return;
287 // TODO: minimize number of redraws
288 view_draw(c->view);
291 static bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
292 int row = 0, col = 0;
293 size_t cur = view->start;
294 Line *line = view->topline;
296 if (pos < view->start || pos > view->end) {
297 if (retline) *retline = NULL;
298 if (retrow) *retrow = -1;
299 if (retcol) *retcol = -1;
300 return false;
303 while (line && line != view->lastline && cur < pos) {
304 if (cur + line->len > pos)
305 break;
306 cur += line->len;
307 line = line->next;
308 row++;
311 if (line) {
312 int max_col = MIN(view->width, line->width);
313 while (cur < pos && col < max_col) {
314 cur += line->cells[col].len;
315 /* skip over columns occupied by the same character */
316 while (++col < max_col && line->cells[col].len == 0);
318 } else {
319 line = view->bottomline;
320 row = view->height - 1;
323 if (retline) *retline = line;
324 if (retrow) *retrow = row;
325 if (retcol) *retcol = col;
326 return true;
329 /* move the cursor to the character at pos bytes from the begining of the file.
330 * if pos is not in the current viewport, redraw the view to make it visible */
331 void view_cursor_to(View *view, size_t pos) {
332 view_cursors_to(view->cursor, pos);
335 /* redraw the complete with data starting from view->start bytes into the file.
336 * stop once the screen is full, update view->end, view->lastline */
337 void view_draw(View *view) {
338 view_clear(view);
339 /* read a screenful of text */
340 const size_t text_size = view->width * view->height;
341 /* current buffer to work with */
342 char text[text_size+1];
343 /* remaining bytes to process in buffer */
344 size_t rem = text_bytes_get(view->text, view->start, text_size, text);
345 /* NUL terminate text section */
346 text[rem] = '\0';
347 /* absolute position of character currently being added to display */
348 size_t pos = view->start;
349 /* current position into buffer from which to interpret a character */
350 char *cur = text;
351 /* start from known multibyte state */
352 mbstate_t mbstate = { 0 };
354 while (rem > 0) {
356 /* current 'parsed' character' */
357 wchar_t wchar;
358 Cell cell;
359 memset(&cell, 0, sizeof cell);
361 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
362 if (len == (size_t)-1 && errno == EILSEQ) {
363 /* ok, we encountered an invalid multibyte sequence,
364 * replace it with the Unicode Replacement Character
365 * (FFFD) and skip until the start of the next utf8 char */
366 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
367 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1, .istab = false };
368 } else if (len == (size_t)-2) {
369 /* not enough bytes available to convert to a
370 * wide character. advance file position and read
371 * another junk into buffer.
373 rem = text_bytes_get(view->text, pos, text_size, text);
374 text[rem] = '\0';
375 cur = text;
376 continue;
377 } else if (len == 0) {
378 /* NUL byte encountered, store it and continue */
379 cell = (Cell){ .data = "\x00", .len = 1, .width = 0, .istab = false };
380 } else {
381 for (size_t i = 0; i < len; i++)
382 cell.data[i] = cur[i];
383 cell.data[len] = '\0';
384 cell.istab = false;
385 cell.len = len;
386 cell.width = wcwidth(wchar);
387 if (cell.width == -1)
388 cell.width = 1;
391 if (cur[0] == '\r' && rem > 1 && cur[1] == '\n') {
392 /* convert views style newline \r\n into a single char with len = 2 */
393 cell = (Cell){ .data = "\n", .len = 2, .width = 1, .istab = false };
396 if (!view_addch(view, &cell))
397 break;
399 rem -= cell.len;
400 cur += cell.len;
401 pos += cell.len;
404 /* set end of vieviewg region */
405 view->end = pos;
406 view->lastline = view->line ? view->line : view->bottomline;
408 /* clear remaining of line, important to show cursor at end of file */
409 if (view->line) {
410 for (int x = view->col; x < view->width; x++)
411 view->line->cells[x] = cell_blank;
414 /* resync position of cursors within visible area */
415 for (Cursor *c = view->cursors; c; c = c->next) {
416 size_t pos = view_cursors_pos(c);
417 if (view_coord_get(view, pos, &c->line, &c->row, &c->col)) {
418 c->line->cells[c->col].cursor = true;
419 if (view->ui) {
420 Line *line_match; int col_match;
421 size_t pos_match = text_bracket_match_except(view->text, pos, "<>");
422 if (pos != pos_match && view_coord_get(view, pos_match, &line_match, NULL, &col_match)) {
423 line_match->cells[col_match].selected = true;
426 } else if (c == view->cursor) {
427 c->line = view->topline;
428 c->row = 0;
429 c->col = 0;
433 view->need_update = true;
436 void view_update(View *view) {
437 if (!view->need_update)
438 return;
439 /* maximal number of bytes to consider for syntax highlighting before
440 * the visible area */
441 const size_t lexer_before_max = 4096;
442 /* absolute position to start syntax highlighting */
443 const size_t lexer_start = view->start >= lexer_before_max ? view->start - lexer_before_max : 0;
444 /* number of bytes used for syntax highlighting before visible are */
445 const size_t lexer_before = view->start - lexer_start;
446 /* number of bytes to read in one go */
447 const size_t text_size = lexer_before + (view->end - view->start);
448 /* current buffer to work with */
449 char text[text_size+1];
450 /* bytes to process */
451 const size_t text_len = text_bytes_get(view->text, lexer_start, text_size, text);
452 /* NUL terminate text section */
453 text[text_len] = '\0';
455 lua_State *L = view->lua;
456 if (L && view->lexer_name) {
458 lua_getglobal(L, "lexers");
459 lua_getfield(L, -1, "load");
460 lua_pushstring(L, view->lexer_name);
461 lua_pcall(L, 1, 1, 0);
463 lua_getfield(L, -1, "_TOKENSTYLES");
464 lua_getfield(L, -2, "lex");
466 lua_pushvalue(L, -3); /* lexer obj */
468 const char *lex_text = text;
469 if (lexer_start > 0) {
470 /* try to start lexing at a line boundry */
471 /* TODO: start at known state, handle nested lexers */
472 const char *newline = memchr(text, '\n', lexer_before);
473 if (newline)
474 lex_text = newline;
477 lua_pushlstring(L, lex_text, text_len - (lex_text - text));
478 lua_pushinteger(L, 1 /* inital style: whitespace */);
480 int token_count;
482 if (lua_isfunction(L, -4) && !lua_pcall(L, 3, 1, 0) && lua_istable(L, -1) &&
483 (token_count = lua_objlen(L, -1)) > 0) {
485 size_t pos = lexer_before - (lex_text - text);
486 Line *line = view->topline;
487 int col = 0;
489 for (int i = 1; i < token_count; i += 2) {
490 lua_rawgeti(L, -1, i);
491 //const char *name = lua_tostring(L, -1);
492 lua_gettable(L, -3); /* _TOKENSTYLES[token] */
493 size_t token_style = lua_tointeger(L, -1);
494 lua_pop(L, 1); /* style */
495 lua_rawgeti(L, -1, i + 1);
496 size_t token_end = lua_tointeger(L, -1) - 1;
497 lua_pop(L, 1); /* pos */
499 for (bool token_next = false; line; line = line->next, col = 0) {
500 for (; col < line->width; col++) {
501 if (pos < token_end) {
502 line->cells[col].attr = token_style;
503 pos += line->cells[col].len;
504 } else {
505 token_next = true;
506 break;
509 if (token_next)
510 break;
513 lua_pop(L, 1);
516 lua_pop(L, 3); /* _TOKENSTYLES, language specific lexer, lexers global */
519 for (Line *l = view->lastline->next; l; l = l->next) {
520 strncpy(l->cells[0].data, view->symbols[SYNTAX_SYMBOL_EOF]->symbol, sizeof(l->cells[0].data));
521 l->cells[0].attr = view->symbols[SYNTAX_SYMBOL_EOF]->style;
522 for (int x = 1; x < view->width; x++)
523 l->cells[x] = cell_blank;
524 l->width = 1;
525 l->len = 0;
528 for (Selection *s = view->selections; s; s = s->next) {
529 Filerange sel = view_selections_get(s);
530 if (text_range_valid(&sel)) {
531 Line *start_line; int start_col;
532 Line *end_line; int end_col;
533 view_coord_get(view, sel.start, &start_line, NULL, &start_col);
534 view_coord_get(view, sel.end, &end_line, NULL, &end_col);
535 if (start_line || end_line) {
536 if (!start_line) {
537 start_line = view->topline;
538 start_col = 0;
540 if (!end_line) {
541 end_line = view->lastline;
542 end_col = end_line->width;
544 for (Line *l = start_line; l != end_line->next; l = l->next) {
545 int col = (l == start_line) ? start_col : 0;
546 int end = (l == end_line) ? end_col : l->width;
547 while (col < end) {
548 l->cells[col++].selected = true;
553 if (view->events && view->events->selection)
554 view->events->selection(view->events->data, &sel);
558 if (view->ui)
559 view->ui->draw(view->ui);
560 view->need_update = false;
563 bool view_resize(View *view, int width, int height) {
564 if (width <= 0)
565 width = 1;
566 if (height <= 0)
567 height = 1;
568 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
569 if (lines_size > view->lines_size) {
570 Line *lines = realloc(view->lines, lines_size);
571 if (!lines)
572 return false;
573 view->lines = lines;
574 view->lines_size = lines_size;
576 view->width = width;
577 view->height = height;
578 if (view->lines)
579 memset(view->lines, 0, view->lines_size);
580 view_draw(view);
581 return true;
584 int view_height_get(View *view) {
585 return view->height;
588 int view_width_get(View *view) {
589 return view->width;
592 void view_free(View *view) {
593 if (!view)
594 return;
595 while (view->cursors)
596 view_cursors_free(view->cursors);
597 while (view->selections)
598 view_selections_free(view->selections);
599 free(view->lines);
600 free(view->lexer_name);
601 free(view);
604 void view_reload(View *view, Text *text) {
605 view->text = text;
606 view_selections_clear(view);
607 view_cursor_to(view, 0);
610 View *view_new(Text *text, lua_State *lua, ViewEvent *events) {
611 if (!text)
612 return NULL;
613 View *view = calloc(1, sizeof(View));
614 if (!view)
615 return NULL;
616 if (!view_cursors_new(view)) {
617 view_free(view);
618 return NULL;
621 view->text = text;
622 view->lua = lua;
623 view->events = events;
624 view->tabwidth = 8;
625 view_options_set(view, 0);
627 if (!view_resize(view, 1, 1)) {
628 view_free(view);
629 return NULL;
632 view_cursor_to(view, 0);
634 return view;
637 void view_ui(View *view, UiWin* ui) {
638 view->ui = ui;
641 static size_t cursor_set(Cursor *cursor, Line *line, int col) {
642 int row = 0;
643 View *view = cursor->view;
644 size_t pos = view->start;
645 /* get row number and file offset at start of the given line */
646 for (Line *cur = view->topline; cur && cur != line; cur = cur->next) {
647 pos += cur->len;
648 row++;
651 /* for characters which use more than 1 column, make sure we are on the left most */
652 while (col > 0 && line->cells[col].len == 0)
653 col--;
654 while (col < line->width && line->cells[col].istab)
655 col++;
657 /* calculate offset within the line */
658 for (int i = 0; i < col; i++)
659 pos += line->cells[i].len;
661 cursor->col = col;
662 cursor->row = row;
663 cursor->line = line;
665 cursor_to(cursor, pos);
667 return pos;
670 bool view_viewport_down(View *view, int n) {
671 Line *line;
672 if (view->end == text_size(view->text))
673 return false;
674 if (n >= view->height) {
675 view->start = view->end;
676 } else {
677 for (line = view->topline; line && n > 0; line = line->next, n--)
678 view->start += line->len;
680 view_draw(view);
681 return true;
684 bool view_viewport_up(View *view, int n) {
685 /* scrolling up is somewhat tricky because we do not yet know where
686 * the lines start, therefore scan backwards but stop at a reasonable
687 * maximum in case we are dealing with a file without any newlines
689 if (view->start == 0)
690 return false;
691 size_t max = view->width * view->height;
692 char c;
693 Iterator it = text_iterator_get(view->text, view->start - 1);
695 if (!text_iterator_byte_get(&it, &c))
696 return false;
697 size_t off = 0;
698 /* skip newlines immediately before display area */
699 if (c == '\n' && text_iterator_byte_prev(&it, &c))
700 off++;
701 if (c == '\r' && text_iterator_byte_prev(&it, &c))
702 off++;
703 do {
704 if (c == '\n' && --n == 0)
705 break;
706 if (++off > max)
707 break;
708 } while (text_iterator_byte_prev(&it, &c));
709 if (c == '\r')
710 off++;
711 view->start -= off;
712 view_draw(view);
713 return true;
716 void view_redraw_top(View *view) {
717 Line *line = view->cursor->line;
718 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
719 view->start += cur->len;
720 view_draw(view);
721 view_cursor_to(view, view->cursor->pos);
724 void view_redraw_center(View *view) {
725 int center = view->height / 2;
726 size_t pos = view->cursor->pos;
727 for (int i = 0; i < 2; i++) {
728 int linenr = 0;
729 Line *line = view->cursor->line;
730 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
731 linenr++;
732 if (linenr < center) {
733 view_slide_down(view, center - linenr);
734 continue;
736 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
737 view->start += cur->len;
738 linenr--;
740 break;
742 view_draw(view);
743 view_cursor_to(view, pos);
746 void view_redraw_bottom(View *view) {
747 Line *line = view->cursor->line;
748 if (line == view->lastline)
749 return;
750 size_t pos = view->cursor->pos;
751 view_viewport_up(view, view->height);
752 while (pos > view->end && view_viewport_down(view, 1));
753 view_cursor_to(view, pos);
756 size_t view_slide_up(View *view, int lines) {
757 Cursor *cursor = view->cursor;
758 if (view_viewport_down(view, lines)) {
759 if (cursor->line == view->topline)
760 cursor_set(cursor, view->topline, cursor->col);
761 else
762 view_cursor_to(view, cursor->pos);
763 } else {
764 view_screenline_down(cursor);
766 return cursor->pos;
769 size_t view_slide_down(View *view, int lines) {
770 Cursor *cursor = view->cursor;
771 if (view_viewport_up(view, lines)) {
772 if (cursor->line == view->lastline)
773 cursor_set(cursor, view->lastline, cursor->col);
774 else
775 view_cursor_to(view, cursor->pos);
776 } else {
777 view_screenline_up(cursor);
779 return cursor->pos;
782 size_t view_scroll_up(View *view, int lines) {
783 Cursor *cursor = view->cursor;
784 if (view_viewport_up(view, lines)) {
785 Line *line = cursor->line < view->lastline ? cursor->line : view->lastline;
786 cursor_set(cursor, line, view->cursor->col);
787 } else {
788 view_cursor_to(view, 0);
790 return cursor->pos;
793 size_t view_scroll_down(View *view, int lines) {
794 Cursor *cursor = view->cursor;
795 if (view_viewport_down(view, lines)) {
796 Line *line = cursor->line > view->topline ? cursor->line : view->topline;
797 cursor_set(cursor, line, cursor->col);
798 } else {
799 view_cursor_to(view, text_size(view->text));
801 return cursor->pos;
804 size_t view_line_up(Cursor *cursor) {
805 if (cursor->line && cursor->line->prev && cursor->line->prev->prev &&
806 cursor->line->lineno != cursor->line->prev->lineno &&
807 cursor->line->prev->lineno != cursor->line->prev->prev->lineno)
808 return view_screenline_up(cursor);
809 size_t pos = text_line_up(cursor->view->text, cursor->pos);
810 view_cursors_to(cursor, pos);
811 return pos;
814 size_t view_line_down(Cursor *cursor) {
815 if (cursor->line && (!cursor->line->next || cursor->line->next->lineno != cursor->line->lineno))
816 return view_screenline_down(cursor);
817 size_t pos = text_line_down(cursor->view->text, cursor->pos);
818 view_cursors_to(cursor, pos);
819 return pos;
822 size_t view_screenline_up(Cursor *cursor) {
823 int lastcol = cursor->lastcol;
824 if (!lastcol)
825 lastcol = cursor->col;
826 if (!cursor->line->prev)
827 view_scroll_up(cursor->view, 1);
828 if (cursor->line->prev)
829 cursor_set(cursor, cursor->line->prev, lastcol);
830 cursor->lastcol = lastcol;
831 return cursor->pos;
834 size_t view_screenline_down(Cursor *cursor) {
835 int lastcol = cursor->lastcol;
836 if (!lastcol)
837 lastcol = cursor->col;
838 if (!cursor->line->next && cursor->line == cursor->view->bottomline)
839 view_scroll_down(cursor->view, 1);
840 if (cursor->line->next)
841 cursor_set(cursor, cursor->line->next, lastcol);
842 cursor->lastcol = lastcol;
843 return cursor->pos;
846 size_t view_screenline_begin(Cursor *cursor) {
847 if (!cursor->line)
848 return cursor->pos;
849 return cursor_set(cursor, cursor->line, 0);
852 size_t view_screenline_middle(Cursor *cursor) {
853 if (!cursor->line)
854 return cursor->pos;
855 return cursor_set(cursor, cursor->line, cursor->line->width / 2);
858 size_t view_screenline_end(Cursor *cursor) {
859 if (!cursor->line)
860 return cursor->pos;
861 int col = cursor->line->width - 1;
862 return cursor_set(cursor, cursor->line, col >= 0 ? col : 0);
865 size_t view_cursor_get(View *view) {
866 return view_cursors_pos(view->cursor);
869 const Line *view_lines_get(View *view) {
870 return view->topline;
873 void view_scroll_to(View *view, size_t pos) {
874 view_cursors_scroll_to(view->cursor, pos);
877 bool view_syntax_set(View *view, const char *name) {
878 if (!name) {
879 free(view->lexer_name);
880 view->lexer_name = NULL;
881 return true;
884 lua_State *L = view->lua;
885 if (!L)
886 return false;
888 /* Try to load the specified lexer and parse its token styles.
889 * Roughly equivalent to the following lua code:
891 * lang = lexers.load(name)
892 * for token_name, id in pairs(lang._TOKENSTYLES) do
893 * ui->syntax_style(id, lexers:get_style(lang, token_name);
895 lua_getglobal(L, "lexers");
897 lua_getfield(L, -1, "STYLE_DEFAULT");
898 view->ui->syntax_style(view->ui, UI_STYLE_DEFAULT, lua_tostring(L, -1));
899 lua_pop(L, 1);
900 lua_getfield(L, -1, "STYLE_CURSOR");
901 view->ui->syntax_style(view->ui, UI_STYLE_CURSOR, lua_tostring(L, -1));
902 lua_pop(L, 1);
903 lua_getfield(L, -1, "STYLE_CURSOR_LINE");
904 view->ui->syntax_style(view->ui, UI_STYLE_CURSOR_LINE, lua_tostring(L, -1));
905 lua_pop(L, 1);
906 lua_getfield(L, -1, "STYLE_SELECTION");
907 view->ui->syntax_style(view->ui, UI_STYLE_SELECTION, lua_tostring(L, -1));
908 lua_pop(L, 1);
909 lua_getfield(L, -1, "STYLE_LINENUMBER");
910 view->ui->syntax_style(view->ui, UI_STYLE_LINENUMBER, lua_tostring(L, -1));
911 lua_pop(L, 1);
914 lua_getfield(L, -1, "load");
915 lua_pushstring(L, name);
917 if (lua_pcall(L, 1, 1, 0))
918 return false;
920 if (!lua_istable(L, -1)) {
921 lua_pop(L, 2);
922 return false;
925 view->lexer_name = strdup(name);
926 /* loop through all _TOKENSTYLES and parse them */
927 lua_getfield(L, -1, "_TOKENSTYLES");
928 lua_pushnil(L); /* first key */
930 while (lua_next(L, -2)) {
931 size_t id = lua_tointeger(L, -1);
932 //const char *name = lua_tostring(L, -2);
933 lua_pop(L, 1); /* remove value (=id), keep key (=name) */
934 lua_getfield(L, -4, "get_style");
935 lua_pushvalue(L, -5); /* lexer */
936 lua_pushvalue(L, -5); /* lang */
937 lua_pushvalue(L, -4); /* token_name */
938 if (lua_pcall(L, 3, 1, 0))
939 return false;
940 const char *style = lua_tostring(L, -1);
941 //printf("%s\t%d\t%s\n", name, id, style);
942 view->ui->syntax_style(view->ui, id, style);
943 lua_pop(L, 1); /* style */
946 lua_pop(L, 1); /* _TOKENSTYLES */
947 lua_pop(L, 1); /* grammar */
949 lua_pop(L, 1); /* lexers */
951 return true;
954 const char *view_syntax_get(View *view) {
955 return view->lexer_name;
958 void view_options_set(View *view, enum UiOption options) {
959 int mapping[] = {
960 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
961 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
962 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
963 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
964 [SYNTAX_SYMBOL_EOF] = UI_OPTION_SYMBOL_EOF,
966 for (int i = 0; i < LENGTH(mapping); i++) {
967 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
968 &symbols_none[i];
970 if (view->ui)
971 view->ui->options_set(view->ui, options);
974 enum UiOption view_options_get(View *view) {
975 return view->ui ? view->ui->options_get(view->ui) : 0;
978 size_t view_screenline_goto(View *view, int n) {
979 size_t pos = view->start;
980 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
981 pos += line->len;
982 return pos;
985 Cursor *view_cursors_new(View *view) {
986 Cursor *c = calloc(1, sizeof(*c));
987 if (!c)
988 return NULL;
990 c->view = view;
991 c->next = view->cursors;
992 if (view->cursors)
993 view->cursors->prev = c;
994 view->cursors = c;
995 view->cursor = c;
996 return c;
999 int view_cursors_count(View *view) {
1000 int i = 0;
1001 for (Cursor *c = view_cursors(view); c; c = view_cursors_next(c))
1002 i++;
1003 return i;
1006 static void view_cursors_free(Cursor *c) {
1007 if (!c)
1008 return;
1009 register_release(&c->reg);
1010 if (c->prev)
1011 c->prev->next = c->next;
1012 if (c->next)
1013 c->next->prev = c->prev;
1014 if (c->view->cursors == c)
1015 c->view->cursors = c->next;
1016 if (c->view->cursor == c)
1017 c->view->cursor = c->next ? c->next : c->prev;
1018 free(c);
1021 void view_cursors_dispose(Cursor *c) {
1022 if (!c)
1023 return;
1024 View *view = c->view;
1025 if (view->cursors && view->cursors->next) {
1026 view_selections_free(c->sel);
1027 view_cursors_free(c);
1028 view_draw(view);
1032 Cursor *view_cursors(View *view) {
1033 return view->cursors;
1036 Cursor *view_cursor(View *view) {
1037 return view->cursor;
1040 Cursor *view_cursors_prev(Cursor *c) {
1041 return c->prev;
1044 Cursor *view_cursors_next(Cursor *c) {
1045 return c->next;
1048 size_t view_cursors_pos(Cursor *c) {
1049 return text_mark_get(c->view->text, c->mark);
1052 Register *view_cursors_register(Cursor *c) {
1053 return &c->reg;
1056 void view_cursors_scroll_to(Cursor *c, size_t pos) {
1057 View *view = c->view;
1058 if (view->cursor == c) {
1059 while (pos < view->start && view_viewport_up(view, 1));
1060 while (pos > view->end && view_viewport_down(view, 1));
1062 view_cursors_to(c, pos);
1065 void view_cursors_to(Cursor *c, size_t pos) {
1066 View *view = c->view;
1067 if (c->view->cursors == c) {
1068 c->mark = text_mark_set(view->text, pos);
1070 size_t max = text_size(view->text);
1071 if (pos == max && view->end != max) {
1072 /* do not display an empty screen when shoviewg the end of the file */
1073 view->start = pos;
1074 view_viewport_up(view, view->height / 2);
1075 } else {
1076 /* set the start of the viewable region to the start of the line on which
1077 * the cursor should be placed. if this line requires more space than
1078 * available in the view then simply start displaying text at the new
1079 * cursor position */
1080 for (int i = 0; i < 2 && (pos < view->start || pos > view->end); i++) {
1081 view->start = i == 0 ? text_line_begin(view->text, pos) : pos;
1082 view_draw(view);
1087 cursor_to(c, pos);
1090 void view_cursors_selection_start(Cursor *c) {
1091 if (c->sel)
1092 return;
1093 size_t pos = view_cursors_pos(c);
1094 if (pos == EPOS || !(c->sel = view_selections_new(c->view)))
1095 return;
1096 Text *txt = c->view->text;
1097 c->sel->anchor = text_mark_set(txt, pos);
1098 c->sel->cursor = text_mark_set(txt, text_char_next(txt, pos));
1099 view_draw(c->view);
1102 void view_cursors_selection_restore(Cursor *c) {
1103 Text *txt = c->view->text;
1104 if (c->sel)
1105 return;
1106 Filerange sel = text_range_new(
1107 text_mark_get(txt, c->lastsel_anchor),
1108 text_mark_get(txt, c->lastsel_cursor)
1110 if (!text_range_valid(&sel))
1111 return;
1112 if (!(c->sel = view_selections_new(c->view)))
1113 return;
1114 view_selections_set(c->sel, &sel);
1115 view_cursors_selection_sync(c);
1116 view_draw(c->view);
1119 void view_cursors_selection_stop(Cursor *c) {
1120 c->sel = NULL;
1123 void view_cursors_selection_clear(Cursor *c) {
1124 view_selections_free(c->sel);
1125 view_draw(c->view);
1128 void view_cursors_selection_swap(Cursor *c) {
1129 if (!c->sel)
1130 return;
1131 view_selections_swap(c->sel);
1132 view_cursors_selection_sync(c);
1135 void view_cursors_selection_sync(Cursor *c) {
1136 if (!c->sel)
1137 return;
1138 Text *txt = c->view->text;
1139 size_t anchor = text_mark_get(txt, c->sel->anchor);
1140 size_t cursor = text_mark_get(txt, c->sel->cursor);
1141 bool right_extending = anchor < cursor;
1142 if (right_extending)
1143 cursor = text_char_prev(txt, cursor);
1144 view_cursors_to(c, cursor);
1147 Filerange view_cursors_selection_get(Cursor *c) {
1148 return view_selections_get(c->sel);
1151 void view_cursors_selection_set(Cursor *c, Filerange *r) {
1152 if (!text_range_valid(r))
1153 return;
1154 if (!c->sel)
1155 c->sel = view_selections_new(c->view);
1156 if (!c->sel)
1157 return;
1159 view_selections_set(c->sel, r);
1162 Selection *view_selections_new(View *view) {
1163 Selection *s = calloc(1, sizeof(*s));
1164 if (!s)
1165 return NULL;
1167 s->view = view;
1168 s->next = view->selections;
1169 if (view->selections)
1170 view->selections->prev = s;
1171 view->selections = s;
1172 return s;
1175 void view_selections_free(Selection *s) {
1176 if (!s)
1177 return;
1178 if (s->prev)
1179 s->prev->next = s->next;
1180 if (s->next)
1181 s->next->prev = s->prev;
1182 if (s->view->selections == s)
1183 s->view->selections = s->next;
1184 // XXX: add backlink Selection->Cursor?
1185 for (Cursor *c = s->view->cursors; c; c = c->next) {
1186 if (c->sel == s) {
1187 c->lastsel_anchor = s->anchor;
1188 c->lastsel_cursor = s->cursor;
1189 c->sel = NULL;
1192 free(s);
1195 void view_selections_clear(View *view) {
1196 while (view->selections)
1197 view_selections_free(view->selections);
1198 view_draw(view);
1201 void view_cursors_clear(View *view) {
1202 for (Cursor *c = view->cursors, *next; c; c = next) {
1203 next = c->next;
1204 if (c != view->cursor) {
1205 view_selections_free(c->sel);
1206 view_cursors_free(c);
1209 view_draw(view);
1212 void view_selections_swap(Selection *s) {
1213 Mark temp = s->anchor;
1214 s->anchor = s->cursor;
1215 s->cursor = temp;
1218 Selection *view_selections(View *view) {
1219 return view->selections;
1222 Selection *view_selections_prev(Selection *s) {
1223 return s->prev;
1226 Selection *view_selections_next(Selection *s) {
1227 return s->next;
1230 Filerange view_selections_get(Selection *s) {
1231 if (!s)
1232 return text_range_empty();
1233 Text *txt = s->view->text;
1234 size_t anchor = text_mark_get(txt, s->anchor);
1235 size_t cursor = text_mark_get(txt, s->cursor);
1236 return text_range_new(anchor, cursor);
1239 void view_selections_set(Selection *s, Filerange *r) {
1240 if (!text_range_valid(r))
1241 return;
1242 Text *txt = s->view->text;
1243 size_t anchor = text_mark_get(txt, s->anchor);
1244 size_t cursor = text_mark_get(txt, s->cursor);
1245 bool left_extending = anchor > cursor;
1246 if (left_extending) {
1247 s->anchor = text_mark_set(txt, r->end);
1248 s->cursor = text_mark_set(txt, r->start);
1249 } else {
1250 s->anchor = text_mark_set(txt, r->start);
1251 s->cursor = text_mark_set(txt, r->end);
1253 view_draw(s->view);