11 #include "text-motions.h"
12 #include "text-util.h"
23 SYNTAX_SYMBOL_TAB_FILL
,
29 /* A selection is made up of two marks named cursor and anchor.
30 * While the anchor remains fixed the cursor mark follows cursor motions.
31 * For a selection (indicated by []), the marks (^) are placed as follows:
37 * That is the marks point to the *start* of the first and last character
38 * of the selection. In particular for a single character selection (as
39 * depicted on the right above) both marks point to the same location.
41 * The view_selections_{get,set} functions take care of adding/removing
42 * the necessary offset for the last character.
45 Mark anchor
; /* position where the selection was created */
46 Mark cursor
; /* other selection endpoint where it changes */
47 View
*view
; /* associated view to which this selection belongs */
48 Selection
*prev
, *next
; /* previsous/next selections in no particular order */
51 struct Cursor
{ /* cursor position */
52 Filepos pos
; /* in bytes from the start of the file */
53 int row
, col
; /* in terms of zero based screen coordinates */
54 int lastcol
; /* remembered column used when moving across lines */
55 Line
*line
; /* screen line on which cursor currently resides */
56 Mark mark
; /* mark used to keep track of current cursor position */
57 Selection
*sel
; /* selection (if any) which folows the cursor upon movement */
58 Mark lastsel_anchor
;/* previously used selection data, */
59 Mark lastsel_cursor
;/* used to restore it */
60 Register reg
; /* per cursor register to support yank/put operation */
61 int generation
; /* used to filter out newly created cursors during iteration */
62 int number
; /* how many cursors are located before this one */
63 View
*view
; /* associated view to which this cursor belongs */
64 Cursor
*prev
, *next
;/* previous/next cursors ordered by location at creation time */
67 /* Viewable area, showing part of a file. Keeps track of cursors and selections.
68 * At all times there exists at least one cursor, which is placed in the visible viewport.
69 * Additional cursors can be created and positioned anywhere in the file. */
71 Text
*text
; /* underlying text management */
73 int width
, height
; /* size of display area */
74 Filepos start
, end
; /* currently displayed area [start, end] in bytes from the start of the file */
75 Filepos start_last
; /* previously used start of visible area, used to update the mark */
76 Mark start_mark
; /* mark to keep track of the start of the visible area */
77 size_t lines_size
; /* number of allocated bytes for lines (grows only) */
78 Line
*lines
; /* view->height number of lines representing view content */
79 Line
*topline
; /* top of the view, first line currently shown */
80 Line
*lastline
; /* last currently used line, always <= bottomline */
81 Line
*bottomline
; /* bottom of view, might be unused if lastline < bottomline */
82 Cursor
*cursor
; /* main cursor, always placed within the visible viewport */
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 lua_State
*lua
; /* lua state used for syntax highlighting */
91 int cursor_generation
; /* used to filter out newly created cursors during iteration */
93 size_t horizon
; /* maximal number of bytes to consider for syntax highlighting
94 * before the visible area */
95 bool need_update
; /* whether view has been redrawn */
96 bool large_file
; /* optimize for displaying large files */
100 static const SyntaxSymbol symbols_none
[] = {
101 [SYNTAX_SYMBOL_SPACE
] = { " " },
102 [SYNTAX_SYMBOL_TAB
] = { " " },
103 [SYNTAX_SYMBOL_TAB_FILL
] = { " " },
104 [SYNTAX_SYMBOL_EOL
] = { " " },
105 [SYNTAX_SYMBOL_EOF
] = { "~" },
108 static const SyntaxSymbol symbols_default
[] = {
109 [SYNTAX_SYMBOL_SPACE
] = { "\xC2\xB7" },
110 [SYNTAX_SYMBOL_TAB
] = { "\xE2\x96\xB6" },
111 [SYNTAX_SYMBOL_TAB_FILL
] = { " " },
112 [SYNTAX_SYMBOL_EOL
] = { "\xE2\x8F\x8E" },
113 [SYNTAX_SYMBOL_EOF
] = { "~" },
116 static Cell cell_unused
;
117 static Cell cell_blank
= { .data
= " " };
119 static void view_clear(View
*view
);
120 static bool view_addch(View
*view
, Cell
*cell
);
121 static bool view_coord_get(View
*view
, size_t pos
, Line
**retline
, int *retrow
, int *retcol
);
122 static void view_cursors_free(Cursor
*c
);
123 /* set/move current cursor position to a given (line, column) pair */
124 static size_t cursor_set(Cursor
*cursor
, Line
*line
, int col
);
128 static void view_syntax_color(View
*view
) { }
129 bool view_syntax_set(View
*view
, const char *name
) { return false; }
133 static void view_syntax_color(View
*view
) {
134 lua_State
*L
= view
->lua
;
135 if (!L
|| !view
->lexer_name
)
137 lua_getglobal(L
, "vis");
138 lua_getfield(L
, -1, "lexers");
139 if (lua_isnil(L
, -1))
142 /* absolute position to start syntax highlighting */
143 const size_t lexer_start
= view
->start
>= view
->horizon
?
144 view
->start
- view
->horizon
: 0;
145 /* number of bytes used for syntax highlighting before visible are */
146 size_t lexer_before
= view
->start
- lexer_start
;
147 /* number of bytes to read in one go */
148 const size_t text_size
= lexer_before
+ (view
->end
- view
->start
);
149 /* current buffer to work with */
150 char text
[text_size
+1];
151 /* bytes to process */
152 const size_t text_len
= text_bytes_get(view
->text
, lexer_start
, text_size
, text
);
153 /* NUL terminate text section */
154 text
[text_len
] = '\0';
155 if (text_len
< lexer_before
)
156 lexer_before
= text_len
;
158 lua_getfield(L
, -1, "load");
159 lua_pushstring(L
, view
->lexer_name
);
160 lua_pcall(L
, 1, 1, 0);
162 lua_getfield(L
, -1, "_TOKENSTYLES");
163 lua_getfield(L
, -2, "lex");
165 lua_pushvalue(L
, -3); /* lexer obj */
167 const char *lex_text
= text
;
168 if (lexer_start
> 0) {
169 /* try to start lexing at a line boundry */
170 /* TODO: start at known state, handle nested lexers */
171 const char *newline
= memchr(text
, '\n', lexer_before
);
176 lua_pushlstring(L
, lex_text
, text_len
- (lex_text
- text
));
177 lua_pushinteger(L
, 1 /* inital style: whitespace */);
181 if (lua_isfunction(L
, -4) && !lua_pcall(L
, 3, 1, 0) && lua_istable(L
, -1) &&
182 (token_count
= lua_objlen(L
, -1)) > 0) {
184 size_t pos
= lexer_before
- (lex_text
- text
);
185 Line
*line
= view
->topline
;
188 for (int i
= 1; i
< token_count
; i
+= 2) {
189 lua_rawgeti(L
, -1, i
);
190 //const char *name = lua_tostring(L, -1);
191 lua_gettable(L
, -3); /* _TOKENSTYLES[token] */
192 size_t token_style
= lua_tointeger(L
, -1);
193 lua_pop(L
, 1); /* style */
194 lua_rawgeti(L
, -1, i
+ 1);
195 size_t token_end
= lua_tointeger(L
, -1) - 1;
196 lua_pop(L
, 1); /* pos */
198 for (bool token_next
= false; line
; line
= line
->next
, col
= 0) {
199 for (; col
< line
->width
; col
++) {
200 if (pos
< token_end
) {
201 line
->cells
[col
].style
= token_style
;
202 pos
+= line
->cells
[col
].len
;
215 lua_pop(L
, 3); /* _TOKENSTYLES, language specific lexer, lexers global */
218 bool view_syntax_set(View
*view
, const char *name
) {
219 lua_State
*L
= view
->lua
;
223 lua_getglobal(L
, "vis");
224 lua_getfield(L
, -1, "lexers");
226 static const struct {
230 { UI_STYLE_DEFAULT
, "STYLE_DEFAULT" },
231 { UI_STYLE_CURSOR
, "STYLE_CURSOR" },
232 { UI_STYLE_CURSOR_PRIMARY
, "STYLE_CURSOR_PRIMARY" },
233 { UI_STYLE_CURSOR_LINE
, "STYLE_CURSOR_LINE" },
234 { UI_STYLE_SELECTION
, "STYLE_SELECTION" },
235 { UI_STYLE_LINENUMBER
, "STYLE_LINENUMBER" },
236 { UI_STYLE_COLOR_COLUMN
, "STYLE_COLOR_COLUMN" },
239 for (size_t i
= 0; i
< LENGTH(styles
); i
++) {
240 lua_getfield(L
, -1, styles
[i
].name
);
241 view
->ui
->syntax_style(view
->ui
, styles
[i
].id
, lua_tostring(L
, -1));
246 free(view
->lexer_name
);
247 view
->lexer_name
= NULL
;
251 /* Try to load the specified lexer and parse its token styles.
252 * Roughly equivalent to the following lua code:
254 * lang = vis.lexers.load(name)
255 * for token_name, id in pairs(lang._TOKENSTYLES) do
256 * ui->syntax_style(id, vis.lexers:get_style(lang, token_name);
258 lua_getfield(L
, -1, "load");
259 lua_pushstring(L
, name
);
261 if (lua_pcall(L
, 1, 1, 0))
264 if (!lua_istable(L
, -1)) {
269 view
->lexer_name
= strdup(name
);
270 /* loop through all _TOKENSTYLES and parse them */
271 lua_getfield(L
, -1, "_TOKENSTYLES");
272 lua_pushnil(L
); /* first key */
274 while (lua_next(L
, -2)) {
275 size_t id
= lua_tointeger(L
, -1);
276 //const char *name = lua_tostring(L, -2);
277 lua_pop(L
, 1); /* remove value (=id), keep key (=name) */
278 lua_getfield(L
, -4, "get_style");
279 lua_pushvalue(L
, -5); /* lexer */
280 lua_pushvalue(L
, -5); /* lang */
281 lua_pushvalue(L
, -4); /* token_name */
282 if (lua_pcall(L
, 3, 1, 0))
284 const char *style
= lua_tostring(L
, -1);
285 //printf("%s\t%d\t%s\n", name, id, style);
286 view
->ui
->syntax_style(view
->ui
, id
, style
);
287 lua_pop(L
, 1); /* style */
290 lua_pop(L
, 4); /* _TOKENSTYLES, grammar, lexers, vis */
298 void view_tabwidth_set(View
*view
, int tabwidth
) {
299 view
->tabwidth
= tabwidth
;
303 /* reset internal view data structures (cell matrix, line offsets etc.) */
304 static void view_clear(View
*view
) {
305 memset(view
->lines
, 0, view
->lines_size
);
306 if (view
->start
!= view
->start_last
) {
307 view
->start_mark
= text_mark_set(view
->text
, view
->start
);
309 size_t start
= text_mark_get(view
->text
, view
->start_mark
);
314 view
->start_last
= view
->start
;
315 view
->topline
= view
->lines
;
316 view
->topline
->lineno
= view
->large_file
? 1 : text_lineno_by_pos(view
->text
, view
->start
);
317 view
->lastline
= view
->topline
;
319 size_t line_size
= sizeof(Line
) + view
->width
*sizeof(Cell
);
320 size_t end
= view
->height
* line_size
;
322 for (size_t i
= 0; i
< end
; i
+= line_size
) {
323 Line
*line
= (Line
*)(((char*)view
->lines
) + i
);
329 view
->bottomline
= prev
? prev
: view
->topline
;
330 view
->bottomline
->next
= NULL
;
331 view
->line
= view
->topline
;
335 Filerange
view_viewport_get(View
*view
) {
336 return (Filerange
){ .start
= view
->start
, .end
= view
->end
};
339 /* try to add another character to the view, return whether there was space left */
340 static bool view_addch(View
*view
, Cell
*cell
) {
345 size_t lineno
= view
->line
->lineno
;
346 unsigned char ch
= (unsigned char)cell
->data
[0];
351 width
= view
->tabwidth
- (view
->col
% view
->tabwidth
);
352 for (int w
= 0; w
< width
; w
++) {
353 if (view
->col
+ 1 > view
->width
) {
354 view
->line
= view
->line
->next
;
358 view
->line
->lineno
= lineno
;
361 cell
->len
= w
== 0 ? 1 : 0;
362 int t
= w
== 0 ? SYNTAX_SYMBOL_TAB
: SYNTAX_SYMBOL_TAB_FILL
;
363 strncpy(cell
->data
, view
->symbols
[t
]->symbol
, sizeof(cell
->data
)-1);
364 cell
->style
= view
->symbols
[t
]->style
;
365 view
->line
->cells
[view
->col
] = *cell
;
366 view
->line
->len
+= cell
->len
;
367 view
->line
->width
+= cell
->width
;
374 if (view
->col
+ cell
->width
> view
->width
) {
375 view
->line
= view
->line
->next
;
379 view
->line
->lineno
= lineno
;
382 strncpy(cell
->data
, view
->symbols
[SYNTAX_SYMBOL_EOL
]->symbol
, sizeof(cell
->data
)-1);
383 cell
->style
= view
->symbols
[SYNTAX_SYMBOL_EOL
]->style
;
385 view
->line
->cells
[view
->col
] = *cell
;
386 view
->line
->len
+= cell
->len
;
387 view
->line
->width
+= cell
->width
;
388 for (int i
= view
->col
+ 1; i
< view
->width
; i
++)
389 view
->line
->cells
[i
] = cell_blank
;
391 view
->line
= view
->line
->next
;
393 view
->line
->lineno
= lineno
+ 1;
397 if (ch
< 128 && !isprint(ch
)) {
398 /* non-printable ascii char, represent it as ^(char + 64) */
400 .data
= { '^', ch
== 127 ? '?' : ch
+ 64, '\0' },
403 .style
= cell
->style
,
408 strncpy(cell
->data
, view
->symbols
[SYNTAX_SYMBOL_SPACE
]->symbol
, sizeof(cell
->data
)-1);
409 cell
->style
= view
->symbols
[SYNTAX_SYMBOL_SPACE
]->style
;
413 if (view
->col
+ cell
->width
> view
->width
) {
414 for (int i
= view
->col
; i
< view
->width
; i
++)
415 view
->line
->cells
[i
] = cell_blank
;
416 view
->line
= view
->line
->next
;
421 view
->line
->width
+= cell
->width
;
422 view
->line
->len
+= cell
->len
;
423 view
->line
->lineno
= lineno
;
424 view
->line
->cells
[view
->col
] = *cell
;
426 /* set cells of a character which uses multiple columns */
427 for (int i
= 1; i
< cell
->width
; i
++)
428 view
->line
->cells
[view
->col
++] = cell_unused
;
435 CursorPos
view_cursor_getpos(View
*view
) {
436 Cursor
*cursor
= view
->cursor
;
437 Line
*line
= cursor
->line
;
438 CursorPos pos
= { .line
= line
->lineno
, .col
= cursor
->col
};
439 while (line
->prev
&& line
->prev
->lineno
== pos
.line
) {
441 pos
.col
+= line
->width
;
447 static void cursor_to(Cursor
*c
, size_t pos
) {
448 Text
*txt
= c
->view
->text
;
449 c
->mark
= text_mark_set(txt
, pos
);
454 c
->sel
->cursor
= text_mark_set(txt
, pos
);
456 if (!view_coord_get(c
->view
, pos
, &c
->line
, &c
->row
, &c
->col
)) {
457 if (c
->view
->cursor
== c
) {
458 c
->line
= c
->view
->topline
;
464 // TODO: minimize number of redraws
468 static bool view_coord_get(View
*view
, size_t pos
, Line
**retline
, int *retrow
, int *retcol
) {
469 int row
= 0, col
= 0;
470 size_t cur
= view
->start
;
471 Line
*line
= view
->topline
;
473 if (pos
< view
->start
|| pos
> view
->end
) {
474 if (retline
) *retline
= NULL
;
475 if (retrow
) *retrow
= -1;
476 if (retcol
) *retcol
= -1;
480 while (line
&& line
!= view
->lastline
&& cur
< pos
) {
481 if (cur
+ line
->len
> pos
)
489 int max_col
= MIN(view
->width
, line
->width
);
490 while (cur
< pos
&& col
< max_col
) {
491 cur
+= line
->cells
[col
].len
;
492 /* skip over columns occupied by the same character */
493 while (++col
< max_col
&& line
->cells
[col
].len
== 0);
496 line
= view
->bottomline
;
497 row
= view
->height
- 1;
500 if (retline
) *retline
= line
;
501 if (retrow
) *retrow
= row
;
502 if (retcol
) *retcol
= col
;
506 /* move the cursor to the character at pos bytes from the begining of the file.
507 * if pos is not in the current viewport, redraw the view to make it visible */
508 void view_cursor_to(View
*view
, size_t pos
) {
509 view_cursors_to(view
->cursor
, pos
);
512 /* redraw the complete with data starting from view->start bytes into the file.
513 * stop once the screen is full, update view->end, view->lastline */
514 void view_draw(View
*view
) {
516 /* read a screenful of text considering each character as 4-byte UTF character*/
517 const size_t text_size
= view
->width
* view
->height
* 4;
518 /* current buffer to work with */
519 char text
[text_size
+1];
520 /* remaining bytes to process in buffer */
521 size_t rem
= text_bytes_get(view
->text
, view
->start
, text_size
, text
);
522 /* NUL terminate text section */
524 /* absolute position of character currently being added to display */
525 size_t pos
= view
->start
;
526 /* current position into buffer from which to interpret a character */
528 /* start from known multibyte state */
529 mbstate_t mbstate
= { 0 };
531 Cell cell
= { 0 }, prev_cell
= { 0 };
535 /* current 'parsed' character' */
538 size_t len
= mbrtowc(&wchar
, cur
, rem
, &mbstate
);
539 if (len
== (size_t)-1 && errno
== EILSEQ
) {
540 /* ok, we encountered an invalid multibyte sequence,
541 * replace it with the Unicode Replacement Character
542 * (FFFD) and skip until the start of the next utf8 char */
543 for (len
= 1; rem
> len
&& !ISUTF8(cur
[len
]); len
++);
544 cell
= (Cell
){ .data
= "\xEF\xBF\xBD", .len
= len
, .width
= 1 };
545 } else if (len
== (size_t)-2) {
546 /* not enough bytes available to convert to a
547 * wide character. advance file position and read
548 * another junk into buffer.
550 rem
= text_bytes_get(view
->text
, pos
, text_size
, text
);
554 } else if (len
== 0) {
555 /* NUL byte encountered, store it and continue */
556 cell
= (Cell
){ .data
= "\x00", .len
= 1, .width
= 2 };
558 for (size_t i
= 0; i
< len
; i
++)
559 cell
.data
[i
] = cur
[i
];
560 cell
.data
[len
] = '\0';
562 cell
.width
= wcwidth(wchar
);
563 if (cell
.width
== -1)
567 if (cur
[0] == '\r' && rem
> 1 && cur
[1] == '\n') {
568 /* convert views style newline \r\n into a single char with len = 2 */
569 cell
= (Cell
){ .data
= "\n", .len
= 2, .width
= 1 };
572 if (cell
.width
== 0 && prev_cell
.len
+ cell
.len
< sizeof(cell
.len
)) {
573 prev_cell
.len
+= cell
.len
;
574 strcat(prev_cell
.data
, cell
.data
);
576 if (prev_cell
.len
&& !view_addch(view
, &prev_cell
))
578 pos
+= prev_cell
.len
;
585 memset(&cell
, 0, sizeof cell
);
588 if (prev_cell
.len
&& view_addch(view
, &prev_cell
))
589 pos
+= prev_cell
.len
;
591 /* set end of vieviewg region */
593 view
->lastline
= view
->line
? view
->line
: view
->bottomline
;
595 /* clear remaining of line, important to show cursor at end of file */
597 for (int x
= view
->col
; x
< view
->width
; x
++)
598 view
->line
->cells
[x
] = cell_blank
;
601 /* resync position of cursors within visible area */
602 for (Cursor
*c
= view
->cursors
; c
; c
= c
->next
) {
603 size_t pos
= view_cursors_pos(c
);
604 if (view_coord_get(view
, pos
, &c
->line
, &c
->row
, &c
->col
)) {
605 c
->line
->cells
[c
->col
].cursor
= true;
606 c
->line
->cells
[c
->col
].cursor_primary
= (c
== view
->cursor
);
607 if (view
->ui
&& !c
->sel
) {
608 Line
*line_match
; int col_match
;
609 size_t pos_match
= text_bracket_match_symbol(view
->text
, pos
, "(){}[]\"'`");
610 if (pos
!= pos_match
&& view_coord_get(view
, pos_match
, &line_match
, NULL
, &col_match
)) {
611 line_match
->cells
[col_match
].selected
= true;
614 } else if (c
== view
->cursor
) {
615 c
->line
= view
->topline
;
621 view
->need_update
= true;
624 void view_update(View
*view
) {
625 if (!view
->need_update
)
628 view_syntax_color(view
);
630 if (view
->colorcolumn
> 0) {
632 int line_cols
= 0; /* Track the number of columns we've passed on each line */
633 bool line_cc_set
= false; /* Has the colorcolumn attribute been set for this line yet */
635 for (Line
*l
= view
->topline
; l
; l
= l
->next
) {
636 if (l
->lineno
!= lineno
) {
642 line_cols
+= view
->width
;
644 /* This screen line contains the cell we want to highlight */
645 if (line_cols
>= view
->colorcolumn
) {
646 l
->cells
[(view
->colorcolumn
- 1) % view
->width
].style
= UI_STYLE_COLOR_COLUMN
;
655 for (Line
*l
= view
->lastline
->next
; l
; l
= l
->next
) {
656 strncpy(l
->cells
[0].data
, view
->symbols
[SYNTAX_SYMBOL_EOF
]->symbol
, sizeof(l
->cells
[0].data
));
657 l
->cells
[0].style
= view
->symbols
[SYNTAX_SYMBOL_EOF
]->style
;
658 for (int x
= 1; x
< view
->width
; x
++)
659 l
->cells
[x
] = cell_blank
;
664 for (Selection
*s
= view
->selections
; s
; s
= s
->next
) {
665 Filerange sel
= view_selections_get(s
);
666 if (text_range_valid(&sel
)) {
667 Line
*start_line
; int start_col
;
668 Line
*end_line
; int end_col
;
669 view_coord_get(view
, sel
.start
, &start_line
, NULL
, &start_col
);
670 view_coord_get(view
, sel
.end
, &end_line
, NULL
, &end_col
);
671 if (start_line
|| end_line
) {
673 start_line
= view
->topline
;
677 end_line
= view
->lastline
;
678 end_col
= end_line
->width
;
680 for (Line
*l
= start_line
; l
!= end_line
->next
; l
= l
->next
) {
681 int col
= (l
== start_line
) ? start_col
: 0;
682 int end
= (l
== end_line
) ? end_col
: l
->width
;
684 l
->cells
[col
++].selected
= true;
692 view
->ui
->draw(view
->ui
);
693 view
->need_update
= false;
696 bool view_resize(View
*view
, int width
, int height
) {
701 size_t lines_size
= height
*(sizeof(Line
) + width
*sizeof(Cell
));
702 if (lines_size
> view
->lines_size
) {
703 Line
*lines
= realloc(view
->lines
, lines_size
);
707 view
->lines_size
= lines_size
;
710 view
->height
= height
;
711 memset(view
->lines
, 0, view
->lines_size
);
716 int view_height_get(View
*view
) {
720 int view_width_get(View
*view
) {
724 void view_free(View
*view
) {
727 while (view
->cursors
)
728 view_cursors_free(view
->cursors
);
729 while (view
->selections
)
730 view_selections_free(view
->selections
);
732 free(view
->lexer_name
);
736 void view_reload(View
*view
, Text
*text
) {
738 view_selections_clear(view
);
739 view_cursor_to(view
, 0);
742 View
*view_new(Text
*text
, lua_State
*lua
) {
745 View
*view
= calloc(1, sizeof(View
));
748 if (!view_cursors_new(view
, 0)) {
756 view
->horizon
= 1 << 15;
757 view_options_set(view
, 0);
759 if (!view_resize(view
, 1, 1)) {
764 view_cursor_to(view
, 0);
769 void view_ui(View
*view
, UiWin
* ui
) {
773 static size_t cursor_set(Cursor
*cursor
, Line
*line
, int col
) {
775 View
*view
= cursor
->view
;
776 size_t pos
= view
->start
;
777 /* get row number and file offset at start of the given line */
778 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
) {
783 /* for characters which use more than 1 column, make sure we are on the left most */
784 while (col
> 0 && line
->cells
[col
].len
== 0)
786 /* calculate offset within the line */
787 for (int i
= 0; i
< col
; i
++)
788 pos
+= line
->cells
[i
].len
;
794 cursor_to(cursor
, pos
);
799 bool view_viewport_down(View
*view
, int n
) {
801 if (view
->end
== text_size(view
->text
))
803 if (n
>= view
->height
) {
804 view
->start
= view
->end
;
806 for (line
= view
->topline
; line
&& n
> 0; line
= line
->next
, n
--)
807 view
->start
+= line
->len
;
813 bool view_viewport_up(View
*view
, int n
) {
814 /* scrolling up is somewhat tricky because we do not yet know where
815 * the lines start, therefore scan backwards but stop at a reasonable
816 * maximum in case we are dealing with a file without any newlines
818 if (view
->start
== 0)
820 size_t max
= view
->width
* view
->height
;
822 Iterator it
= text_iterator_get(view
->text
, view
->start
- 1);
824 if (!text_iterator_byte_get(&it
, &c
))
827 /* skip newlines immediately before display area */
828 if (c
== '\n' && text_iterator_byte_prev(&it
, &c
))
830 if (c
== '\r' && text_iterator_byte_prev(&it
, &c
))
833 if (c
== '\n' && --n
== 0)
837 } while (text_iterator_byte_prev(&it
, &c
));
845 void view_redraw_top(View
*view
) {
846 Line
*line
= view
->cursor
->line
;
847 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
)
848 view
->start
+= cur
->len
;
850 view_cursor_to(view
, view
->cursor
->pos
);
853 void view_redraw_center(View
*view
) {
854 int center
= view
->height
/ 2;
855 size_t pos
= view
->cursor
->pos
;
856 for (int i
= 0; i
< 2; i
++) {
858 Line
*line
= view
->cursor
->line
;
859 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
)
861 if (linenr
< center
) {
862 view_slide_down(view
, center
- linenr
);
865 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
&& linenr
> center
; cur
= cur
->next
) {
866 view
->start
+= cur
->len
;
872 view_cursor_to(view
, pos
);
875 void view_redraw_bottom(View
*view
) {
876 Line
*line
= view
->cursor
->line
;
877 if (line
== view
->lastline
)
879 size_t pos
= view
->cursor
->pos
;
880 view_viewport_up(view
, view
->height
);
881 while (pos
> view
->end
&& view_viewport_down(view
, 1));
882 view_cursor_to(view
, pos
);
885 size_t view_slide_up(View
*view
, int lines
) {
886 Cursor
*cursor
= view
->cursor
;
887 if (view_viewport_down(view
, lines
)) {
888 if (cursor
->line
== view
->topline
)
889 cursor_set(cursor
, view
->topline
, cursor
->col
);
891 view_cursor_to(view
, cursor
->pos
);
893 view_screenline_down(cursor
);
898 size_t view_slide_down(View
*view
, int lines
) {
899 Cursor
*cursor
= view
->cursor
;
900 if (view_viewport_up(view
, lines
)) {
901 if (cursor
->line
== view
->lastline
)
902 cursor_set(cursor
, view
->lastline
, cursor
->col
);
904 view_cursor_to(view
, cursor
->pos
);
906 view_screenline_up(cursor
);
911 size_t view_scroll_up(View
*view
, int lines
) {
912 Cursor
*cursor
= view
->cursor
;
913 if (view_viewport_up(view
, lines
)) {
914 Line
*line
= cursor
->line
< view
->lastline
? cursor
->line
: view
->lastline
;
915 cursor_set(cursor
, line
, view
->cursor
->col
);
917 view_cursor_to(view
, 0);
922 size_t view_scroll_down(View
*view
, int lines
) {
923 Cursor
*cursor
= view
->cursor
;
924 if (view_viewport_down(view
, lines
)) {
925 Line
*line
= cursor
->line
> view
->topline
? cursor
->line
: view
->topline
;
926 cursor_set(cursor
, line
, cursor
->col
);
928 view_cursor_to(view
, text_size(view
->text
));
933 size_t view_line_up(Cursor
*cursor
) {
934 if (cursor
->line
&& cursor
->line
->prev
&& cursor
->line
->prev
->prev
&&
935 cursor
->line
->lineno
!= cursor
->line
->prev
->lineno
&&
936 cursor
->line
->prev
->lineno
!= cursor
->line
->prev
->prev
->lineno
)
937 return view_screenline_up(cursor
);
938 int lastcol
= cursor
->lastcol
;
940 lastcol
= cursor
->col
;
941 view_cursors_to(cursor
, text_line_up(cursor
->view
->text
, cursor
->pos
));
943 cursor_set(cursor
, cursor
->line
, lastcol
);
944 cursor
->lastcol
= lastcol
;
948 size_t view_line_down(Cursor
*cursor
) {
949 if (cursor
->line
&& (!cursor
->line
->next
|| cursor
->line
->next
->lineno
!= cursor
->line
->lineno
))
950 return view_screenline_down(cursor
);
951 int lastcol
= cursor
->lastcol
;
953 lastcol
= cursor
->col
;
954 view_cursors_to(cursor
, text_line_down(cursor
->view
->text
, cursor
->pos
));
956 cursor_set(cursor
, cursor
->line
, lastcol
);
957 cursor
->lastcol
= lastcol
;
961 size_t view_screenline_up(Cursor
*cursor
) {
962 int lastcol
= cursor
->lastcol
;
964 lastcol
= cursor
->col
;
965 if (!cursor
->line
->prev
)
966 view_scroll_up(cursor
->view
, 1);
967 if (cursor
->line
->prev
)
968 cursor_set(cursor
, cursor
->line
->prev
, lastcol
);
969 cursor
->lastcol
= lastcol
;
973 size_t view_screenline_down(Cursor
*cursor
) {
974 int lastcol
= cursor
->lastcol
;
976 lastcol
= cursor
->col
;
977 if (!cursor
->line
->next
&& cursor
->line
== cursor
->view
->bottomline
)
978 view_scroll_down(cursor
->view
, 1);
979 if (cursor
->line
->next
)
980 cursor_set(cursor
, cursor
->line
->next
, lastcol
);
981 cursor
->lastcol
= lastcol
;
985 size_t view_screenline_begin(Cursor
*cursor
) {
988 return cursor_set(cursor
, cursor
->line
, 0);
991 size_t view_screenline_middle(Cursor
*cursor
) {
994 return cursor_set(cursor
, cursor
->line
, cursor
->line
->width
/ 2);
997 size_t view_screenline_end(Cursor
*cursor
) {
1000 int col
= cursor
->line
->width
- 1;
1001 return cursor_set(cursor
, cursor
->line
, col
>= 0 ? col
: 0);
1004 size_t view_cursor_get(View
*view
) {
1005 return view_cursors_pos(view
->cursor
);
1008 const Line
*view_lines_get(View
*view
) {
1009 return view
->topline
;
1012 void view_scroll_to(View
*view
, size_t pos
) {
1013 view_cursors_scroll_to(view
->cursor
, pos
);
1016 const char *view_syntax_get(View
*view
) {
1017 return view
->lexer_name
;
1020 void view_options_set(View
*view
, enum UiOption options
) {
1021 const int mapping
[] = {
1022 [SYNTAX_SYMBOL_SPACE
] = UI_OPTION_SYMBOL_SPACE
,
1023 [SYNTAX_SYMBOL_TAB
] = UI_OPTION_SYMBOL_TAB
,
1024 [SYNTAX_SYMBOL_TAB_FILL
] = UI_OPTION_SYMBOL_TAB_FILL
,
1025 [SYNTAX_SYMBOL_EOL
] = UI_OPTION_SYMBOL_EOL
,
1026 [SYNTAX_SYMBOL_EOF
] = UI_OPTION_SYMBOL_EOF
,
1029 for (int i
= 0; i
< LENGTH(mapping
); i
++) {
1030 view
->symbols
[i
] = (options
& mapping
[i
]) ? &symbols_default
[i
] :
1034 if (options
& UI_OPTION_LINE_NUMBERS_ABSOLUTE
)
1035 options
&= ~UI_OPTION_LARGE_FILE
;
1037 view
->large_file
= (options
& UI_OPTION_LARGE_FILE
);
1040 view
->ui
->options_set(view
->ui
, options
);
1043 enum UiOption
view_options_get(View
*view
) {
1044 return view
->ui
? view
->ui
->options_get(view
->ui
) : 0;
1047 void view_colorcolumn_set(View
*view
, int col
) {
1049 view
->colorcolumn
= col
;
1052 int view_colorcolumn_get(View
*view
) {
1053 return view
->colorcolumn
;
1056 void view_horizon_set(View
*view
, size_t bytes
) {
1057 view
->horizon
= bytes
;
1060 size_t view_horizon_get(View
*view
) {
1061 return view
->horizon
;
1064 size_t view_screenline_goto(View
*view
, int n
) {
1065 size_t pos
= view
->start
;
1066 for (Line
*line
= view
->topline
; --n
> 0 && line
!= view
->lastline
; line
= line
->next
)
1071 static Cursor
*cursors_new(View
*view
, size_t pos
, bool force
) {
1072 Cursor
*c
= calloc(1, sizeof(*c
));
1076 c
->generation
= view
->cursor_generation
;
1077 if (!view
->cursors
) {
1080 view
->cursor_count
= 1;
1084 Cursor
*prev
= NULL
, *next
= NULL
;
1085 size_t cur
= view_cursors_pos(view
->cursor
);
1087 prev
= view
->cursor
;
1088 for (next
= prev
->next
; next
; prev
= next
, next
= next
->next
) {
1089 cur
= view_cursors_pos(next
);
1093 } else if (pos
< cur
) {
1094 next
= view
->cursor
;
1095 for (prev
= next
->prev
; prev
; next
= prev
, prev
= prev
->prev
) {
1096 cur
= view_cursors_pos(prev
);
1102 if (pos
== cur
&& !force
)
1105 for (Cursor
*after
= next
; after
; after
= after
->next
)
1114 c
->number
= prev
->number
+ 1;
1119 view
->cursor_count
++;;
1120 view_cursors_to(c
, pos
);
1127 Cursor
*view_cursors_new(View
*view
, size_t pos
) {
1128 return cursors_new(view
, pos
, false);
1131 Cursor
*view_cursors_new_force(View
*view
, size_t pos
) {
1132 return cursors_new(view
, pos
, true);
1135 int view_cursors_count(View
*view
) {
1136 return view
->cursor_count
;
1139 int view_cursors_number(Cursor
*c
) {
1143 int view_cursors_column_count(View
*view
) {
1144 Text
*txt
= view
->text
;
1145 int cpl_max
= 0, cpl
= 0; /* cursors per line */
1146 size_t line_prev
= 0;
1147 for (Cursor
*c
= view
->cursors
; c
; c
= c
->next
) {
1148 size_t pos
= view_cursors_pos(c
);
1149 size_t line
= text_lineno_by_pos(txt
, pos
);
1150 if (line
== line_prev
)
1161 static Cursor
*cursors_column_next(View
*view
, Cursor
*c
, int column
) {
1162 size_t line_cur
= 0;
1164 Text
*txt
= view
->text
;
1166 size_t pos
= view_cursors_pos(c
);
1167 line_cur
= text_lineno_by_pos(txt
, pos
);
1168 column_cur
= INT_MIN
;
1173 for (; c
; c
= c
->next
) {
1174 size_t pos
= view_cursors_pos(c
);
1175 size_t line
= text_lineno_by_pos(txt
, pos
);
1176 if (line
!= line_cur
) {
1182 if (column
== column_cur
)
1188 Cursor
*view_cursors_column(View
*view
, int column
) {
1189 return cursors_column_next(view
, NULL
, column
);
1192 Cursor
*view_cursors_column_next(Cursor
*c
, int column
) {
1193 return cursors_column_next(c
->view
, c
, column
);
1196 bool view_cursors_multiple(View
*view
) {
1197 return view
->cursors
&& view
->cursors
->next
;
1200 static void view_cursors_free(Cursor
*c
) {
1203 register_release(&c
->reg
);
1204 for (Cursor
*after
= c
->next
; after
; after
= after
->next
)
1207 c
->prev
->next
= c
->next
;
1209 c
->next
->prev
= c
->prev
;
1210 if (c
->view
->cursors
== c
)
1211 c
->view
->cursors
= c
->next
;
1212 if (c
->view
->cursor
== c
)
1213 c
->view
->cursor
= c
->next
? c
->next
: c
->prev
;
1214 c
->view
->cursor_count
--;
1218 bool view_cursors_dispose(Cursor
*c
) {
1221 View
*view
= c
->view
;
1222 if (!view
->cursors
|| !view
->cursors
->next
)
1224 view_selections_free(c
->sel
);
1225 view_cursors_free(c
);
1226 view_cursors_primary_set(view
->cursor
);
1230 Cursor
*view_cursors(View
*view
) {
1231 view
->cursor_generation
++;
1232 return view
->cursors
;
1235 Cursor
*view_cursors_primary_get(View
*view
) {
1236 view
->cursor_generation
++;
1237 return view
->cursor
;
1240 void view_cursors_primary_set(Cursor
*c
) {
1243 View
*view
= c
->view
;
1245 Filerange sel
= view_cursors_selection_get(c
);
1246 view_cursors_to(c
, view_cursors_pos(c
));
1247 view_cursors_selection_set(c
, &sel
);
1250 Cursor
*view_cursors_prev(Cursor
*c
) {
1251 View
*view
= c
->view
;
1252 for (c
= c
->prev
; c
; c
= c
->prev
) {
1253 if (c
->generation
!= view
->cursor_generation
)
1256 view
->cursor_generation
++;
1260 Cursor
*view_cursors_next(Cursor
*c
) {
1261 View
*view
= c
->view
;
1262 for (c
= c
->next
; c
; c
= c
->next
) {
1263 if (c
->generation
!= view
->cursor_generation
)
1266 view
->cursor_generation
++;
1270 size_t view_cursors_pos(Cursor
*c
) {
1271 return text_mark_get(c
->view
->text
, c
->mark
);
1274 size_t view_cursors_line(Cursor
*c
) {
1275 size_t pos
= view_cursors_pos(c
);
1276 return text_lineno_by_pos(c
->view
->text
, pos
);
1279 size_t view_cursors_col(Cursor
*c
) {
1280 size_t pos
= view_cursors_pos(c
);
1281 return text_line_char_get(c
->view
->text
, pos
) + 1;
1284 int view_cursors_cell_get(Cursor
*c
) {
1285 return c
->line
? c
->col
: -1;
1288 int view_cursors_cell_set(Cursor
*c
, int cell
) {
1289 if (!c
->line
|| cell
< 0)
1291 cursor_set(c
, c
->line
, cell
);
1295 Register
*view_cursors_register(Cursor
*c
) {
1299 void view_cursors_scroll_to(Cursor
*c
, size_t pos
) {
1300 View
*view
= c
->view
;
1301 if (view
->cursor
== c
) {
1302 while (pos
< view
->start
&& view_viewport_up(view
, 1));
1303 while (pos
> view
->end
&& view_viewport_down(view
, 1));
1305 view_cursors_to(c
, pos
);
1308 void view_cursors_to(Cursor
*c
, size_t pos
) {
1309 View
*view
= c
->view
;
1310 if (c
->view
->cursor
== c
) {
1311 c
->mark
= text_mark_set(view
->text
, pos
);
1313 size_t max
= text_size(view
->text
);
1314 if (pos
== max
&& view
->end
< max
) {
1315 /* do not display an empty screen when shoviewg the end of the file */
1317 view_viewport_up(view
, view
->height
/ 2);
1319 /* make sure we redraw changes to the very first character of the window */
1320 if (view
->start
== pos
)
1321 view
->start_last
= 0;
1322 /* set the start of the viewable region to the start of the line on which
1323 * the cursor should be placed. if this line requires more space than
1324 * available in the view then simply start displaying text at the new
1325 * cursor position */
1326 for (int i
= 0; i
< 2 && (pos
<= view
->start
|| pos
> view
->end
); i
++) {
1327 view
->start
= i
== 0 ? text_line_begin(view
->text
, pos
) : pos
;
1336 void view_cursors_place(Cursor
*c
, size_t line
, size_t col
) {
1337 Text
*txt
= c
->view
->text
;
1338 size_t pos
= text_pos_by_lineno(txt
, line
);
1339 pos
= text_line_char_set(txt
, pos
, col
);
1340 view_cursors_to(c
, pos
);
1343 void view_cursors_selection_start(Cursor
*c
) {
1346 size_t pos
= view_cursors_pos(c
);
1347 if (pos
== EPOS
|| !(c
->sel
= view_selections_new(c
->view
)))
1349 Text
*txt
= c
->view
->text
;
1350 c
->sel
->anchor
= text_mark_set(txt
, pos
);
1351 c
->sel
->cursor
= c
->sel
->anchor
;
1355 void view_cursors_selection_restore(Cursor
*c
) {
1356 Text
*txt
= c
->view
->text
;
1359 Filerange sel
= text_range_new(
1360 text_mark_get(txt
, c
->lastsel_anchor
),
1361 text_mark_get(txt
, c
->lastsel_cursor
)
1363 if (!text_range_valid(&sel
))
1365 sel
.end
= text_char_next(txt
, sel
.end
);
1366 if (!(c
->sel
= view_selections_new(c
->view
)))
1368 view_selections_set(c
->sel
, &sel
);
1369 view_cursors_selection_sync(c
);
1373 void view_cursors_selection_stop(Cursor
*c
) {
1377 void view_cursors_selection_clear(Cursor
*c
) {
1378 view_selections_free(c
->sel
);
1382 void view_cursors_selection_swap(Cursor
*c
) {
1385 view_selections_swap(c
->sel
);
1386 view_cursors_selection_sync(c
);
1389 void view_cursors_selection_sync(Cursor
*c
) {
1392 Text
*txt
= c
->view
->text
;
1393 size_t cursor
= text_mark_get(txt
, c
->sel
->cursor
);
1394 view_cursors_to(c
, cursor
);
1397 Filerange
view_cursors_selection_get(Cursor
*c
) {
1398 return view_selections_get(c
->sel
);
1401 void view_cursors_selection_set(Cursor
*c
, const Filerange
*r
) {
1402 if (!text_range_valid(r
))
1405 c
->sel
= view_selections_new(c
->view
);
1409 view_selections_set(c
->sel
, r
);
1412 Selection
*view_selections_new(View
*view
) {
1413 Selection
*s
= calloc(1, sizeof(*s
));
1418 s
->next
= view
->selections
;
1419 if (view
->selections
)
1420 view
->selections
->prev
= s
;
1421 view
->selections
= s
;
1425 void view_selections_free(Selection
*s
) {
1429 s
->prev
->next
= s
->next
;
1431 s
->next
->prev
= s
->prev
;
1432 if (s
->view
->selections
== s
)
1433 s
->view
->selections
= s
->next
;
1434 // XXX: add backlink Selection->Cursor?
1435 for (Cursor
*c
= s
->view
->cursors
; c
; c
= c
->next
) {
1437 c
->lastsel_anchor
= s
->anchor
;
1438 c
->lastsel_cursor
= s
->cursor
;
1445 void view_selections_clear(View
*view
) {
1446 while (view
->selections
)
1447 view_selections_free(view
->selections
);
1451 void view_cursors_clear(View
*view
) {
1452 for (Cursor
*c
= view
->cursors
, *next
; c
; c
= next
) {
1454 if (c
!= view
->cursor
) {
1455 view_selections_free(c
->sel
);
1456 view_cursors_free(c
);
1462 void view_selections_swap(Selection
*s
) {
1463 Mark temp
= s
->anchor
;
1464 s
->anchor
= s
->cursor
;
1468 Selection
*view_selections(View
*view
) {
1469 return view
->selections
;
1472 Selection
*view_selections_prev(Selection
*s
) {
1476 Selection
*view_selections_next(Selection
*s
) {
1480 Filerange
view_selection_get(View
*view
) {
1481 return view_selections_get(view
->cursor
->sel
);
1484 Filerange
view_selections_get(Selection
*s
) {
1486 return text_range_empty();
1487 Text
*txt
= s
->view
->text
;
1488 size_t anchor
= text_mark_get(txt
, s
->anchor
);
1489 size_t cursor
= text_mark_get(txt
, s
->cursor
);
1490 Filerange sel
= text_range_new(anchor
, cursor
);
1491 if (text_range_valid(&sel
))
1492 sel
.end
= text_char_next(txt
, sel
.end
);
1496 void view_selections_set(Selection
*s
, const Filerange
*r
) {
1497 if (!text_range_valid(r
))
1499 Text
*txt
= s
->view
->text
;
1500 size_t anchor
= text_mark_get(txt
, s
->anchor
);
1501 size_t cursor
= text_mark_get(txt
, s
->cursor
);
1502 bool left_extending
= anchor
!= EPOS
&& anchor
> cursor
;
1503 size_t end
= r
->end
;
1504 if (r
->start
!= end
)
1505 end
= text_char_prev(txt
, end
);
1506 if (left_extending
) {
1507 s
->anchor
= text_mark_set(txt
, end
);
1508 s
->cursor
= text_mark_set(txt
, r
->start
);
1510 s
->anchor
= text_mark_set(txt
, r
->start
);
1511 s
->cursor
= text_mark_set(txt
, end
);
1516 Text
*view_text(View
*view
) {