vis: reject invalid register name when recording a macro
[vis.git] / view.c
blob7e893ffec34d718494c681126a70a9ab932f89bb
1 #include <string.h>
2 #include <stdlib.h>
3 #include <wchar.h>
4 #include <ctype.h>
5 #include <errno.h>
6 #include <limits.h>
7 #include "view.h"
8 #include "text.h"
9 #include "text-motions.h"
10 #include "text-util.h"
11 #include "util.h"
13 typedef struct {
14 char *symbol;
15 } SyntaxSymbol;
17 enum {
18 SYNTAX_SYMBOL_SPACE,
19 SYNTAX_SYMBOL_TAB,
20 SYNTAX_SYMBOL_TAB_FILL,
21 SYNTAX_SYMBOL_EOL,
22 SYNTAX_SYMBOL_LAST,
25 /* A selection is made up of two marks named cursor and anchor.
26 * While the anchor remains fixed the cursor mark follows cursor motions.
27 * For a selection (indicated by []), the marks (^) are placed as follows:
29 * [some text] [!]
30 * ^ ^ ^
31 * ^
33 * That is the marks point to the *start* of the first and last character
34 * of the selection. In particular for a single character selection (as
35 * depicted on the right above) both marks point to the same location.
37 * The view_selections_{get,set} functions take care of adding/removing
38 * the necessary offset for the last character.
40 struct Selection {
41 Mark anchor; /* position where the selection was created */
42 Mark cursor; /* other selection endpoint where it changes */
43 View *view; /* associated view to which this selection belongs */
44 Cursor *cur; /* cursor whose movement will affect this selection */
45 Selection *prev, *next; /* previsous/next selections in no particular order */
48 struct Cursor { /* cursor position */
49 size_t pos; /* in bytes from the start of the file */
50 int row, col; /* in terms of zero based screen coordinates */
51 int lastcol; /* remembered column used when moving across lines */
52 Line *line; /* screen line on which cursor currently resides */
53 Mark mark; /* mark used to keep track of current cursor position */
54 Mark mark_old; /* previous value of the mark, used to recover cursor position */
55 Selection *sel; /* selection (if any) which folows the cursor upon movement */
56 Mark lastsel_anchor;/* previously used selection data, */
57 Mark lastsel_cursor;/* used to restore it */
58 Register reg; /* per cursor register to support yank/put operation */
59 int generation; /* used to filter out newly created cursors during iteration */
60 int number; /* how many cursors are located before this one */
61 View *view; /* associated view to which this cursor belongs */
62 Cursor *prev, *next;/* previous/next cursors ordered by location at creation time */
65 /* Viewable area, showing part of a file. Keeps track of cursors and selections.
66 * At all times there exists at least one cursor, which is placed in the visible viewport.
67 * Additional cursors can be created and positioned anywhere in the file. */
68 struct View {
69 Text *text; /* underlying text management */
70 UiWin *ui;
71 Cell cell_blank; /* used for empty/blank cells */
72 int width, height; /* size of display area */
73 size_t start, end; /* currently displayed area [start, end] in bytes from the start of the file */
74 size_t start_last; /* previously used start of visible area, used to update the mark */
75 Mark start_mark; /* mark to keep track of the start of the visible area */
76 size_t lines_size; /* number of allocated bytes for lines (grows only) */
77 Line *lines; /* view->height number of lines representing view content */
78 Line *topline; /* top of the view, first line currently shown */
79 Line *lastline; /* last currently used line, always <= bottomline */
80 Line *bottomline; /* bottom of view, might be unused if lastline < bottomline */
81 Cursor *cursor; /* main cursor, always placed within the visible viewport */
82 Cursor *cursor_latest; /* most recently created cursor */
83 Cursor *cursor_dead;/* main cursor which was disposed, will be removed when another cursor is created */
84 int cursor_count; /* how many cursors do currently exist */
85 Line *line; /* used while drawing view content, line where next char will be drawn */
86 int col; /* used while drawing view content, column where next char will be drawn */
87 const SyntaxSymbol *symbols[SYNTAX_SYMBOL_LAST]; /* symbols to use for white spaces etc */
88 int tabwidth; /* how many spaces should be used to display a tab character */
89 Cursor *cursors; /* all cursors currently active */
90 Selection *selections; /* all selected regions */
91 int cursor_generation; /* used to filter out newly created cursors during iteration */
92 bool need_update; /* whether view has been redrawn */
93 bool large_file; /* optimize for displaying large files */
94 int colorcolumn;
97 static const SyntaxSymbol symbols_none[] = {
98 [SYNTAX_SYMBOL_SPACE] = { " " },
99 [SYNTAX_SYMBOL_TAB] = { " " },
100 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
101 [SYNTAX_SYMBOL_EOL] = { " " },
104 static const SyntaxSymbol symbols_default[] = {
105 [SYNTAX_SYMBOL_SPACE] = { "·" /* Middle Dot U+00B7 */ },
106 [SYNTAX_SYMBOL_TAB] = { "›" /* Single Right-Pointing Angle Quotation Mark U+203A */ },
107 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
108 [SYNTAX_SYMBOL_EOL] = { "↵" /* Downwards Arrow with Corner Leftwards U+21B5 */ },
111 static Cell cell_unused;
113 static void view_clear(View *view);
114 static bool view_addch(View *view, Cell *cell);
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 void view_tabwidth_set(View *view, int tabwidth) {
120 view->tabwidth = tabwidth;
121 view_draw(view);
124 /* reset internal view data structures (cell matrix, line offsets etc.) */
125 static void view_clear(View *view) {
126 memset(view->lines, 0, view->lines_size);
127 if (view->start != view->start_last) {
128 if (view->start == 0)
129 view->start_mark = EMARK;
130 else
131 view->start_mark = text_mark_set(view->text, view->start);
132 } else {
133 size_t start;
134 if (view->start_mark == EMARK)
135 start = 0;
136 else
137 start = text_mark_get(view->text, view->start_mark);
138 if (start != EPOS)
139 view->start = start;
142 view->start_last = view->start;
143 view->topline = view->lines;
144 view->topline->lineno = view->large_file ? 1 : text_lineno_by_pos(view->text, view->start);
145 view->lastline = view->topline;
147 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
148 size_t end = view->height * line_size;
149 Line *prev = NULL;
150 for (size_t i = 0; i < end; i += line_size) {
151 Line *line = (Line*)(((char*)view->lines) + i);
152 line->prev = prev;
153 if (prev)
154 prev->next = line;
155 prev = line;
157 view->bottomline = prev ? prev : view->topline;
158 view->bottomline->next = NULL;
159 view->line = view->topline;
160 view->col = 0;
161 if (view->ui)
162 view->cell_blank.style = view->ui->style_get(view->ui, UI_STYLE_DEFAULT);
165 Filerange view_viewport_get(View *view) {
166 return (Filerange){ .start = view->start, .end = view->end };
169 /* try to add another character to the view, return whether there was space left */
170 static bool view_addch(View *view, Cell *cell) {
171 if (!view->line)
172 return false;
174 int width;
175 size_t lineno = view->line->lineno;
176 unsigned char ch = (unsigned char)cell->data[0];
177 cell->style = view->cell_blank.style;
179 switch (ch) {
180 case '\t':
181 cell->width = 1;
182 width = view->tabwidth - (view->col % view->tabwidth);
183 for (int w = 0; w < width; w++) {
184 if (view->col + 1 > view->width) {
185 view->line = view->line->next;
186 view->col = 0;
187 if (!view->line)
188 return false;
189 view->line->lineno = lineno;
192 cell->len = w == 0 ? 1 : 0;
193 int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
194 strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
195 view->line->cells[view->col] = *cell;
196 view->line->len += cell->len;
197 view->line->width += cell->width;
198 view->col++;
200 cell->len = 1;
201 return true;
202 case '\n':
203 cell->width = 1;
204 if (view->col + cell->width > view->width) {
205 view->line = view->line->next;
206 view->col = 0;
207 if (!view->line)
208 return false;
209 view->line->lineno = lineno;
212 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
214 view->line->cells[view->col] = *cell;
215 view->line->len += cell->len;
216 view->line->width += cell->width;
217 for (int i = view->col + 1; i < view->width; i++)
218 view->line->cells[i] = view->cell_blank;
220 view->line = view->line->next;
221 if (view->line)
222 view->line->lineno = lineno + 1;
223 view->col = 0;
224 return true;
225 default:
226 if (ch < 128 && !isprint(ch)) {
227 /* non-printable ascii char, represent it as ^(char + 64) */
228 *cell = (Cell) {
229 .data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
230 .len = 1,
231 .width = 2,
232 .style = cell->style,
236 if (ch == ' ') {
237 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
241 if (view->col + cell->width > view->width) {
242 for (int i = view->col; i < view->width; i++)
243 view->line->cells[i] = view->cell_blank;
244 view->line = view->line->next;
245 view->col = 0;
248 if (view->line) {
249 view->line->width += cell->width;
250 view->line->len += cell->len;
251 view->line->lineno = lineno;
252 view->line->cells[view->col] = *cell;
253 view->col++;
254 /* set cells of a character which uses multiple columns */
255 for (int i = 1; i < cell->width; i++)
256 view->line->cells[view->col++] = cell_unused;
257 return true;
259 return false;
263 static void cursor_to(Cursor *c, size_t pos) {
264 Text *txt = c->view->text;
265 c->mark_old = c->mark;
266 c->mark = text_mark_set(txt, pos);
267 if (pos != c->pos)
268 c->lastcol = 0;
269 c->pos = pos;
270 if (c->sel)
271 c->sel->cursor = text_mark_set(txt, pos);
272 if (!view_coord_get(c->view, pos, &c->line, &c->row, &c->col)) {
273 if (c->view->cursor == c) {
274 c->line = c->view->topline;
275 c->row = 0;
276 c->col = 0;
278 return;
280 // TODO: minimize number of redraws
281 view_draw(c->view);
284 bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
285 int row = 0, col = 0;
286 size_t cur = view->start;
287 Line *line = view->topline;
289 if (pos < view->start || pos > view->end) {
290 if (retline) *retline = NULL;
291 if (retrow) *retrow = -1;
292 if (retcol) *retcol = -1;
293 return false;
296 while (line && line != view->lastline && cur < pos) {
297 if (cur + line->len > pos)
298 break;
299 cur += line->len;
300 line = line->next;
301 row++;
304 if (line) {
305 int max_col = MIN(view->width, line->width);
306 while (cur < pos && col < max_col) {
307 cur += line->cells[col].len;
308 /* skip over columns occupied by the same character */
309 while (++col < max_col && line->cells[col].len == 0);
311 } else {
312 line = view->bottomline;
313 row = view->height - 1;
316 if (retline) *retline = line;
317 if (retrow) *retrow = row;
318 if (retcol) *retcol = col;
319 return true;
322 /* move the cursor to the character at pos bytes from the begining of the file.
323 * if pos is not in the current viewport, redraw the view to make it visible */
324 void view_cursor_to(View *view, size_t pos) {
325 view_cursors_to(view->cursor, pos);
328 /* redraw the complete with data starting from view->start bytes into the file.
329 * stop once the screen is full, update view->end, view->lastline */
330 void view_draw(View *view) {
331 view_clear(view);
332 /* read a screenful of text considering each character as 4-byte UTF character*/
333 const size_t size = view->width * view->height * 4;
334 /* current buffer to work with */
335 char text[size+1];
336 /* remaining bytes to process in buffer */
337 size_t rem = text_bytes_get(view->text, view->start, size, text);
338 /* NUL terminate text section */
339 text[rem] = '\0';
340 /* absolute position of character currently being added to display */
341 size_t pos = view->start;
342 /* current position into buffer from which to interpret a character */
343 char *cur = text;
344 /* start from known multibyte state */
345 mbstate_t mbstate = { 0 };
347 Cell cell = { .data = "", .len = 0, .width = 0, }, prev_cell = cell;
349 while (rem > 0) {
351 /* current 'parsed' character' */
352 wchar_t wchar;
354 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
355 if (len == (size_t)-1 && errno == EILSEQ) {
356 /* ok, we encountered an invalid multibyte sequence,
357 * replace it with the Unicode Replacement Character
358 * (FFFD) and skip until the start of the next utf8 char */
359 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
360 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
361 } else if (len == (size_t)-2) {
362 /* not enough bytes available to convert to a
363 * wide character. advance file position and read
364 * another junk into buffer.
366 rem = text_bytes_get(view->text, pos, size, text);
367 text[rem] = '\0';
368 cur = text;
369 continue;
370 } else if (len == 0) {
371 /* NUL byte encountered, store it and continue */
372 cell = (Cell){ .data = "\x00", .len = 1, .width = 2 };
373 } else {
374 if (len >= sizeof(cell.data))
375 len = sizeof(cell.data)-1;
376 for (size_t i = 0; i < len; i++)
377 cell.data[i] = cur[i];
378 cell.data[len] = '\0';
379 cell.len = len;
380 cell.width = wcwidth(wchar);
381 if (cell.width == -1)
382 cell.width = 1;
385 if (cell.width == 0 && prev_cell.len + cell.len < sizeof(cell.data)) {
386 prev_cell.len += cell.len;
387 strcat(prev_cell.data, cell.data);
388 } else {
389 if (prev_cell.len && !view_addch(view, &prev_cell))
390 break;
391 pos += prev_cell.len;
392 prev_cell = cell;
395 rem -= cell.len;
396 cur += cell.len;
398 memset(&cell, 0, sizeof cell);
401 if (prev_cell.len && view_addch(view, &prev_cell))
402 pos += prev_cell.len;
404 /* set end of viewing region */
405 view->end = pos;
406 if (view->line) {
407 bool eof = view->end == text_size(view->text);
408 if (view->line->len == 0 && eof && view->line->prev)
409 view->lastline = view->line->prev;
410 else
411 view->lastline = view->line;
412 } else {
413 view->lastline = view->bottomline;
416 /* clear remaining of line, important to show cursor at end of file */
417 if (view->line) {
418 for (int x = view->col; x < view->width; x++)
419 view->line->cells[x] = view->cell_blank;
422 /* resync position of cursors within visible area */
423 for (Cursor *c = view->cursors; c; c = c->next) {
424 size_t pos = view_cursors_pos(c);
425 if (!view_coord_get(view, pos, &c->line, &c->row, &c->col) &&
426 c == view->cursor) {
427 c->line = view->topline;
428 c->row = 0;
429 c->col = 0;
433 view->need_update = true;
436 void view_invalidate(View *view) {
437 view->need_update = true;
440 bool view_update(View *view) {
441 if (!view->need_update)
442 return false;
443 for (Line *l = view->lastline->next; l; l = l->next) {
444 for (int x = 0; x < view->width; x++)
445 l->cells[x] = view->cell_blank;
447 view->need_update = false;
448 return true;
451 bool view_resize(View *view, int width, int height) {
452 if (width <= 0)
453 width = 1;
454 if (height <= 0)
455 height = 1;
456 if (view->width == width && view->height == height) {
457 view->need_update = true;
458 return true;
460 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
461 if (lines_size > view->lines_size) {
462 Line *lines = realloc(view->lines, lines_size);
463 if (!lines)
464 return false;
465 view->lines = lines;
466 view->lines_size = lines_size;
468 view->width = width;
469 view->height = height;
470 memset(view->lines, 0, view->lines_size);
471 view_draw(view);
472 return true;
475 int view_height_get(View *view) {
476 return view->height;
479 int view_width_get(View *view) {
480 return view->width;
483 void view_free(View *view) {
484 if (!view)
485 return;
486 while (view->cursors)
487 view_cursors_free(view->cursors);
488 while (view->selections)
489 view_selections_free(view->selections);
490 free(view->lines);
491 free(view);
494 void view_reload(View *view, Text *text) {
495 view->text = text;
496 view_selections_clear(view);
497 view_cursor_to(view, 0);
500 View *view_new(Text *text) {
501 if (!text)
502 return NULL;
503 View *view = calloc(1, sizeof(View));
504 if (!view)
505 return NULL;
506 if (!view_cursors_new(view, 0)) {
507 view_free(view);
508 return NULL;
511 view->cell_blank = (Cell) {
512 .width = 0,
513 .len = 0,
514 .data = " ",
516 view->text = text;
517 view->tabwidth = 8;
518 view_options_set(view, 0);
520 if (!view_resize(view, 1, 1)) {
521 view_free(view);
522 return NULL;
525 view_cursor_to(view, 0);
527 return view;
530 void view_ui(View *view, UiWin* ui) {
531 view->ui = ui;
534 static size_t cursor_set(Cursor *cursor, Line *line, int col) {
535 int row = 0;
536 View *view = cursor->view;
537 size_t pos = view->start;
538 /* get row number and file offset at start of the given line */
539 for (Line *cur = view->topline; cur && cur != line; cur = cur->next) {
540 pos += cur->len;
541 row++;
544 /* for characters which use more than 1 column, make sure we are on the left most */
545 while (col > 0 && line->cells[col].len == 0)
546 col--;
547 /* calculate offset within the line */
548 for (int i = 0; i < col; i++)
549 pos += line->cells[i].len;
551 cursor->col = col;
552 cursor->row = row;
553 cursor->line = line;
555 cursor_to(cursor, pos);
557 return pos;
560 bool view_viewport_down(View *view, int n) {
561 Line *line;
562 if (view->end >= text_size(view->text))
563 return false;
564 if (n >= view->height) {
565 view->start = view->end;
566 } else {
567 for (line = view->topline; line && n > 0; line = line->next, n--)
568 view->start += line->len;
570 view_draw(view);
571 return true;
574 bool view_viewport_up(View *view, int n) {
575 /* scrolling up is somewhat tricky because we do not yet know where
576 * the lines start, therefore scan backwards but stop at a reasonable
577 * maximum in case we are dealing with a file without any newlines
579 if (view->start == 0)
580 return false;
581 size_t max = view->width * view->height;
582 char c;
583 Iterator it = text_iterator_get(view->text, view->start - 1);
585 if (!text_iterator_byte_get(&it, &c))
586 return false;
587 size_t off = 0;
588 /* skip newlines immediately before display area */
589 if (c == '\n' && text_iterator_byte_prev(&it, &c))
590 off++;
591 do {
592 if (c == '\n' && --n == 0)
593 break;
594 if (++off > max)
595 break;
596 } while (text_iterator_byte_prev(&it, &c));
597 view->start -= MIN(view->start, off);
598 view_draw(view);
599 return true;
602 void view_redraw_top(View *view) {
603 Line *line = view->cursor->line;
604 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
605 view->start += cur->len;
606 view_draw(view);
607 view_cursor_to(view, view->cursor->pos);
610 void view_redraw_center(View *view) {
611 int center = view->height / 2;
612 size_t pos = view->cursor->pos;
613 for (int i = 0; i < 2; i++) {
614 int linenr = 0;
615 Line *line = view->cursor->line;
616 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
617 linenr++;
618 if (linenr < center) {
619 view_slide_down(view, center - linenr);
620 continue;
622 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
623 view->start += cur->len;
624 linenr--;
626 break;
628 view_draw(view);
629 view_cursor_to(view, pos);
632 void view_redraw_bottom(View *view) {
633 Line *line = view->cursor->line;
634 if (line == view->lastline)
635 return;
636 size_t pos = view->cursor->pos;
637 view_viewport_up(view, view->height);
638 while (pos >= view->end && view_viewport_down(view, 1));
639 cursor_to(view->cursor, pos);
642 size_t view_slide_up(View *view, int lines) {
643 Cursor *cursor = view->cursor;
644 if (view_viewport_down(view, lines)) {
645 if (cursor->line == view->topline)
646 cursor_set(cursor, view->topline, cursor->col);
647 else
648 view_cursor_to(view, cursor->pos);
649 } else {
650 view_screenline_down(cursor);
652 return cursor->pos;
655 size_t view_slide_down(View *view, int lines) {
656 Cursor *cursor = view->cursor;
657 bool lastline = cursor->line == view->lastline;
658 size_t col = cursor->col;
659 if (view_viewport_up(view, lines)) {
660 if (lastline)
661 cursor_set(cursor, view->lastline, col);
662 else
663 view_cursor_to(view, cursor->pos);
664 } else {
665 view_screenline_up(cursor);
667 return cursor->pos;
670 size_t view_scroll_up(View *view, int lines) {
671 Cursor *cursor = view->cursor;
672 if (view_viewport_up(view, lines)) {
673 Line *line = cursor->line < view->lastline ? cursor->line : view->lastline;
674 cursor_set(cursor, line, view->cursor->col);
675 } else {
676 view_cursor_to(view, 0);
678 return cursor->pos;
681 size_t view_scroll_page_up(View *view) {
682 Cursor *cursor = view->cursor;
683 if (view->start == 0) {
684 view_cursor_to(view, 0);
685 } else {
686 view_cursor_to(view, view->start-1);
687 view_redraw_bottom(view);
688 view_screenline_begin(cursor);
690 return cursor->pos;
693 size_t view_scroll_page_down(View *view) {
694 view_scroll_down(view, view->height);
695 return view_screenline_begin(view->cursor);
698 size_t view_scroll_halfpage_up(View *view) {
699 Cursor *cursor = view->cursor;
700 if (view->start == 0) {
701 view_cursor_to(view, 0);
702 } else {
703 view_cursor_to(view, view->start-1);
704 view_redraw_center(view);
705 view_screenline_begin(cursor);
707 return cursor->pos;
710 size_t view_scroll_halfpage_down(View *view) {
711 size_t end = view->end;
712 size_t pos = view_scroll_down(view, view->height/2);
713 if (pos < text_size(view->text))
714 view_cursor_to(view, end);
715 return view->cursor->pos;
718 size_t view_scroll_down(View *view, int lines) {
719 Cursor *cursor = view->cursor;
720 if (view_viewport_down(view, lines)) {
721 Line *line = cursor->line > view->topline ? cursor->line : view->topline;
722 cursor_set(cursor, line, cursor->col);
723 } else {
724 view_cursor_to(view, text_size(view->text));
726 return cursor->pos;
729 size_t view_line_up(Cursor *cursor) {
730 View *view = cursor->view;
731 int lastcol = cursor->lastcol;
732 if (!lastcol)
733 lastcol = cursor->col;
734 size_t pos = text_line_up(cursor->view->text, cursor->pos);
735 bool offscreen = view->cursor == cursor && pos < view->start;
736 view_cursors_to(cursor, pos);
737 if (offscreen)
738 view_redraw_top(view);
739 if (cursor->line)
740 cursor_set(cursor, cursor->line, lastcol);
741 cursor->lastcol = lastcol;
742 return cursor->pos;
745 size_t view_line_down(Cursor *cursor) {
746 View *view = cursor->view;
747 int lastcol = cursor->lastcol;
748 if (!lastcol)
749 lastcol = cursor->col;
750 size_t pos = text_line_down(cursor->view->text, cursor->pos);
751 bool offscreen = view->cursor == cursor && pos > view->end;
752 view_cursors_to(cursor, pos);
753 if (offscreen)
754 view_redraw_bottom(view);
755 if (cursor->line)
756 cursor_set(cursor, cursor->line, lastcol);
757 cursor->lastcol = lastcol;
758 return cursor->pos;
761 size_t view_screenline_up(Cursor *cursor) {
762 if (!cursor->line)
763 return view_line_up(cursor);
764 int lastcol = cursor->lastcol;
765 if (!lastcol)
766 lastcol = cursor->col;
767 if (!cursor->line->prev)
768 view_scroll_up(cursor->view, 1);
769 if (cursor->line->prev)
770 cursor_set(cursor, cursor->line->prev, lastcol);
771 cursor->lastcol = lastcol;
772 return cursor->pos;
775 size_t view_screenline_down(Cursor *cursor) {
776 if (!cursor->line)
777 return view_line_down(cursor);
778 int lastcol = cursor->lastcol;
779 if (!lastcol)
780 lastcol = cursor->col;
781 if (!cursor->line->next && cursor->line == cursor->view->bottomline)
782 view_scroll_down(cursor->view, 1);
783 if (cursor->line->next)
784 cursor_set(cursor, cursor->line->next, lastcol);
785 cursor->lastcol = lastcol;
786 return cursor->pos;
789 size_t view_screenline_begin(Cursor *cursor) {
790 if (!cursor->line)
791 return cursor->pos;
792 return cursor_set(cursor, cursor->line, 0);
795 size_t view_screenline_middle(Cursor *cursor) {
796 if (!cursor->line)
797 return cursor->pos;
798 return cursor_set(cursor, cursor->line, cursor->line->width / 2);
801 size_t view_screenline_end(Cursor *cursor) {
802 if (!cursor->line)
803 return cursor->pos;
804 int col = cursor->line->width - 1;
805 return cursor_set(cursor, cursor->line, col >= 0 ? col : 0);
808 size_t view_cursor_get(View *view) {
809 return view_cursors_pos(view->cursor);
812 Line *view_lines_first(View *view) {
813 return view->topline;
816 Line *view_lines_last(View *view) {
817 return view->lastline;
820 Line *view_cursors_line_get(Cursor *c) {
821 return c->line;
824 void view_scroll_to(View *view, size_t pos) {
825 view_cursors_scroll_to(view->cursor, pos);
828 void view_options_set(View *view, enum UiOption options) {
829 const int mapping[] = {
830 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
831 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
832 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
833 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
836 for (int i = 0; i < LENGTH(mapping); i++) {
837 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
838 &symbols_none[i];
841 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
842 options &= ~UI_OPTION_LARGE_FILE;
844 view->large_file = (options & UI_OPTION_LARGE_FILE);
846 if (view->ui)
847 view->ui->options_set(view->ui, options);
850 enum UiOption view_options_get(View *view) {
851 return view->ui ? view->ui->options_get(view->ui) : 0;
854 void view_colorcolumn_set(View *view, int col) {
855 if (col >= 0)
856 view->colorcolumn = col;
859 int view_colorcolumn_get(View *view) {
860 return view->colorcolumn;
863 size_t view_screenline_goto(View *view, int n) {
864 size_t pos = view->start;
865 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
866 pos += line->len;
867 return pos;
870 static Cursor *cursors_new(View *view, size_t pos, bool force) {
871 Cursor *c = calloc(1, sizeof(*c));
872 if (!c)
873 return NULL;
874 c->view = view;
875 c->generation = view->cursor_generation;
876 if (!view->cursors) {
877 view->cursor = c;
878 view->cursor_latest = c;
879 view->cursors = c;
880 view->cursor_count = 1;
881 return c;
884 Cursor *prev = NULL, *next = NULL;
885 Cursor *latest = view->cursor_latest ? view->cursor_latest : view->cursor;
886 size_t cur = view_cursors_pos(latest);
887 if (pos == cur) {
888 prev = latest;
889 next = prev->next;
890 } else if (pos > cur) {
891 prev = latest;
892 for (next = prev->next; next; prev = next, next = next->next) {
893 cur = view_cursors_pos(next);
894 if (pos <= cur)
895 break;
897 } else if (pos < cur) {
898 next = latest;
899 for (prev = next->prev; prev; next = prev, prev = prev->prev) {
900 cur = view_cursors_pos(prev);
901 if (pos >= cur)
902 break;
906 if (pos == cur && !force)
907 goto err;
909 for (Cursor *after = next; after; after = after->next)
910 after->number++;
912 c->prev = prev;
913 c->next = next;
914 if (next)
915 next->prev = c;
916 if (prev) {
917 prev->next = c;
918 c->number = prev->number + 1;
919 } else {
920 view->cursors = c;
922 view->cursor_latest = c;
923 view->cursor_count++;
924 view_cursors_dispose(view->cursor_dead);
925 view_cursors_to(c, pos);
926 return c;
927 err:
928 free(c);
929 return NULL;
932 Cursor *view_cursors_new(View *view, size_t pos) {
933 return cursors_new(view, pos, false);
936 Cursor *view_cursors_new_force(View *view, size_t pos) {
937 return cursors_new(view, pos, true);
940 int view_cursors_count(View *view) {
941 return view->cursor_count;
944 int view_cursors_number(Cursor *c) {
945 return c->number;
948 int view_cursors_column_count(View *view) {
949 Text *txt = view->text;
950 int cpl_max = 0, cpl = 0; /* cursors per line */
951 size_t line_prev = 0;
952 for (Cursor *c = view->cursors; c; c = c->next) {
953 size_t pos = view_cursors_pos(c);
954 size_t line = text_lineno_by_pos(txt, pos);
955 if (line == line_prev)
956 cpl++;
957 else
958 cpl = 1;
959 line_prev = line;
960 if (cpl > cpl_max)
961 cpl_max = cpl;
963 return cpl_max;
966 static Cursor *cursors_column_next(View *view, Cursor *c, int column) {
967 size_t line_cur = 0;
968 int column_cur = 0;
969 Text *txt = view->text;
970 if (c) {
971 size_t pos = view_cursors_pos(c);
972 line_cur = text_lineno_by_pos(txt, pos);
973 column_cur = INT_MIN;
974 } else {
975 c = view->cursors;
978 for (; c; c = c->next) {
979 size_t pos = view_cursors_pos(c);
980 size_t line = text_lineno_by_pos(txt, pos);
981 if (line != line_cur) {
982 line_cur = line;
983 column_cur = 0;
984 } else {
985 column_cur++;
987 if (column == column_cur)
988 return c;
990 return NULL;
993 Cursor *view_cursors_column(View *view, int column) {
994 return cursors_column_next(view, NULL, column);
997 Cursor *view_cursors_column_next(Cursor *c, int column) {
998 return cursors_column_next(c->view, c, column);
1001 bool view_cursors_multiple(View *view) {
1002 return view->cursors && view->cursors->next;
1005 static void view_cursors_free(Cursor *c) {
1006 if (!c)
1007 return;
1008 register_release(&c->reg);
1009 for (Cursor *after = c->next; after; after = after->next)
1010 after->number--;
1011 if (c->prev)
1012 c->prev->next = c->next;
1013 if (c->next)
1014 c->next->prev = c->prev;
1015 if (c->view->cursors == c)
1016 c->view->cursors = c->next;
1017 if (c->view->cursor == c)
1018 c->view->cursor = c->next ? c->next : c->prev;
1019 if (c->view->cursor_dead == c)
1020 c->view->cursor_dead = NULL;
1021 if (c->view->cursor_latest == c)
1022 c->view->cursor_latest = c->prev ? c->prev : c->next;
1023 c->view->cursor_count--;
1024 free(c);
1027 bool view_cursors_dispose(Cursor *c) {
1028 if (!c)
1029 return true;
1030 View *view = c->view;
1031 if (!view->cursors || !view->cursors->next)
1032 return false;
1033 view_selections_free(c->sel);
1034 view_cursors_free(c);
1035 view_cursors_primary_set(view->cursor);
1036 return true;
1039 bool view_cursors_dispose_force(Cursor *c) {
1040 if (view_cursors_dispose(c))
1041 return true;
1042 View *view = c->view;
1043 if (view->cursor_dead)
1044 return false;
1045 view_cursors_selection_clear(c);
1046 view->cursor_dead = c;
1047 return true;
1050 Cursor *view_cursor_disposed(View *view) {
1051 Cursor *c = view->cursor_dead;
1052 view->cursor_dead = NULL;
1053 return c;
1056 Cursor *view_cursors(View *view) {
1057 view->cursor_generation++;
1058 return view->cursors;
1061 Cursor *view_cursors_primary_get(View *view) {
1062 view->cursor_generation++;
1063 return view->cursor;
1066 void view_cursors_primary_set(Cursor *c) {
1067 if (!c)
1068 return;
1069 View *view = c->view;
1070 view->cursor = c;
1071 Filerange sel = view_cursors_selection_get(c);
1072 view_cursors_to(c, view_cursors_pos(c));
1073 view_cursors_selection_set(c, &sel);
1076 Cursor *view_cursors_prev(Cursor *c) {
1077 View *view = c->view;
1078 for (c = c->prev; c; c = c->prev) {
1079 if (c->generation != view->cursor_generation)
1080 return c;
1082 view->cursor_generation++;
1083 return NULL;
1086 Cursor *view_cursors_next(Cursor *c) {
1087 View *view = c->view;
1088 for (c = c->next; c; c = c->next) {
1089 if (c->generation != view->cursor_generation)
1090 return c;
1092 view->cursor_generation++;
1093 return NULL;
1096 size_t view_cursors_pos(Cursor *c) {
1097 size_t pos = text_mark_get(c->view->text, c->mark);
1098 return pos != EPOS ? pos : text_mark_get(c->view->text, c->mark_old);
1101 size_t view_cursors_line(Cursor *c) {
1102 size_t pos = view_cursors_pos(c);
1103 return text_lineno_by_pos(c->view->text, pos);
1106 size_t view_cursors_col(Cursor *c) {
1107 size_t pos = view_cursors_pos(c);
1108 return text_line_char_get(c->view->text, pos) + 1;
1111 int view_cursors_cell_get(Cursor *c) {
1112 return c->line ? c->col : -1;
1115 int view_cursors_cell_set(Cursor *c, int cell) {
1116 if (!c->line || cell < 0)
1117 return -1;
1118 cursor_set(c, c->line, cell);
1119 return c->col;
1122 Register *view_cursors_register(Cursor *c) {
1123 return &c->reg;
1126 void view_cursors_scroll_to(Cursor *c, size_t pos) {
1127 View *view = c->view;
1128 if (view->cursor == c) {
1129 view_draw(view);
1130 while (pos < view->start && view_viewport_up(view, 1));
1131 while (pos > view->end && view_viewport_down(view, 1));
1133 view_cursors_to(c, pos);
1136 void view_cursors_to(Cursor *c, size_t pos) {
1137 View *view = c->view;
1138 if (pos == EPOS)
1139 return;
1140 size_t size = text_size(view->text);
1141 if (pos > size)
1142 pos = size;
1143 if (c->view->cursor == c) {
1144 /* make sure we redraw changes to the very first character of the window */
1145 if (view->start == pos)
1146 view->start_last = 0;
1148 if (view->end == pos && view->lastline == view->bottomline) {
1149 view->start += view->topline->len;
1150 view_draw(view);
1153 if (pos < view->start || pos > view->end) {
1154 view->start = pos;
1155 view_viewport_up(view, view->height / 2);
1158 if (pos <= view->start || pos > view->end) {
1159 view->start = text_line_begin(view->text, pos);
1160 view_draw(view);
1163 if (pos <= view->start || pos > view->end) {
1164 view->start = pos;
1165 view_draw(view);
1169 cursor_to(c, pos);
1172 void view_cursors_place(Cursor *c, size_t line, size_t col) {
1173 Text *txt = c->view->text;
1174 size_t pos = text_pos_by_lineno(txt, line);
1175 pos = text_line_char_set(txt, pos, col > 0 ? col-1 : col);
1176 view_cursors_to(c, pos);
1179 void view_cursors_selection_start(Cursor *c) {
1180 if (c->sel)
1181 return;
1182 size_t pos = view_cursors_pos(c);
1183 if (pos == EPOS || !(c->sel = view_selections_new(c->view, c)))
1184 return;
1185 Text *txt = c->view->text;
1186 c->sel->anchor = text_mark_set(txt, pos);
1187 c->sel->cursor = c->sel->anchor;
1188 c->view->need_update = true;
1191 void view_cursors_selection_restore(Cursor *c) {
1192 Text *txt = c->view->text;
1193 if (c->sel)
1194 return;
1195 Filerange sel = text_range_new(
1196 text_mark_get(txt, c->lastsel_anchor),
1197 text_mark_get(txt, c->lastsel_cursor)
1199 if (!text_range_valid(&sel))
1200 return;
1201 view_cursors_to(c, sel.end);
1202 sel.end = text_char_next(txt, sel.end);
1203 if (!(c->sel = view_selections_new(c->view, c)))
1204 return;
1205 view_selections_set(c->sel, &sel);
1208 void view_cursors_selection_stop(Cursor *c) {
1209 c->sel = NULL;
1212 void view_cursors_selection_clear(Cursor *c) {
1213 view_selections_free(c->sel);
1214 c->view->need_update = true;
1217 void view_cursors_selection_swap(Cursor *c) {
1218 if (!c->sel)
1219 return;
1220 view_selections_swap(c->sel);
1221 view_cursors_selection_sync(c);
1224 void view_cursors_selection_sync(Cursor *c) {
1225 if (!c->sel)
1226 return;
1227 Text *txt = c->view->text;
1228 size_t pos = text_mark_get(txt, c->sel->cursor);
1229 view_cursors_to(c, pos);
1232 Filerange view_cursors_selection_get(Cursor *c) {
1233 return view_selections_get(c->sel);
1236 void view_cursors_selection_set(Cursor *c, const Filerange *r) {
1237 if (!text_range_valid(r))
1238 return;
1239 bool new = !c->sel;
1240 if (new && !(c->sel = view_selections_new(c->view, c)))
1241 return;
1242 view_selections_set(c->sel, r);
1243 if (!new) {
1244 size_t pos = view_cursors_pos(c);
1245 if (!text_range_contains(r, pos))
1246 view_cursors_selection_sync(c);
1250 Selection *view_selections_new(View *view, Cursor *c) {
1251 Selection *s = calloc(1, sizeof(*s));
1252 if (!s)
1253 return NULL;
1254 s->view = view;
1255 s->next = view->selections;
1256 if (view->selections)
1257 view->selections->prev = s;
1258 view->selections = s;
1259 s->cur = c;
1260 return s;
1263 void view_selections_free(Selection *s) {
1264 if (!s)
1265 return;
1266 if (s->prev)
1267 s->prev->next = s->next;
1268 if (s->next)
1269 s->next->prev = s->prev;
1270 if (s->view->selections == s)
1271 s->view->selections = s->next;
1272 Cursor *c = s->cur;
1273 if (c) {
1274 c->lastsel_anchor = s->anchor;
1275 c->lastsel_cursor = s->cursor;
1276 c->sel = NULL;
1278 free(s);
1281 void view_selections_clear(View *view) {
1282 while (view->selections)
1283 view_selections_free(view->selections);
1284 view_draw(view);
1287 void view_cursors_clear(View *view) {
1288 for (Cursor *c = view->cursors, *next; c; c = next) {
1289 next = c->next;
1290 if (c != view->cursor) {
1291 view_selections_free(c->sel);
1292 view_cursors_free(c);
1295 view_draw(view);
1298 void view_selections_swap(Selection *s) {
1299 Mark temp = s->anchor;
1300 s->anchor = s->cursor;
1301 s->cursor = temp;
1304 Selection *view_selections(View *view) {
1305 return view->selections;
1308 Selection *view_selections_prev(Selection *s) {
1309 return s->prev;
1312 Selection *view_selections_next(Selection *s) {
1313 return s->next;
1316 Filerange view_selection_get(View *view) {
1317 return view_selections_get(view->cursor->sel);
1320 Filerange view_selections_get(Selection *s) {
1321 if (!s)
1322 return text_range_empty();
1323 Text *txt = s->view->text;
1324 size_t anchor = text_mark_get(txt, s->anchor);
1325 size_t cursor = text_mark_get(txt, s->cursor);
1326 Filerange sel = text_range_new(anchor, cursor);
1327 if (text_range_valid(&sel))
1328 sel.end = text_char_next(txt, sel.end);
1329 return sel;
1332 void view_selections_set(Selection *s, const Filerange *r) {
1333 if (!text_range_valid(r))
1334 return;
1335 Text *txt = s->view->text;
1336 size_t anchor = text_mark_get(txt, s->anchor);
1337 size_t cursor = text_mark_get(txt, s->cursor);
1338 bool left_extending = anchor != EPOS && anchor > cursor;
1339 size_t end = r->end;
1340 if (r->start != end)
1341 end = text_char_prev(txt, end);
1342 if (left_extending) {
1343 s->anchor = text_mark_set(txt, end);
1344 s->cursor = text_mark_set(txt, r->start);
1345 } else {
1346 s->anchor = text_mark_set(txt, r->start);
1347 s->cursor = text_mark_set(txt, end);
1349 s->view->need_update = true;
1352 Text *view_text(View *view) {
1353 return view->text;
1356 bool view_style_define(View *view, enum UiStyle id, const char *style) {
1357 return view->ui->style_define(view->ui, id, style);
1360 void view_style(View *view, enum UiStyle style_id, size_t start, size_t end) {
1361 if (end < view->start || start > view->end)
1362 return;
1364 CellStyle style = view->ui->style_get(view->ui, style_id);
1365 size_t pos = view->start;
1366 Line *line = view->topline;
1368 /* skip lines before range to be styled */
1369 while (line && pos + line->len <= start) {
1370 pos += line->len;
1371 line = line->next;
1374 if (!line)
1375 return;
1377 int col = 0, width = view->width;
1379 /* skip columns before range to be styled */
1380 while (pos < start && col < width)
1381 pos += line->cells[col++].len;
1383 do {
1384 while (pos <= end && col < width) {
1385 pos += line->cells[col].len;
1386 line->cells[col++].style = style;
1388 col = 0;
1389 } while (pos <= end && (line = line->next));