vis: strip double leading slashes of paths
[vis.git] / view.c
blob76410aaa3bbf7313af66a60d76b23547a30edaf4
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 int generation; /* used to filter out newly created cursors during iteration */
59 int number; /* how many cursors are located before this one */
60 View *view; /* associated view to which this cursor belongs */
61 Cursor *prev, *next;/* previous/next cursors ordered by location at creation time */
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 Cell cell_blank; /* used for empty/blank cells */
71 int width, height; /* size of display area */
72 size_t start, end; /* currently displayed area [start, end] in bytes from the start of the file */
73 size_t start_last; /* previously used start of visible area, used to update the mark */
74 Mark start_mark; /* mark to keep track of the start of the visible area */
75 size_t lines_size; /* number of allocated bytes for lines (grows only) */
76 Line *lines; /* view->height number of lines representing view content */
77 Line *topline; /* top of the view, first line currently shown */
78 Line *lastline; /* last currently used line, always <= bottomline */
79 Line *bottomline; /* bottom of view, might be unused if lastline < bottomline */
80 Cursor *cursor; /* main cursor, always placed within the visible viewport */
81 Cursor *cursor_latest; /* most recently created cursor */
82 Cursor *cursor_dead;/* main cursor which was disposed, will be removed when another cursor is created */
83 int cursor_count; /* how many cursors do currently exist */
84 Line *line; /* used while drawing view content, line where next char will be drawn */
85 int col; /* used while drawing view content, column where next char will be drawn */
86 const SyntaxSymbol *symbols[SYNTAX_SYMBOL_LAST]; /* symbols to use for white spaces etc */
87 int tabwidth; /* how many spaces should be used to display a tab character */
88 Cursor *cursors; /* all cursors currently active */
89 Selection *selections; /* all selected regions */
90 int cursor_generation; /* used to filter out newly created cursors during iteration */
91 bool need_update; /* whether view has been redrawn */
92 bool large_file; /* optimize for displaying large files */
93 int colorcolumn;
96 static const SyntaxSymbol symbols_none[] = {
97 [SYNTAX_SYMBOL_SPACE] = { " " },
98 [SYNTAX_SYMBOL_TAB] = { " " },
99 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
100 [SYNTAX_SYMBOL_EOL] = { " " },
103 static const SyntaxSymbol symbols_default[] = {
104 [SYNTAX_SYMBOL_SPACE] = { "·" /* Middle Dot U+00B7 */ },
105 [SYNTAX_SYMBOL_TAB] = { "›" /* Single Right-Pointing Angle Quotation Mark U+203A */ },
106 [SYNTAX_SYMBOL_TAB_FILL] = { " " },
107 [SYNTAX_SYMBOL_EOL] = { "↵" /* Downwards Arrow with Corner Leftwards U+21B5 */ },
110 static Cell cell_unused;
112 static void view_clear(View *view);
113 static bool view_addch(View *view, Cell *cell);
114 static void view_cursors_free(Cursor *c);
115 /* set/move current cursor position to a given (line, column) pair */
116 static size_t cursor_set(Cursor *cursor, Line *line, int col);
118 void view_tabwidth_set(View *view, int tabwidth) {
119 view->tabwidth = tabwidth;
120 view_draw(view);
123 /* reset internal view data structures (cell matrix, line offsets etc.) */
124 static void view_clear(View *view) {
125 memset(view->lines, 0, view->lines_size);
126 if (view->start != view->start_last) {
127 if (view->start == 0)
128 view->start_mark = EMARK;
129 else
130 view->start_mark = text_mark_set(view->text, view->start);
131 } else {
132 size_t start;
133 if (view->start_mark == EMARK)
134 start = 0;
135 else
136 start = text_mark_get(view->text, view->start_mark);
137 if (start != EPOS)
138 view->start = start;
141 view->start_last = view->start;
142 view->topline = view->lines;
143 view->topline->lineno = view->large_file ? 1 : text_lineno_by_pos(view->text, view->start);
144 view->lastline = view->topline;
146 size_t line_size = sizeof(Line) + view->width*sizeof(Cell);
147 size_t end = view->height * line_size;
148 Line *prev = NULL;
149 for (size_t i = 0; i < end; i += line_size) {
150 Line *line = (Line*)(((char*)view->lines) + i);
151 line->prev = prev;
152 if (prev)
153 prev->next = line;
154 prev = line;
156 view->bottomline = prev ? prev : view->topline;
157 view->bottomline->next = NULL;
158 view->line = view->topline;
159 view->col = 0;
160 if (view->ui)
161 view->cell_blank.style = view->ui->style_get(view->ui, UI_STYLE_DEFAULT);
164 Filerange view_viewport_get(View *view) {
165 return (Filerange){ .start = view->start, .end = view->end };
168 /* try to add another character to the view, return whether there was space left */
169 static bool view_addch(View *view, Cell *cell) {
170 if (!view->line)
171 return false;
173 int width;
174 size_t lineno = view->line->lineno;
175 unsigned char ch = (unsigned char)cell->data[0];
176 cell->style = view->cell_blank.style;
178 switch (ch) {
179 case '\t':
180 cell->width = 1;
181 width = view->tabwidth - (view->col % view->tabwidth);
182 for (int w = 0; w < width; w++) {
183 if (view->col + 1 > view->width) {
184 view->line = view->line->next;
185 view->col = 0;
186 if (!view->line)
187 return false;
188 view->line->lineno = lineno;
191 cell->len = w == 0 ? 1 : 0;
192 int t = w == 0 ? SYNTAX_SYMBOL_TAB : SYNTAX_SYMBOL_TAB_FILL;
193 strncpy(cell->data, view->symbols[t]->symbol, sizeof(cell->data)-1);
194 view->line->cells[view->col] = *cell;
195 view->line->len += cell->len;
196 view->line->width += cell->width;
197 view->col++;
199 cell->len = 1;
200 return true;
201 case '\n':
202 cell->width = 1;
203 if (view->col + cell->width > view->width) {
204 view->line = view->line->next;
205 view->col = 0;
206 if (!view->line)
207 return false;
208 view->line->lineno = lineno;
211 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_EOL]->symbol, sizeof(cell->data)-1);
213 view->line->cells[view->col] = *cell;
214 view->line->len += cell->len;
215 view->line->width += cell->width;
216 for (int i = view->col + 1; i < view->width; i++)
217 view->line->cells[i] = view->cell_blank;
219 view->line = view->line->next;
220 if (view->line)
221 view->line->lineno = lineno + 1;
222 view->col = 0;
223 return true;
224 default:
225 if (ch < 128 && !isprint(ch)) {
226 /* non-printable ascii char, represent it as ^(char + 64) */
227 *cell = (Cell) {
228 .data = { '^', ch == 127 ? '?' : ch + 64, '\0' },
229 .len = 1,
230 .width = 2,
231 .style = cell->style,
235 if (ch == ' ') {
236 strncpy(cell->data, view->symbols[SYNTAX_SYMBOL_SPACE]->symbol, sizeof(cell->data)-1);
240 if (view->col + cell->width > view->width) {
241 for (int i = view->col; i < view->width; i++)
242 view->line->cells[i] = view->cell_blank;
243 view->line = view->line->next;
244 view->col = 0;
247 if (view->line) {
248 view->line->width += cell->width;
249 view->line->len += cell->len;
250 view->line->lineno = lineno;
251 view->line->cells[view->col] = *cell;
252 view->col++;
253 /* set cells of a character which uses multiple columns */
254 for (int i = 1; i < cell->width; i++)
255 view->line->cells[view->col++] = cell_unused;
256 return true;
258 return false;
262 static void cursor_to(Cursor *c, size_t pos) {
263 Text *txt = c->view->text;
264 c->mark_old = c->mark;
265 c->mark = text_mark_set(txt, pos);
266 if (pos != c->pos)
267 c->lastcol = 0;
268 c->pos = pos;
269 if (c->sel)
270 c->sel->cursor = text_mark_set(txt, pos);
271 if (!view_coord_get(c->view, pos, &c->line, &c->row, &c->col)) {
272 if (c->view->cursor == c) {
273 c->line = c->view->topline;
274 c->row = 0;
275 c->col = 0;
277 return;
279 // TODO: minimize number of redraws
280 view_draw(c->view);
283 bool view_coord_get(View *view, size_t pos, Line **retline, int *retrow, int *retcol) {
284 int row = 0, col = 0;
285 size_t cur = view->start;
286 Line *line = view->topline;
288 if (pos < view->start || pos > view->end) {
289 if (retline) *retline = NULL;
290 if (retrow) *retrow = -1;
291 if (retcol) *retcol = -1;
292 return false;
295 while (line && line != view->lastline && cur < pos) {
296 if (cur + line->len > pos)
297 break;
298 cur += line->len;
299 line = line->next;
300 row++;
303 if (line) {
304 int max_col = MIN(view->width, line->width);
305 while (cur < pos && col < max_col) {
306 cur += line->cells[col].len;
307 /* skip over columns occupied by the same character */
308 while (++col < max_col && line->cells[col].len == 0);
310 } else {
311 line = view->bottomline;
312 row = view->height - 1;
315 if (retline) *retline = line;
316 if (retrow) *retrow = row;
317 if (retcol) *retcol = col;
318 return true;
321 /* move the cursor to the character at pos bytes from the begining of the file.
322 * if pos is not in the current viewport, redraw the view to make it visible */
323 void view_cursor_to(View *view, size_t pos) {
324 view_cursors_to(view->cursor, pos);
327 /* redraw the complete with data starting from view->start bytes into the file.
328 * stop once the screen is full, update view->end, view->lastline */
329 void view_draw(View *view) {
330 view_clear(view);
331 /* read a screenful of text considering each character as 4-byte UTF character*/
332 const size_t size = view->width * view->height * 4;
333 /* current buffer to work with */
334 char text[size+1];
335 /* remaining bytes to process in buffer */
336 size_t rem = text_bytes_get(view->text, view->start, size, text);
337 /* NUL terminate text section */
338 text[rem] = '\0';
339 /* absolute position of character currently being added to display */
340 size_t pos = view->start;
341 /* current position into buffer from which to interpret a character */
342 char *cur = text;
343 /* start from known multibyte state */
344 mbstate_t mbstate = { 0 };
346 Cell cell = { .data = "", .len = 0, .width = 0, }, prev_cell = cell;
348 while (rem > 0) {
350 /* current 'parsed' character' */
351 wchar_t wchar;
353 size_t len = mbrtowc(&wchar, cur, rem, &mbstate);
354 if (len == (size_t)-1 && errno == EILSEQ) {
355 /* ok, we encountered an invalid multibyte sequence,
356 * replace it with the Unicode Replacement Character
357 * (FFFD) and skip until the start of the next utf8 char */
358 for (len = 1; rem > len && !ISUTF8(cur[len]); len++);
359 cell = (Cell){ .data = "\xEF\xBF\xBD", .len = len, .width = 1 };
360 } else if (len == (size_t)-2) {
361 /* not enough bytes available to convert to a
362 * wide character. advance file position and read
363 * another junk into buffer.
365 rem = text_bytes_get(view->text, pos, size, text);
366 text[rem] = '\0';
367 cur = text;
368 continue;
369 } else if (len == 0) {
370 /* NUL byte encountered, store it and continue */
371 cell = (Cell){ .data = "\x00", .len = 1, .width = 2 };
372 } else {
373 if (len >= sizeof(cell.data))
374 len = sizeof(cell.data)-1;
375 for (size_t i = 0; i < len; i++)
376 cell.data[i] = cur[i];
377 cell.data[len] = '\0';
378 cell.len = len;
379 cell.width = wcwidth(wchar);
380 if (cell.width == -1)
381 cell.width = 1;
384 if (cell.width == 0 && prev_cell.len + cell.len < sizeof(cell.data)) {
385 prev_cell.len += cell.len;
386 strcat(prev_cell.data, cell.data);
387 } else {
388 if (prev_cell.len && !view_addch(view, &prev_cell))
389 break;
390 pos += prev_cell.len;
391 prev_cell = cell;
394 rem -= cell.len;
395 cur += cell.len;
397 memset(&cell, 0, sizeof cell);
400 if (prev_cell.len && view_addch(view, &prev_cell))
401 pos += prev_cell.len;
403 /* set end of viewing region */
404 view->end = pos;
405 if (view->line) {
406 bool eof = view->end == text_size(view->text);
407 if (view->line->len == 0 && eof && view->line->prev)
408 view->lastline = view->line->prev;
409 else
410 view->lastline = view->line;
411 } else {
412 view->lastline = view->bottomline;
415 /* clear remaining of line, important to show cursor at end of file */
416 if (view->line) {
417 for (int x = view->col; x < view->width; x++)
418 view->line->cells[x] = view->cell_blank;
421 /* resync position of cursors within visible area */
422 for (Cursor *c = view->cursors; c; c = c->next) {
423 size_t pos = view_cursors_pos(c);
424 if (!view_coord_get(view, pos, &c->line, &c->row, &c->col) &&
425 c == view->cursor) {
426 c->line = view->topline;
427 c->row = 0;
428 c->col = 0;
432 view->need_update = true;
435 void view_invalidate(View *view) {
436 view->need_update = true;
439 bool view_update(View *view) {
440 if (!view->need_update)
441 return false;
442 for (Line *l = view->lastline->next; l; l = l->next) {
443 for (int x = 0; x < view->width; x++)
444 l->cells[x] = view->cell_blank;
446 view->need_update = false;
447 return true;
450 bool view_resize(View *view, int width, int height) {
451 if (width <= 0)
452 width = 1;
453 if (height <= 0)
454 height = 1;
455 if (view->width == width && view->height == height) {
456 view->need_update = true;
457 return true;
459 size_t lines_size = height*(sizeof(Line) + width*sizeof(Cell));
460 if (lines_size > view->lines_size) {
461 Line *lines = realloc(view->lines, lines_size);
462 if (!lines)
463 return false;
464 view->lines = lines;
465 view->lines_size = lines_size;
467 view->width = width;
468 view->height = height;
469 memset(view->lines, 0, view->lines_size);
470 view_draw(view);
471 return true;
474 int view_height_get(View *view) {
475 return view->height;
478 int view_width_get(View *view) {
479 return view->width;
482 void view_free(View *view) {
483 if (!view)
484 return;
485 while (view->cursors)
486 view_cursors_free(view->cursors);
487 while (view->selections)
488 view_selections_free(view->selections);
489 free(view->lines);
490 free(view);
493 void view_reload(View *view, Text *text) {
494 view->text = text;
495 view_selections_clear(view);
496 view_cursor_to(view, 0);
499 View *view_new(Text *text) {
500 if (!text)
501 return NULL;
502 View *view = calloc(1, sizeof(View));
503 if (!view)
504 return NULL;
505 if (!view_cursors_new(view, 0)) {
506 view_free(view);
507 return NULL;
510 view->cell_blank = (Cell) {
511 .width = 0,
512 .len = 0,
513 .data = " ",
515 view->text = text;
516 view->tabwidth = 8;
517 view_options_set(view, 0);
519 if (!view_resize(view, 1, 1)) {
520 view_free(view);
521 return NULL;
524 view_cursor_to(view, 0);
526 return view;
529 void view_ui(View *view, UiWin* ui) {
530 view->ui = ui;
533 static size_t cursor_set(Cursor *cursor, Line *line, int col) {
534 int row = 0;
535 View *view = cursor->view;
536 size_t pos = view->start;
537 /* get row number and file offset at start of the given line */
538 for (Line *cur = view->topline; cur && cur != line; cur = cur->next) {
539 pos += cur->len;
540 row++;
543 /* for characters which use more than 1 column, make sure we are on the left most */
544 while (col > 0 && line->cells[col].len == 0)
545 col--;
546 /* calculate offset within the line */
547 for (int i = 0; i < col; i++)
548 pos += line->cells[i].len;
550 cursor->col = col;
551 cursor->row = row;
552 cursor->line = line;
554 cursor_to(cursor, pos);
556 return pos;
559 bool view_viewport_down(View *view, int n) {
560 Line *line;
561 if (view->end >= text_size(view->text))
562 return false;
563 if (n >= view->height) {
564 view->start = view->end;
565 } else {
566 for (line = view->topline; line && n > 0; line = line->next, n--)
567 view->start += line->len;
569 view_draw(view);
570 return true;
573 bool view_viewport_up(View *view, int n) {
574 /* scrolling up is somewhat tricky because we do not yet know where
575 * the lines start, therefore scan backwards but stop at a reasonable
576 * maximum in case we are dealing with a file without any newlines
578 if (view->start == 0)
579 return false;
580 size_t max = view->width * view->height;
581 char c;
582 Iterator it = text_iterator_get(view->text, view->start - 1);
584 if (!text_iterator_byte_get(&it, &c))
585 return false;
586 size_t off = 0;
587 /* skip newlines immediately before display area */
588 if (c == '\n' && text_iterator_byte_prev(&it, &c))
589 off++;
590 do {
591 if (c == '\n' && --n == 0)
592 break;
593 if (++off > max)
594 break;
595 } while (text_iterator_byte_prev(&it, &c));
596 view->start -= MIN(view->start, off);
597 view_draw(view);
598 return true;
601 void view_redraw_top(View *view) {
602 Line *line = view->cursor->line;
603 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
604 view->start += cur->len;
605 view_draw(view);
606 view_cursor_to(view, view->cursor->pos);
609 void view_redraw_center(View *view) {
610 int center = view->height / 2;
611 size_t pos = view->cursor->pos;
612 for (int i = 0; i < 2; i++) {
613 int linenr = 0;
614 Line *line = view->cursor->line;
615 for (Line *cur = view->topline; cur && cur != line; cur = cur->next)
616 linenr++;
617 if (linenr < center) {
618 view_slide_down(view, center - linenr);
619 continue;
621 for (Line *cur = view->topline; cur && cur != line && linenr > center; cur = cur->next) {
622 view->start += cur->len;
623 linenr--;
625 break;
627 view_draw(view);
628 view_cursor_to(view, pos);
631 void view_redraw_bottom(View *view) {
632 Line *line = view->cursor->line;
633 if (line == view->lastline)
634 return;
635 size_t pos = view->cursor->pos;
636 view_viewport_up(view, view->height);
637 while (pos >= view->end && view_viewport_down(view, 1));
638 cursor_to(view->cursor, pos);
641 size_t view_slide_up(View *view, int lines) {
642 Cursor *cursor = view->cursor;
643 if (view_viewport_down(view, lines)) {
644 if (cursor->line == view->topline)
645 cursor_set(cursor, view->topline, cursor->col);
646 else
647 view_cursor_to(view, cursor->pos);
648 } else {
649 view_screenline_down(cursor);
651 return cursor->pos;
654 size_t view_slide_down(View *view, int lines) {
655 Cursor *cursor = view->cursor;
656 bool lastline = cursor->line == view->lastline;
657 size_t col = cursor->col;
658 if (view_viewport_up(view, lines)) {
659 if (lastline)
660 cursor_set(cursor, view->lastline, col);
661 else
662 view_cursor_to(view, cursor->pos);
663 } else {
664 view_screenline_up(cursor);
666 return cursor->pos;
669 size_t view_scroll_up(View *view, int lines) {
670 Cursor *cursor = view->cursor;
671 if (view_viewport_up(view, lines)) {
672 Line *line = cursor->line < view->lastline ? cursor->line : view->lastline;
673 cursor_set(cursor, line, view->cursor->col);
674 } else {
675 view_cursor_to(view, 0);
677 return cursor->pos;
680 size_t view_scroll_page_up(View *view) {
681 Cursor *cursor = view->cursor;
682 if (view->start == 0) {
683 view_cursor_to(view, 0);
684 } else {
685 view_cursor_to(view, view->start-1);
686 view_redraw_bottom(view);
687 view_screenline_begin(cursor);
689 return cursor->pos;
692 size_t view_scroll_page_down(View *view) {
693 view_scroll_down(view, view->height);
694 return view_screenline_begin(view->cursor);
697 size_t view_scroll_halfpage_up(View *view) {
698 Cursor *cursor = view->cursor;
699 if (view->start == 0) {
700 view_cursor_to(view, 0);
701 } else {
702 view_cursor_to(view, view->start-1);
703 view_redraw_center(view);
704 view_screenline_begin(cursor);
706 return cursor->pos;
709 size_t view_scroll_halfpage_down(View *view) {
710 size_t end = view->end;
711 size_t pos = view_scroll_down(view, view->height/2);
712 if (pos < text_size(view->text))
713 view_cursor_to(view, end);
714 return view->cursor->pos;
717 size_t view_scroll_down(View *view, int lines) {
718 Cursor *cursor = view->cursor;
719 if (view_viewport_down(view, lines)) {
720 Line *line = cursor->line > view->topline ? cursor->line : view->topline;
721 cursor_set(cursor, line, cursor->col);
722 } else {
723 view_cursor_to(view, text_size(view->text));
725 return cursor->pos;
728 size_t view_line_up(Cursor *cursor) {
729 View *view = cursor->view;
730 int lastcol = cursor->lastcol;
731 if (!lastcol)
732 lastcol = cursor->col;
733 size_t pos = text_line_up(cursor->view->text, cursor->pos);
734 bool offscreen = view->cursor == cursor && pos < view->start;
735 view_cursors_to(cursor, pos);
736 if (offscreen)
737 view_redraw_top(view);
738 if (cursor->line)
739 cursor_set(cursor, cursor->line, lastcol);
740 cursor->lastcol = lastcol;
741 return cursor->pos;
744 size_t view_line_down(Cursor *cursor) {
745 View *view = cursor->view;
746 int lastcol = cursor->lastcol;
747 if (!lastcol)
748 lastcol = cursor->col;
749 size_t pos = text_line_down(cursor->view->text, cursor->pos);
750 bool offscreen = view->cursor == cursor && pos > view->end;
751 view_cursors_to(cursor, pos);
752 if (offscreen)
753 view_redraw_bottom(view);
754 if (cursor->line)
755 cursor_set(cursor, cursor->line, lastcol);
756 cursor->lastcol = lastcol;
757 return cursor->pos;
760 size_t view_screenline_up(Cursor *cursor) {
761 if (!cursor->line)
762 return view_line_up(cursor);
763 int lastcol = cursor->lastcol;
764 if (!lastcol)
765 lastcol = cursor->col;
766 if (!cursor->line->prev)
767 view_scroll_up(cursor->view, 1);
768 if (cursor->line->prev)
769 cursor_set(cursor, cursor->line->prev, lastcol);
770 cursor->lastcol = lastcol;
771 return cursor->pos;
774 size_t view_screenline_down(Cursor *cursor) {
775 if (!cursor->line)
776 return view_line_down(cursor);
777 int lastcol = cursor->lastcol;
778 if (!lastcol)
779 lastcol = cursor->col;
780 if (!cursor->line->next && cursor->line == cursor->view->bottomline)
781 view_scroll_down(cursor->view, 1);
782 if (cursor->line->next)
783 cursor_set(cursor, cursor->line->next, lastcol);
784 cursor->lastcol = lastcol;
785 return cursor->pos;
788 size_t view_screenline_begin(Cursor *cursor) {
789 if (!cursor->line)
790 return cursor->pos;
791 return cursor_set(cursor, cursor->line, 0);
794 size_t view_screenline_middle(Cursor *cursor) {
795 if (!cursor->line)
796 return cursor->pos;
797 return cursor_set(cursor, cursor->line, cursor->line->width / 2);
800 size_t view_screenline_end(Cursor *cursor) {
801 if (!cursor->line)
802 return cursor->pos;
803 int col = cursor->line->width - 1;
804 return cursor_set(cursor, cursor->line, col >= 0 ? col : 0);
807 size_t view_cursor_get(View *view) {
808 return view_cursors_pos(view->cursor);
811 Line *view_lines_first(View *view) {
812 return view->topline;
815 Line *view_lines_last(View *view) {
816 return view->lastline;
819 Line *view_cursors_line_get(Cursor *c) {
820 return c->line;
823 void view_scroll_to(View *view, size_t pos) {
824 view_cursors_scroll_to(view->cursor, pos);
827 void view_options_set(View *view, enum UiOption options) {
828 const int mapping[] = {
829 [SYNTAX_SYMBOL_SPACE] = UI_OPTION_SYMBOL_SPACE,
830 [SYNTAX_SYMBOL_TAB] = UI_OPTION_SYMBOL_TAB,
831 [SYNTAX_SYMBOL_TAB_FILL] = UI_OPTION_SYMBOL_TAB_FILL,
832 [SYNTAX_SYMBOL_EOL] = UI_OPTION_SYMBOL_EOL,
835 for (int i = 0; i < LENGTH(mapping); i++) {
836 view->symbols[i] = (options & mapping[i]) ? &symbols_default[i] :
837 &symbols_none[i];
840 if (options & UI_OPTION_LINE_NUMBERS_ABSOLUTE)
841 options &= ~UI_OPTION_LARGE_FILE;
843 view->large_file = (options & UI_OPTION_LARGE_FILE);
845 if (view->ui)
846 view->ui->options_set(view->ui, options);
849 enum UiOption view_options_get(View *view) {
850 return view->ui ? view->ui->options_get(view->ui) : 0;
853 void view_colorcolumn_set(View *view, int col) {
854 if (col >= 0)
855 view->colorcolumn = col;
858 int view_colorcolumn_get(View *view) {
859 return view->colorcolumn;
862 size_t view_screenline_goto(View *view, int n) {
863 size_t pos = view->start;
864 for (Line *line = view->topline; --n > 0 && line != view->lastline; line = line->next)
865 pos += line->len;
866 return pos;
869 static Cursor *cursors_new(View *view, size_t pos, bool force) {
870 Cursor *c = calloc(1, sizeof(*c));
871 if (!c)
872 return NULL;
873 c->view = view;
874 c->generation = view->cursor_generation;
875 if (!view->cursors) {
876 view->cursor = c;
877 view->cursor_latest = c;
878 view->cursors = c;
879 view->cursor_count = 1;
880 return c;
883 Cursor *prev = NULL, *next = NULL;
884 Cursor *latest = view->cursor_latest ? view->cursor_latest : view->cursor;
885 size_t cur = view_cursors_pos(latest);
886 if (pos == cur) {
887 prev = latest;
888 next = prev->next;
889 } else if (pos > cur) {
890 prev = latest;
891 for (next = prev->next; next; prev = next, next = next->next) {
892 cur = view_cursors_pos(next);
893 if (pos <= cur)
894 break;
896 } else if (pos < cur) {
897 next = latest;
898 for (prev = next->prev; prev; next = prev, prev = prev->prev) {
899 cur = view_cursors_pos(prev);
900 if (pos >= cur)
901 break;
905 if (pos == cur && !force)
906 goto err;
908 for (Cursor *after = next; after; after = after->next)
909 after->number++;
911 c->prev = prev;
912 c->next = next;
913 if (next)
914 next->prev = c;
915 if (prev) {
916 prev->next = c;
917 c->number = prev->number + 1;
918 } else {
919 view->cursors = c;
921 view->cursor_latest = c;
922 view->cursor_count++;
923 view_cursors_dispose(view->cursor_dead);
924 view_cursors_to(c, pos);
925 return c;
926 err:
927 free(c);
928 return NULL;
931 Cursor *view_cursors_new(View *view, size_t pos) {
932 return cursors_new(view, pos, false);
935 Cursor *view_cursors_new_force(View *view, size_t pos) {
936 return cursors_new(view, pos, true);
939 int view_cursors_count(View *view) {
940 return view->cursor_count;
943 int view_cursors_number(Cursor *c) {
944 return c->number;
947 int view_cursors_column_count(View *view) {
948 Text *txt = view->text;
949 int cpl_max = 0, cpl = 0; /* cursors per line */
950 size_t line_prev = 0;
951 for (Cursor *c = view->cursors; c; c = c->next) {
952 size_t pos = view_cursors_pos(c);
953 size_t line = text_lineno_by_pos(txt, pos);
954 if (line == line_prev)
955 cpl++;
956 else
957 cpl = 1;
958 line_prev = line;
959 if (cpl > cpl_max)
960 cpl_max = cpl;
962 return cpl_max;
965 static Cursor *cursors_column_next(View *view, Cursor *c, int column) {
966 size_t line_cur = 0;
967 int column_cur = 0;
968 Text *txt = view->text;
969 if (c) {
970 size_t pos = view_cursors_pos(c);
971 line_cur = text_lineno_by_pos(txt, pos);
972 column_cur = INT_MIN;
973 } else {
974 c = view->cursors;
977 for (; c; c = c->next) {
978 size_t pos = view_cursors_pos(c);
979 size_t line = text_lineno_by_pos(txt, pos);
980 if (line != line_cur) {
981 line_cur = line;
982 column_cur = 0;
983 } else {
984 column_cur++;
986 if (column == column_cur)
987 return c;
989 return NULL;
992 Cursor *view_cursors_column(View *view, int column) {
993 return cursors_column_next(view, NULL, column);
996 Cursor *view_cursors_column_next(Cursor *c, int column) {
997 return cursors_column_next(c->view, c, column);
1000 bool view_cursors_multiple(View *view) {
1001 return view->cursors && view->cursors->next;
1004 static void view_cursors_free(Cursor *c) {
1005 if (!c)
1006 return;
1007 for (Cursor *after = c->next; after; after = after->next)
1008 after->number--;
1009 if (c->prev)
1010 c->prev->next = c->next;
1011 if (c->next)
1012 c->next->prev = c->prev;
1013 if (c->view->cursors == c)
1014 c->view->cursors = c->next;
1015 if (c->view->cursor == c)
1016 c->view->cursor = c->next ? c->next : c->prev;
1017 if (c->view->cursor_dead == c)
1018 c->view->cursor_dead = NULL;
1019 if (c->view->cursor_latest == c)
1020 c->view->cursor_latest = c->prev ? c->prev : c->next;
1021 c->view->cursor_count--;
1022 free(c);
1025 bool view_cursors_dispose(Cursor *c) {
1026 if (!c)
1027 return true;
1028 View *view = c->view;
1029 if (!view->cursors || !view->cursors->next)
1030 return false;
1031 view_selections_free(c->sel);
1032 view_cursors_free(c);
1033 view_cursors_primary_set(view->cursor);
1034 return true;
1037 bool view_cursors_dispose_force(Cursor *c) {
1038 if (view_cursors_dispose(c))
1039 return true;
1040 View *view = c->view;
1041 if (view->cursor_dead)
1042 return false;
1043 view_cursors_selection_clear(c);
1044 view->cursor_dead = c;
1045 return true;
1048 Cursor *view_cursor_disposed(View *view) {
1049 Cursor *c = view->cursor_dead;
1050 view->cursor_dead = NULL;
1051 return c;
1054 Cursor *view_cursors(View *view) {
1055 view->cursor_generation++;
1056 return view->cursors;
1059 Cursor *view_cursors_primary_get(View *view) {
1060 view->cursor_generation++;
1061 return view->cursor;
1064 void view_cursors_primary_set(Cursor *c) {
1065 if (!c)
1066 return;
1067 View *view = c->view;
1068 view->cursor = c;
1069 Filerange sel = view_cursors_selection_get(c);
1070 view_cursors_to(c, view_cursors_pos(c));
1071 view_cursors_selection_set(c, &sel);
1074 Cursor *view_cursors_prev(Cursor *c) {
1075 View *view = c->view;
1076 for (c = c->prev; c; c = c->prev) {
1077 if (c->generation != view->cursor_generation)
1078 return c;
1080 view->cursor_generation++;
1081 return NULL;
1084 Cursor *view_cursors_next(Cursor *c) {
1085 View *view = c->view;
1086 for (c = c->next; c; c = c->next) {
1087 if (c->generation != view->cursor_generation)
1088 return c;
1090 view->cursor_generation++;
1091 return NULL;
1094 size_t view_cursors_pos(Cursor *c) {
1095 size_t pos = text_mark_get(c->view->text, c->mark);
1096 return pos != EPOS ? pos : text_mark_get(c->view->text, c->mark_old);
1099 size_t view_cursors_line(Cursor *c) {
1100 size_t pos = view_cursors_pos(c);
1101 return text_lineno_by_pos(c->view->text, pos);
1104 size_t view_cursors_col(Cursor *c) {
1105 size_t pos = view_cursors_pos(c);
1106 return text_line_char_get(c->view->text, pos) + 1;
1109 int view_cursors_cell_get(Cursor *c) {
1110 return c->line ? c->col : -1;
1113 int view_cursors_cell_set(Cursor *c, int cell) {
1114 if (!c->line || cell < 0)
1115 return -1;
1116 cursor_set(c, c->line, cell);
1117 return c->col;
1120 void view_cursors_scroll_to(Cursor *c, size_t pos) {
1121 View *view = c->view;
1122 if (view->cursor == c) {
1123 view_draw(view);
1124 while (pos < view->start && view_viewport_up(view, 1));
1125 while (pos > view->end && view_viewport_down(view, 1));
1127 view_cursors_to(c, pos);
1130 void view_cursors_to(Cursor *c, size_t pos) {
1131 View *view = c->view;
1132 if (pos == EPOS)
1133 return;
1134 size_t size = text_size(view->text);
1135 if (pos > size)
1136 pos = size;
1137 if (c->view->cursor == c) {
1138 /* make sure we redraw changes to the very first character of the window */
1139 if (view->start == pos)
1140 view->start_last = 0;
1142 if (view->end == pos && view->lastline == view->bottomline) {
1143 view->start += view->topline->len;
1144 view_draw(view);
1147 if (pos < view->start || pos > view->end) {
1148 view->start = pos;
1149 view_viewport_up(view, view->height / 2);
1152 if (pos <= view->start || pos > view->end) {
1153 view->start = text_line_begin(view->text, pos);
1154 view_draw(view);
1157 if (pos <= view->start || pos > view->end) {
1158 view->start = pos;
1159 view_draw(view);
1163 cursor_to(c, pos);
1166 void view_cursors_place(Cursor *c, size_t line, size_t col) {
1167 Text *txt = c->view->text;
1168 size_t pos = text_pos_by_lineno(txt, line);
1169 pos = text_line_char_set(txt, pos, col > 0 ? col-1 : col);
1170 view_cursors_to(c, pos);
1173 void view_cursors_selection_start(Cursor *c) {
1174 if (c->sel)
1175 return;
1176 size_t pos = view_cursors_pos(c);
1177 if (pos == EPOS || !(c->sel = view_selections_new(c->view, c)))
1178 return;
1179 Text *txt = c->view->text;
1180 c->sel->anchor = text_mark_set(txt, pos);
1181 c->sel->cursor = c->sel->anchor;
1182 c->view->need_update = true;
1185 void view_cursors_selection_restore(Cursor *c) {
1186 Text *txt = c->view->text;
1187 if (c->sel)
1188 return;
1189 Filerange sel = text_range_new(
1190 text_mark_get(txt, c->lastsel_anchor),
1191 text_mark_get(txt, c->lastsel_cursor)
1193 if (!text_range_valid(&sel))
1194 return;
1195 view_cursors_to(c, sel.end);
1196 sel.end = text_char_next(txt, sel.end);
1197 if (!(c->sel = view_selections_new(c->view, c)))
1198 return;
1199 view_selections_set(c->sel, &sel);
1202 void view_cursors_selection_stop(Cursor *c) {
1203 c->sel = NULL;
1206 void view_cursors_selection_clear(Cursor *c) {
1207 view_selections_free(c->sel);
1208 c->view->need_update = true;
1211 void view_cursors_selection_swap(Cursor *c) {
1212 if (!c->sel)
1213 return;
1214 view_selections_swap(c->sel);
1215 view_cursors_selection_sync(c);
1218 void view_cursors_selection_sync(Cursor *c) {
1219 if (!c->sel)
1220 return;
1221 Text *txt = c->view->text;
1222 size_t pos = text_mark_get(txt, c->sel->cursor);
1223 view_cursors_to(c, pos);
1226 Filerange view_cursors_selection_get(Cursor *c) {
1227 return view_selections_get(c->sel);
1230 void view_cursors_selection_set(Cursor *c, const Filerange *r) {
1231 if (!text_range_valid(r))
1232 return;
1233 bool new = !c->sel;
1234 if (new && !(c->sel = view_selections_new(c->view, c)))
1235 return;
1236 view_selections_set(c->sel, r);
1237 if (!new) {
1238 size_t pos = view_cursors_pos(c);
1239 if (!text_range_contains(r, pos))
1240 view_cursors_selection_sync(c);
1244 Selection *view_selections_new(View *view, Cursor *c) {
1245 Selection *s = calloc(1, sizeof(*s));
1246 if (!s)
1247 return NULL;
1248 s->view = view;
1249 s->next = view->selections;
1250 if (view->selections)
1251 view->selections->prev = s;
1252 view->selections = s;
1253 s->cur = c;
1254 return s;
1257 void view_selections_free(Selection *s) {
1258 if (!s)
1259 return;
1260 if (s->prev)
1261 s->prev->next = s->next;
1262 if (s->next)
1263 s->next->prev = s->prev;
1264 if (s->view->selections == s)
1265 s->view->selections = s->next;
1266 Cursor *c = s->cur;
1267 if (c) {
1268 c->lastsel_anchor = s->anchor;
1269 c->lastsel_cursor = s->cursor;
1270 c->sel = NULL;
1272 free(s);
1275 void view_selections_clear(View *view) {
1276 while (view->selections)
1277 view_selections_free(view->selections);
1278 view_draw(view);
1281 void view_cursors_clear(View *view) {
1282 for (Cursor *c = view->cursors, *next; c; c = next) {
1283 next = c->next;
1284 if (c != view->cursor) {
1285 view_selections_free(c->sel);
1286 view_cursors_free(c);
1289 view_draw(view);
1292 void view_selections_swap(Selection *s) {
1293 Mark temp = s->anchor;
1294 s->anchor = s->cursor;
1295 s->cursor = temp;
1298 Selection *view_selections(View *view) {
1299 return view->selections;
1302 Selection *view_selections_prev(Selection *s) {
1303 return s->prev;
1306 Selection *view_selections_next(Selection *s) {
1307 return s->next;
1310 Filerange view_selection_get(View *view) {
1311 return view_selections_get(view->cursor->sel);
1314 Filerange view_selections_get(Selection *s) {
1315 if (!s)
1316 return text_range_empty();
1317 Text *txt = s->view->text;
1318 size_t anchor = text_mark_get(txt, s->anchor);
1319 size_t cursor = text_mark_get(txt, s->cursor);
1320 Filerange sel = text_range_new(anchor, cursor);
1321 if (text_range_valid(&sel))
1322 sel.end = text_char_next(txt, sel.end);
1323 return sel;
1326 void view_selections_set(Selection *s, const Filerange *r) {
1327 if (!text_range_valid(r))
1328 return;
1329 Text *txt = s->view->text;
1330 size_t anchor = text_mark_get(txt, s->anchor);
1331 size_t cursor = text_mark_get(txt, s->cursor);
1332 bool left_extending = anchor != EPOS && anchor > cursor;
1333 size_t end = r->end;
1334 if (r->start != end)
1335 end = text_char_prev(txt, end);
1336 if (left_extending) {
1337 s->anchor = text_mark_set(txt, end);
1338 s->cursor = text_mark_set(txt, r->start);
1339 } else {
1340 s->anchor = text_mark_set(txt, r->start);
1341 s->cursor = text_mark_set(txt, end);
1343 s->view->need_update = true;
1346 Text *view_text(View *view) {
1347 return view->text;
1350 bool view_style_define(View *view, enum UiStyle id, const char *style) {
1351 return view->ui->style_define(view->ui, id, style);
1354 void view_style(View *view, enum UiStyle style_id, size_t start, size_t end) {
1355 if (end < view->start || start > view->end)
1356 return;
1358 CellStyle style = view->ui->style_get(view->ui, style_id);
1359 size_t pos = view->start;
1360 Line *line = view->topline;
1362 /* skip lines before range to be styled */
1363 while (line && pos + line->len <= start) {
1364 pos += line->len;
1365 line = line->next;
1368 if (!line)
1369 return;
1371 int col = 0, width = view->width;
1373 /* skip columns before range to be styled */
1374 while (pos < start && col < width)
1375 pos += line->cells[col++].len;
1377 do {
1378 while (pos <= end && col < width) {
1379 pos += line->cells[col].len;
1380 line->cells[col++].style = style;
1382 col = 0;
1383 } while (pos <= end && (line = line->next));