2 * Copyright (c) 2014-2015 Marc André Tanner <mat at brain-dump.org>
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25 #include "text-motions.h"
26 #include "text-util.h"
37 SYNTAX_SYMBOL_TAB_FILL
,
44 Mark anchor
; /* position where the selection was created */
45 Mark cursor
; /* other selection endpoint where it changes */
46 View
*view
; /* associated view to which this selection belongs */
47 Selection
*prev
, *next
; /* previsous/next selections in no particular order */
50 struct Cursor
{ /* cursor position */
51 Filepos pos
; /* in bytes from the start of the file */
52 int row
, col
; /* in terms of zero based screen coordinates */
53 int lastcol
; /* remembered column used when moving across lines */
54 Line
*line
; /* screen line on which cursor currently resides */
55 Mark mark
; /* mark used to keep track of current cursor position */
56 Selection
*sel
; /* selection (if any) which folows the cursor upon movement */
57 Mark lastsel_anchor
;/* previously used selection data, */
58 Mark lastsel_cursor
;/* used to restore it */
59 Register reg
; /* per cursor register to support yank/put operation */
60 View
*view
; /* associated view to which this cursor belongs */
61 Cursor
*prev
, *next
;/* previous/next cursors in no particular order */
64 /* Viewable area, showing part of a file. Keeps track of cursors and selections.
65 * At all times there exists at least one cursor, which is placed in the visible viewport.
66 * Additional cursors can be created and positioned anywhere in the file. */
68 Text
*text
; /* underlying text management */
70 int width
, height
; /* size of display area */
71 Filepos start
, end
; /* currently displayed area [start, end] in bytes from the start of the file */
72 Filepos start_last
; /* previously used start of visible area, used to update the mark */
73 Mark start_mark
; /* mark to keep track of the start of the visible area */
74 size_t lines_size
; /* number of allocated bytes for lines (grows only) */
75 Line
*lines
; /* view->height number of lines representing view content */
76 Line
*topline
; /* top of the view, first line currently shown */
77 Line
*lastline
; /* last currently used line, always <= bottomline */
78 Line
*bottomline
; /* bottom of view, might be unused if lastline < bottomline */
79 Cursor
*cursor
; /* main cursor, always placed within the visible viewport */
80 Line
*line
; /* used while drawing view content, line where next char will be drawn */
81 int col
; /* used while drawing view content, column where next char will be drawn */
82 const SyntaxSymbol
*symbols
[SYNTAX_SYMBOL_LAST
]; /* symbols to use for white spaces etc */
83 int tabwidth
; /* how many spaces should be used to display a tab character */
84 Cursor
*cursors
; /* all cursors currently active */
85 Selection
*selections
; /* all selected regions */
86 lua_State
*lua
; /* lua state used for syntax highlighting */
88 bool need_update
; /* whether view has been redrawn */
89 bool large_file
; /* optimize for displaying large files */
93 static const SyntaxSymbol symbols_none
[] = {
94 [SYNTAX_SYMBOL_SPACE
] = { " " },
95 [SYNTAX_SYMBOL_TAB
] = { " " },
96 [SYNTAX_SYMBOL_TAB_FILL
] = { " " },
97 [SYNTAX_SYMBOL_EOL
] = { " " },
98 [SYNTAX_SYMBOL_EOF
] = { "~" },
101 static const SyntaxSymbol symbols_default
[] = {
102 [SYNTAX_SYMBOL_SPACE
] = { "\xC2\xB7" },
103 [SYNTAX_SYMBOL_TAB
] = { "\xE2\x96\xB6" },
104 [SYNTAX_SYMBOL_TAB_FILL
] = { " " },
105 [SYNTAX_SYMBOL_EOL
] = { "\xE2\x8F\x8E" },
106 [SYNTAX_SYMBOL_EOF
] = { "~" },
109 static Cell cell_unused
;
110 static Cell cell_blank
= { .data
= " " };
112 static void view_clear(View
*view
);
113 static bool view_addch(View
*view
, Cell
*cell
);
114 static bool view_coord_get(View
*view
, size_t pos
, Line
**retline
, int *retrow
, int *retcol
);
115 static void view_cursors_free(Cursor
*c
);
116 /* set/move current cursor position to a given (line, column) pair */
117 static size_t cursor_set(Cursor
*cursor
, Line
*line
, int col
);
121 static void view_syntax_color(View
*view
) { }
122 bool view_syntax_set(View
*view
, const char *name
) { return false; }
126 static void view_syntax_color(View
*view
) {
127 lua_State
*L
= view
->lua
;
128 if (!L
|| !view
->lexer_name
)
130 lua_getglobal(L
, "vis");
131 lua_getfield(L
, -1, "lexers");
132 if (lua_isnil(L
, -1))
135 /* maximal number of bytes to consider for syntax highlighting before
136 * the visible area */
137 const size_t lexer_before_max
= 16384;
138 /* absolute position to start syntax highlighting */
139 const size_t lexer_start
= view
->start
>= lexer_before_max
? view
->start
- lexer_before_max
: 0;
140 /* number of bytes used for syntax highlighting before visible are */
141 const size_t lexer_before
= view
->start
- lexer_start
;
142 /* number of bytes to read in one go */
143 const size_t text_size
= lexer_before
+ (view
->end
- view
->start
);
144 /* current buffer to work with */
145 char text
[text_size
+1];
146 /* bytes to process */
147 const size_t text_len
= text_bytes_get(view
->text
, lexer_start
, text_size
, text
);
148 /* NUL terminate text section */
149 text
[text_len
] = '\0';
151 lua_getfield(L
, -1, "load");
152 lua_pushstring(L
, view
->lexer_name
);
153 lua_pcall(L
, 1, 1, 0);
155 lua_getfield(L
, -1, "_TOKENSTYLES");
156 lua_getfield(L
, -2, "lex");
158 lua_pushvalue(L
, -3); /* lexer obj */
160 const char *lex_text
= text
;
161 if (lexer_start
> 0) {
162 /* try to start lexing at a line boundry */
163 /* TODO: start at known state, handle nested lexers */
164 const char *newline
= memchr(text
, '\n', lexer_before
);
169 lua_pushlstring(L
, lex_text
, text_len
- (lex_text
- text
));
170 lua_pushinteger(L
, 1 /* inital style: whitespace */);
174 if (lua_isfunction(L
, -4) && !lua_pcall(L
, 3, 1, 0) && lua_istable(L
, -1) &&
175 (token_count
= lua_objlen(L
, -1)) > 0) {
177 size_t pos
= lexer_before
- (lex_text
- text
);
178 Line
*line
= view
->topline
;
181 for (int i
= 1; i
< token_count
; i
+= 2) {
182 lua_rawgeti(L
, -1, i
);
183 //const char *name = lua_tostring(L, -1);
184 lua_gettable(L
, -3); /* _TOKENSTYLES[token] */
185 size_t token_style
= lua_tointeger(L
, -1);
186 lua_pop(L
, 1); /* style */
187 lua_rawgeti(L
, -1, i
+ 1);
188 size_t token_end
= lua_tointeger(L
, -1) - 1;
189 lua_pop(L
, 1); /* pos */
191 for (bool token_next
= false; line
; line
= line
->next
, col
= 0) {
192 for (; col
< line
->width
; col
++) {
193 if (pos
< token_end
) {
194 line
->cells
[col
].attr
= token_style
;
195 pos
+= line
->cells
[col
].len
;
208 lua_pop(L
, 3); /* _TOKENSTYLES, language specific lexer, lexers global */
211 bool view_syntax_set(View
*view
, const char *name
) {
213 free(view
->lexer_name
);
214 view
->lexer_name
= NULL
;
218 lua_State
*L
= view
->lua
;
222 /* Try to load the specified lexer and parse its token styles.
223 * Roughly equivalent to the following lua code:
225 * lang = vis.lexers.load(name)
226 * for token_name, id in pairs(lang._TOKENSTYLES) do
227 * ui->syntax_style(id, vis.lexers:get_style(lang, token_name);
229 lua_getglobal(L
, "vis");
230 lua_getfield(L
, -1, "lexers");
232 lua_getfield(L
, -1, "STYLE_DEFAULT");
233 view
->ui
->syntax_style(view
->ui
, UI_STYLE_DEFAULT
, lua_tostring(L
, -1));
235 lua_getfield(L
, -1, "STYLE_CURSOR");
236 view
->ui
->syntax_style(view
->ui
, UI_STYLE_CURSOR
, lua_tostring(L
, -1));
238 lua_getfield(L
, -1, "STYLE_CURSOR_LINE");
239 view
->ui
->syntax_style(view
->ui
, UI_STYLE_CURSOR_LINE
, lua_tostring(L
, -1));
241 lua_getfield(L
, -1, "STYLE_SELECTION");
242 view
->ui
->syntax_style(view
->ui
, UI_STYLE_SELECTION
, lua_tostring(L
, -1));
244 lua_getfield(L
, -1, "STYLE_LINENUMBER");
245 view
->ui
->syntax_style(view
->ui
, UI_STYLE_LINENUMBER
, lua_tostring(L
, -1));
247 lua_getfield(L
, -1, "STYLE_COLOR_COLUMN");
248 view
->ui
->syntax_style(view
->ui
, UI_STYLE_COLOR_COLUMN
, lua_tostring(L
, -1));
251 lua_getfield(L
, -1, "load");
252 lua_pushstring(L
, name
);
254 if (lua_pcall(L
, 1, 1, 0))
257 if (!lua_istable(L
, -1)) {
262 view
->lexer_name
= strdup(name
);
263 /* loop through all _TOKENSTYLES and parse them */
264 lua_getfield(L
, -1, "_TOKENSTYLES");
265 lua_pushnil(L
); /* first key */
267 while (lua_next(L
, -2)) {
268 size_t id
= lua_tointeger(L
, -1);
269 //const char *name = lua_tostring(L, -2);
270 lua_pop(L
, 1); /* remove value (=id), keep key (=name) */
271 lua_getfield(L
, -4, "get_style");
272 lua_pushvalue(L
, -5); /* lexer */
273 lua_pushvalue(L
, -5); /* lang */
274 lua_pushvalue(L
, -4); /* token_name */
275 if (lua_pcall(L
, 3, 1, 0))
277 const char *style
= lua_tostring(L
, -1);
278 //printf("%s\t%d\t%s\n", name, id, style);
279 view
->ui
->syntax_style(view
->ui
, id
, style
);
280 lua_pop(L
, 1); /* style */
283 lua_pop(L
, 4); /* _TOKENSTYLES, grammar, lexers, vis */
291 void view_tabwidth_set(View
*view
, int tabwidth
) {
292 view
->tabwidth
= tabwidth
;
296 /* reset internal view data structures (cell matrix, line offsets etc.) */
297 static void view_clear(View
*view
) {
298 memset(view
->lines
, 0, view
->lines_size
);
299 if (view
->start
!= view
->start_last
) {
300 view
->start_mark
= text_mark_set(view
->text
, view
->start
);
302 size_t start
= text_mark_get(view
->text
, view
->start_mark
);
307 view
->start_last
= view
->start
;
308 view
->topline
= view
->lines
;
309 view
->topline
->lineno
= view
->large_file
? 1 : text_lineno_by_pos(view
->text
, view
->start
);
310 view
->lastline
= view
->topline
;
312 size_t line_size
= sizeof(Line
) + view
->width
*sizeof(Cell
);
313 size_t end
= view
->height
* line_size
;
315 for (size_t i
= 0; i
< end
; i
+= line_size
) {
316 Line
*line
= (Line
*)(((char*)view
->lines
) + i
);
322 view
->bottomline
= prev
? prev
: view
->topline
;
323 view
->bottomline
->next
= NULL
;
324 view
->line
= view
->topline
;
328 Filerange
view_viewport_get(View
*view
) {
329 return (Filerange
){ .start
= view
->start
, .end
= view
->end
};
332 /* try to add another character to the view, return whether there was space left */
333 static bool view_addch(View
*view
, Cell
*cell
) {
338 size_t lineno
= view
->line
->lineno
;
339 unsigned char ch
= (unsigned char)cell
->data
[0];
344 width
= view
->tabwidth
- (view
->col
% view
->tabwidth
);
345 for (int w
= 0; w
< width
; w
++) {
346 if (view
->col
+ 1 > view
->width
) {
347 view
->line
= view
->line
->next
;
351 view
->line
->lineno
= lineno
;
354 cell
->len
= w
== 0 ? 1 : 0;
355 int t
= w
== 0 ? SYNTAX_SYMBOL_TAB
: SYNTAX_SYMBOL_TAB_FILL
;
356 strncpy(cell
->data
, view
->symbols
[t
]->symbol
, sizeof(cell
->data
)-1);
357 cell
->attr
= view
->symbols
[t
]->style
;
358 view
->line
->cells
[view
->col
] = *cell
;
359 view
->line
->len
+= cell
->len
;
360 view
->line
->width
+= cell
->width
;
367 if (view
->col
+ cell
->width
> view
->width
) {
368 view
->line
= view
->line
->next
;
372 view
->line
->lineno
= lineno
;
375 strncpy(cell
->data
, view
->symbols
[SYNTAX_SYMBOL_EOL
]->symbol
, sizeof(cell
->data
)-1);
376 cell
->attr
= view
->symbols
[SYNTAX_SYMBOL_EOL
]->style
;
378 view
->line
->cells
[view
->col
] = *cell
;
379 view
->line
->len
+= cell
->len
;
380 view
->line
->width
+= cell
->width
;
381 for (int i
= view
->col
+ 1; i
< view
->width
; i
++)
382 view
->line
->cells
[i
] = cell_blank
;
384 view
->line
= view
->line
->next
;
386 view
->line
->lineno
= lineno
+ 1;
390 if (ch
< 128 && !isprint(ch
)) {
391 /* non-printable ascii char, represent it as ^(char + 64) */
393 .data
= { '^', ch
== 127 ? '?' : ch
+ 64, '\0' },
401 strncpy(cell
->data
, view
->symbols
[SYNTAX_SYMBOL_SPACE
]->symbol
, sizeof(cell
->data
)-1);
402 cell
->attr
= view
->symbols
[SYNTAX_SYMBOL_SPACE
]->style
;
406 if (view
->col
+ cell
->width
> view
->width
) {
407 for (int i
= view
->col
; i
< view
->width
; i
++)
408 view
->line
->cells
[i
] = cell_blank
;
409 view
->line
= view
->line
->next
;
414 view
->line
->width
+= cell
->width
;
415 view
->line
->len
+= cell
->len
;
416 view
->line
->lineno
= lineno
;
417 view
->line
->cells
[view
->col
] = *cell
;
419 /* set cells of a character which uses multiple columns */
420 for (int i
= 1; i
< cell
->width
; i
++)
421 view
->line
->cells
[view
->col
++] = cell_unused
;
428 CursorPos
view_cursor_getpos(View
*view
) {
429 Cursor
*cursor
= view
->cursor
;
430 Line
*line
= cursor
->line
;
431 CursorPos pos
= { .line
= line
->lineno
, .col
= cursor
->col
};
432 while (line
->prev
&& line
->prev
->lineno
== pos
.line
) {
434 pos
.col
+= line
->width
;
440 static void cursor_to(Cursor
*c
, size_t pos
) {
441 Text
*txt
= c
->view
->text
;
442 c
->mark
= text_mark_set(txt
, pos
);
447 size_t anchor
= text_mark_get(txt
, c
->sel
->anchor
);
448 size_t cursor
= text_mark_get(txt
, c
->sel
->cursor
);
449 /* do we have to change the orientation of the selection? */
450 if (pos
< anchor
&& anchor
< cursor
) {
451 /* right extend -> left extend */
452 anchor
= text_char_next(txt
, anchor
);
453 c
->sel
->anchor
= text_mark_set(txt
, anchor
);
454 } else if (cursor
< anchor
&& anchor
<= pos
) {
455 /* left extend -> right extend */
456 anchor
= text_char_prev(txt
, anchor
);
457 c
->sel
->anchor
= text_mark_set(txt
, anchor
);
460 pos
= text_char_next(txt
, pos
);
461 c
->sel
->cursor
= text_mark_set(txt
, pos
);
463 if (!view_coord_get(c
->view
, pos
, &c
->line
, &c
->row
, &c
->col
)) {
464 if (c
->view
->cursor
== c
) {
465 c
->line
= c
->view
->topline
;
471 // TODO: minimize number of redraws
475 static bool view_coord_get(View
*view
, size_t pos
, Line
**retline
, int *retrow
, int *retcol
) {
476 int row
= 0, col
= 0;
477 size_t cur
= view
->start
;
478 Line
*line
= view
->topline
;
480 if (pos
< view
->start
|| pos
> view
->end
) {
481 if (retline
) *retline
= NULL
;
482 if (retrow
) *retrow
= -1;
483 if (retcol
) *retcol
= -1;
487 while (line
&& line
!= view
->lastline
&& cur
< pos
) {
488 if (cur
+ line
->len
> pos
)
496 int max_col
= MIN(view
->width
, line
->width
);
497 while (cur
< pos
&& col
< max_col
) {
498 cur
+= line
->cells
[col
].len
;
499 /* skip over columns occupied by the same character */
500 while (++col
< max_col
&& line
->cells
[col
].len
== 0);
503 line
= view
->bottomline
;
504 row
= view
->height
- 1;
507 if (retline
) *retline
= line
;
508 if (retrow
) *retrow
= row
;
509 if (retcol
) *retcol
= col
;
513 /* move the cursor to the character at pos bytes from the begining of the file.
514 * if pos is not in the current viewport, redraw the view to make it visible */
515 void view_cursor_to(View
*view
, size_t pos
) {
516 view_cursors_to(view
->cursor
, pos
);
519 /* redraw the complete with data starting from view->start bytes into the file.
520 * stop once the screen is full, update view->end, view->lastline */
521 void view_draw(View
*view
) {
523 /* read a screenful of text */
524 const size_t text_size
= view
->width
* view
->height
;
525 /* current buffer to work with */
526 char text
[text_size
+1];
527 /* remaining bytes to process in buffer */
528 size_t rem
= text_bytes_get(view
->text
, view
->start
, text_size
, text
);
529 /* NUL terminate text section */
531 /* absolute position of character currently being added to display */
532 size_t pos
= view
->start
;
533 /* current position into buffer from which to interpret a character */
535 /* start from known multibyte state */
536 mbstate_t mbstate
= { 0 };
538 Cell cell
= { 0 }, prev_cell
= { 0 };
542 /* current 'parsed' character' */
545 size_t len
= mbrtowc(&wchar
, cur
, rem
, &mbstate
);
546 if (len
== (size_t)-1 && errno
== EILSEQ
) {
547 /* ok, we encountered an invalid multibyte sequence,
548 * replace it with the Unicode Replacement Character
549 * (FFFD) and skip until the start of the next utf8 char */
550 for (len
= 1; rem
> len
&& !ISUTF8(cur
[len
]); len
++);
551 cell
= (Cell
){ .data
= "\xEF\xBF\xBD", .len
= len
, .width
= 1 };
552 } else if (len
== (size_t)-2) {
553 /* not enough bytes available to convert to a
554 * wide character. advance file position and read
555 * another junk into buffer.
557 rem
= text_bytes_get(view
->text
, pos
, text_size
, text
);
561 } else if (len
== 0) {
562 /* NUL byte encountered, store it and continue */
563 cell
= (Cell
){ .data
= "\x00", .len
= 1, .width
= 2 };
565 for (size_t i
= 0; i
< len
; i
++)
566 cell
.data
[i
] = cur
[i
];
567 cell
.data
[len
] = '\0';
569 cell
.width
= wcwidth(wchar
);
570 if (cell
.width
== -1)
574 if (cur
[0] == '\r' && rem
> 1 && cur
[1] == '\n') {
575 /* convert views style newline \r\n into a single char with len = 2 */
576 cell
= (Cell
){ .data
= "\n", .len
= 2, .width
= 1 };
579 if (cell
.width
== 0 && prev_cell
.len
+ cell
.len
< sizeof(cell
.len
)) {
580 prev_cell
.len
+= cell
.len
;
581 strcat(prev_cell
.data
, cell
.data
);
583 if (prev_cell
.len
&& !view_addch(view
, &prev_cell
))
585 pos
+= prev_cell
.len
;
592 memset(&cell
, 0, sizeof cell
);
595 if (prev_cell
.len
&& view_addch(view
, &prev_cell
))
596 pos
+= prev_cell
.len
;
598 /* set end of vieviewg region */
600 view
->lastline
= view
->line
? view
->line
: view
->bottomline
;
602 /* clear remaining of line, important to show cursor at end of file */
604 for (int x
= view
->col
; x
< view
->width
; x
++)
605 view
->line
->cells
[x
] = cell_blank
;
608 /* resync position of cursors within visible area */
609 for (Cursor
*c
= view
->cursors
; c
; c
= c
->next
) {
610 size_t pos
= view_cursors_pos(c
);
611 if (view_coord_get(view
, pos
, &c
->line
, &c
->row
, &c
->col
)) {
612 c
->line
->cells
[c
->col
].cursor
= true;
613 if (view
->ui
&& !c
->sel
) {
614 Line
*line_match
; int col_match
;
615 size_t pos_match
= text_bracket_match_symbol(view
->text
, pos
, "(){}[]\"'`");
616 if (pos
!= pos_match
&& view_coord_get(view
, pos_match
, &line_match
, NULL
, &col_match
)) {
617 line_match
->cells
[col_match
].selected
= true;
620 } else if (c
== view
->cursor
) {
621 c
->line
= view
->topline
;
627 view
->need_update
= true;
630 void view_update(View
*view
) {
631 if (!view
->need_update
)
634 view_syntax_color(view
);
636 if (view
->colorcolumn
> 0) {
638 int line_cols
= 0; /* Track the number of columns we've passed on each line */
639 bool line_cc_set
= false; /* Has the colorcolumn attribute been set for this line yet */
641 for (Line
*l
= view
->topline
; l
; l
= l
->next
) {
642 if (l
->lineno
!= lineno
) {
648 line_cols
+= view
->width
;
650 /* This screen line contains the cell we want to highlight */
651 if (line_cols
>= view
->colorcolumn
) {
652 l
->cells
[(view
->colorcolumn
- 1) % view
->width
].attr
= UI_STYLE_COLOR_COLUMN
;
661 for (Line
*l
= view
->lastline
->next
; l
; l
= l
->next
) {
662 strncpy(l
->cells
[0].data
, view
->symbols
[SYNTAX_SYMBOL_EOF
]->symbol
, sizeof(l
->cells
[0].data
));
663 l
->cells
[0].attr
= view
->symbols
[SYNTAX_SYMBOL_EOF
]->style
;
664 for (int x
= 1; x
< view
->width
; x
++)
665 l
->cells
[x
] = cell_blank
;
670 for (Selection
*s
= view
->selections
; s
; s
= s
->next
) {
671 Filerange sel
= view_selections_get(s
);
672 if (text_range_valid(&sel
)) {
673 Line
*start_line
; int start_col
;
674 Line
*end_line
; int end_col
;
675 view_coord_get(view
, sel
.start
, &start_line
, NULL
, &start_col
);
676 view_coord_get(view
, sel
.end
, &end_line
, NULL
, &end_col
);
677 if (start_line
|| end_line
) {
679 start_line
= view
->topline
;
683 end_line
= view
->lastline
;
684 end_col
= end_line
->width
;
686 for (Line
*l
= start_line
; l
!= end_line
->next
; l
= l
->next
) {
687 int col
= (l
== start_line
) ? start_col
: 0;
688 int end
= (l
== end_line
) ? end_col
: l
->width
;
690 l
->cells
[col
++].selected
= true;
698 view
->ui
->draw(view
->ui
);
699 view
->need_update
= false;
702 bool view_resize(View
*view
, int width
, int height
) {
707 size_t lines_size
= height
*(sizeof(Line
) + width
*sizeof(Cell
));
708 if (lines_size
> view
->lines_size
) {
709 Line
*lines
= realloc(view
->lines
, lines_size
);
713 view
->lines_size
= lines_size
;
716 view
->height
= height
;
717 memset(view
->lines
, 0, view
->lines_size
);
722 int view_height_get(View
*view
) {
726 int view_width_get(View
*view
) {
730 void view_free(View
*view
) {
733 while (view
->cursors
)
734 view_cursors_free(view
->cursors
);
735 while (view
->selections
)
736 view_selections_free(view
->selections
);
738 free(view
->lexer_name
);
742 void view_reload(View
*view
, Text
*text
) {
744 view_selections_clear(view
);
745 view_cursor_to(view
, 0);
748 View
*view_new(Text
*text
, lua_State
*lua
) {
751 View
*view
= calloc(1, sizeof(View
));
754 if (!view_cursors_new(view
)) {
762 view_options_set(view
, 0);
764 if (!view_resize(view
, 1, 1)) {
769 view_cursor_to(view
, 0);
774 void view_ui(View
*view
, UiWin
* ui
) {
778 static size_t cursor_set(Cursor
*cursor
, Line
*line
, int col
) {
780 View
*view
= cursor
->view
;
781 size_t pos
= view
->start
;
782 /* get row number and file offset at start of the given line */
783 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
) {
788 /* for characters which use more than 1 column, make sure we are on the left most */
789 while (col
> 0 && line
->cells
[col
].len
== 0)
791 /* calculate offset within the line */
792 for (int i
= 0; i
< col
; i
++)
793 pos
+= line
->cells
[i
].len
;
799 cursor_to(cursor
, pos
);
804 bool view_viewport_down(View
*view
, int n
) {
806 if (view
->end
== text_size(view
->text
))
808 if (n
>= view
->height
) {
809 view
->start
= view
->end
;
811 for (line
= view
->topline
; line
&& n
> 0; line
= line
->next
, n
--)
812 view
->start
+= line
->len
;
818 bool view_viewport_up(View
*view
, int n
) {
819 /* scrolling up is somewhat tricky because we do not yet know where
820 * the lines start, therefore scan backwards but stop at a reasonable
821 * maximum in case we are dealing with a file without any newlines
823 if (view
->start
== 0)
825 size_t max
= view
->width
* view
->height
;
827 Iterator it
= text_iterator_get(view
->text
, view
->start
- 1);
829 if (!text_iterator_byte_get(&it
, &c
))
832 /* skip newlines immediately before display area */
833 if (c
== '\n' && text_iterator_byte_prev(&it
, &c
))
835 if (c
== '\r' && text_iterator_byte_prev(&it
, &c
))
838 if (c
== '\n' && --n
== 0)
842 } while (text_iterator_byte_prev(&it
, &c
));
850 void view_redraw_top(View
*view
) {
851 Line
*line
= view
->cursor
->line
;
852 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
)
853 view
->start
+= cur
->len
;
855 view_cursor_to(view
, view
->cursor
->pos
);
858 void view_redraw_center(View
*view
) {
859 int center
= view
->height
/ 2;
860 size_t pos
= view
->cursor
->pos
;
861 for (int i
= 0; i
< 2; i
++) {
863 Line
*line
= view
->cursor
->line
;
864 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
)
866 if (linenr
< center
) {
867 view_slide_down(view
, center
- linenr
);
870 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
&& linenr
> center
; cur
= cur
->next
) {
871 view
->start
+= cur
->len
;
877 view_cursor_to(view
, pos
);
880 void view_redraw_bottom(View
*view
) {
881 Line
*line
= view
->cursor
->line
;
882 if (line
== view
->lastline
)
884 size_t pos
= view
->cursor
->pos
;
885 view_viewport_up(view
, view
->height
);
886 while (pos
> view
->end
&& view_viewport_down(view
, 1));
887 view_cursor_to(view
, pos
);
890 size_t view_slide_up(View
*view
, int lines
) {
891 Cursor
*cursor
= view
->cursor
;
892 if (view_viewport_down(view
, lines
)) {
893 if (cursor
->line
== view
->topline
)
894 cursor_set(cursor
, view
->topline
, cursor
->col
);
896 view_cursor_to(view
, cursor
->pos
);
898 view_screenline_down(cursor
);
903 size_t view_slide_down(View
*view
, int lines
) {
904 Cursor
*cursor
= view
->cursor
;
905 if (view_viewport_up(view
, lines
)) {
906 if (cursor
->line
== view
->lastline
)
907 cursor_set(cursor
, view
->lastline
, cursor
->col
);
909 view_cursor_to(view
, cursor
->pos
);
911 view_screenline_up(cursor
);
916 size_t view_scroll_up(View
*view
, int lines
) {
917 Cursor
*cursor
= view
->cursor
;
918 if (view_viewport_up(view
, lines
)) {
919 Line
*line
= cursor
->line
< view
->lastline
? cursor
->line
: view
->lastline
;
920 cursor_set(cursor
, line
, view
->cursor
->col
);
922 view_cursor_to(view
, 0);
927 size_t view_scroll_down(View
*view
, int lines
) {
928 Cursor
*cursor
= view
->cursor
;
929 if (view_viewport_down(view
, lines
)) {
930 Line
*line
= cursor
->line
> view
->topline
? cursor
->line
: view
->topline
;
931 cursor_set(cursor
, line
, cursor
->col
);
933 view_cursor_to(view
, text_size(view
->text
));
938 size_t view_line_up(Cursor
*cursor
) {
939 if (cursor
->line
&& cursor
->line
->prev
&& cursor
->line
->prev
->prev
&&
940 cursor
->line
->lineno
!= cursor
->line
->prev
->lineno
&&
941 cursor
->line
->prev
->lineno
!= cursor
->line
->prev
->prev
->lineno
)
942 return view_screenline_up(cursor
);
943 int lastcol
= cursor
->lastcol
;
945 lastcol
= cursor
->col
;
946 view_cursors_to(cursor
, text_line_up(cursor
->view
->text
, cursor
->pos
));
948 cursor_set(cursor
, cursor
->line
, lastcol
);
949 cursor
->lastcol
= lastcol
;
953 size_t view_line_down(Cursor
*cursor
) {
954 if (cursor
->line
&& (!cursor
->line
->next
|| cursor
->line
->next
->lineno
!= cursor
->line
->lineno
))
955 return view_screenline_down(cursor
);
956 int lastcol
= cursor
->lastcol
;
958 lastcol
= cursor
->col
;
959 view_cursors_to(cursor
, text_line_down(cursor
->view
->text
, cursor
->pos
));
961 cursor_set(cursor
, cursor
->line
, lastcol
);
962 cursor
->lastcol
= lastcol
;
966 size_t view_screenline_up(Cursor
*cursor
) {
967 int lastcol
= cursor
->lastcol
;
969 lastcol
= cursor
->col
;
970 if (!cursor
->line
->prev
)
971 view_scroll_up(cursor
->view
, 1);
972 if (cursor
->line
->prev
)
973 cursor_set(cursor
, cursor
->line
->prev
, lastcol
);
974 cursor
->lastcol
= lastcol
;
978 size_t view_screenline_down(Cursor
*cursor
) {
979 int lastcol
= cursor
->lastcol
;
981 lastcol
= cursor
->col
;
982 if (!cursor
->line
->next
&& cursor
->line
== cursor
->view
->bottomline
)
983 view_scroll_down(cursor
->view
, 1);
984 if (cursor
->line
->next
)
985 cursor_set(cursor
, cursor
->line
->next
, lastcol
);
986 cursor
->lastcol
= lastcol
;
990 size_t view_screenline_begin(Cursor
*cursor
) {
993 return cursor_set(cursor
, cursor
->line
, 0);
996 size_t view_screenline_middle(Cursor
*cursor
) {
999 return cursor_set(cursor
, cursor
->line
, cursor
->line
->width
/ 2);
1002 size_t view_screenline_end(Cursor
*cursor
) {
1005 int col
= cursor
->line
->width
- 1;
1006 return cursor_set(cursor
, cursor
->line
, col
>= 0 ? col
: 0);
1009 size_t view_cursor_get(View
*view
) {
1010 return view_cursors_pos(view
->cursor
);
1013 const Line
*view_lines_get(View
*view
) {
1014 return view
->topline
;
1017 void view_scroll_to(View
*view
, size_t pos
) {
1018 view_cursors_scroll_to(view
->cursor
, pos
);
1021 const char *view_syntax_get(View
*view
) {
1022 return view
->lexer_name
;
1025 void view_options_set(View
*view
, enum UiOption options
) {
1026 const int mapping
[] = {
1027 [SYNTAX_SYMBOL_SPACE
] = UI_OPTION_SYMBOL_SPACE
,
1028 [SYNTAX_SYMBOL_TAB
] = UI_OPTION_SYMBOL_TAB
,
1029 [SYNTAX_SYMBOL_TAB_FILL
] = UI_OPTION_SYMBOL_TAB_FILL
,
1030 [SYNTAX_SYMBOL_EOL
] = UI_OPTION_SYMBOL_EOL
,
1031 [SYNTAX_SYMBOL_EOF
] = UI_OPTION_SYMBOL_EOF
,
1034 for (int i
= 0; i
< LENGTH(mapping
); i
++) {
1035 view
->symbols
[i
] = (options
& mapping
[i
]) ? &symbols_default
[i
] :
1039 if (options
& UI_OPTION_LINE_NUMBERS_ABSOLUTE
)
1040 options
&= ~UI_OPTION_LARGE_FILE
;
1042 view
->large_file
= (options
& UI_OPTION_LARGE_FILE
);
1045 view
->ui
->options_set(view
->ui
, options
);
1048 enum UiOption
view_options_get(View
*view
) {
1049 return view
->ui
? view
->ui
->options_get(view
->ui
) : 0;
1052 void view_colorcolumn_set(View
*view
, int col
) {
1054 view
->colorcolumn
= col
;
1057 int view_colorcolumn_get(View
*view
) {
1058 return view
->colorcolumn
;
1061 size_t view_screenline_goto(View
*view
, int n
) {
1062 size_t pos
= view
->start
;
1063 for (Line
*line
= view
->topline
; --n
> 0 && line
!= view
->lastline
; line
= line
->next
)
1068 Cursor
*view_cursors_new(View
*view
) {
1069 Cursor
*c
= calloc(1, sizeof(*c
));
1074 c
->next
= view
->cursors
;
1076 view
->cursors
->prev
= c
;
1082 int view_cursors_count(View
*view
) {
1084 for (Cursor
*c
= view_cursors(view
); c
; c
= view_cursors_next(c
))
1089 static void view_cursors_free(Cursor
*c
) {
1092 register_release(&c
->reg
);
1094 c
->prev
->next
= c
->next
;
1096 c
->next
->prev
= c
->prev
;
1097 if (c
->view
->cursors
== c
)
1098 c
->view
->cursors
= c
->next
;
1099 if (c
->view
->cursor
== c
)
1100 c
->view
->cursor
= c
->next
? c
->next
: c
->prev
;
1104 void view_cursors_dispose(Cursor
*c
) {
1107 View
*view
= c
->view
;
1108 if (view
->cursors
&& view
->cursors
->next
) {
1109 view_selections_free(c
->sel
);
1110 view_cursors_free(c
);
1115 Cursor
*view_cursors(View
*view
) {
1116 return view
->cursors
;
1119 Cursor
*view_cursor(View
*view
) {
1120 return view
->cursor
;
1123 Cursor
*view_cursors_prev(Cursor
*c
) {
1127 Cursor
*view_cursors_next(Cursor
*c
) {
1131 size_t view_cursors_pos(Cursor
*c
) {
1132 return text_mark_get(c
->view
->text
, c
->mark
);
1135 int view_cursors_cell_get(Cursor
*c
) {
1136 return c
->line
? c
->col
: -1;
1139 int view_cursors_cell_set(Cursor
*c
, int cell
) {
1140 if (!c
->line
|| cell
< 0)
1142 cursor_set(c
, c
->line
, cell
);
1146 Register
*view_cursors_register(Cursor
*c
) {
1150 void view_cursors_scroll_to(Cursor
*c
, size_t pos
) {
1151 View
*view
= c
->view
;
1152 if (view
->cursor
== c
) {
1153 while (pos
< view
->start
&& view_viewport_up(view
, 1));
1154 while (pos
> view
->end
&& view_viewport_down(view
, 1));
1156 view_cursors_to(c
, pos
);
1159 void view_cursors_to(Cursor
*c
, size_t pos
) {
1160 View
*view
= c
->view
;
1161 if (c
->view
->cursors
== c
) {
1162 c
->mark
= text_mark_set(view
->text
, pos
);
1164 size_t max
= text_size(view
->text
);
1165 if (pos
== max
&& view
->end
< max
) {
1166 /* do not display an empty screen when shoviewg the end of the file */
1168 view_viewport_up(view
, view
->height
/ 2);
1170 /* make sure we redraw changes to the very first character of the window */
1171 if (view
->start
== pos
)
1172 view
->start_last
= 0;
1173 /* set the start of the viewable region to the start of the line on which
1174 * the cursor should be placed. if this line requires more space than
1175 * available in the view then simply start displaying text at the new
1176 * cursor position */
1177 for (int i
= 0; i
< 2 && (pos
<= view
->start
|| pos
> view
->end
); i
++) {
1178 view
->start
= i
== 0 ? text_line_begin(view
->text
, pos
) : pos
;
1187 void view_cursors_selection_start(Cursor
*c
) {
1190 size_t pos
= view_cursors_pos(c
);
1191 if (pos
== EPOS
|| !(c
->sel
= view_selections_new(c
->view
)))
1193 Text
*txt
= c
->view
->text
;
1194 c
->sel
->anchor
= text_mark_set(txt
, pos
);
1195 c
->sel
->cursor
= text_mark_set(txt
, text_char_next(txt
, pos
));
1199 void view_cursors_selection_restore(Cursor
*c
) {
1200 Text
*txt
= c
->view
->text
;
1203 Filerange sel
= text_range_new(
1204 text_mark_get(txt
, c
->lastsel_anchor
),
1205 text_mark_get(txt
, c
->lastsel_cursor
)
1207 if (!text_range_valid(&sel
))
1209 if (!(c
->sel
= view_selections_new(c
->view
)))
1211 view_selections_set(c
->sel
, &sel
);
1212 view_cursors_selection_sync(c
);
1216 void view_cursors_selection_stop(Cursor
*c
) {
1220 void view_cursors_selection_clear(Cursor
*c
) {
1221 view_selections_free(c
->sel
);
1225 void view_cursors_selection_swap(Cursor
*c
) {
1228 view_selections_swap(c
->sel
);
1229 view_cursors_selection_sync(c
);
1232 void view_cursors_selection_sync(Cursor
*c
) {
1235 Text
*txt
= c
->view
->text
;
1236 size_t anchor
= text_mark_get(txt
, c
->sel
->anchor
);
1237 size_t cursor
= text_mark_get(txt
, c
->sel
->cursor
);
1238 bool right_extending
= anchor
< cursor
;
1239 if (right_extending
)
1240 cursor
= text_char_prev(txt
, cursor
);
1241 view_cursors_to(c
, cursor
);
1244 Filerange
view_cursors_selection_get(Cursor
*c
) {
1245 return view_selections_get(c
->sel
);
1248 void view_cursors_selection_set(Cursor
*c
, Filerange
*r
) {
1249 if (!text_range_valid(r
))
1252 c
->sel
= view_selections_new(c
->view
);
1256 view_selections_set(c
->sel
, r
);
1259 Selection
*view_selections_new(View
*view
) {
1260 Selection
*s
= calloc(1, sizeof(*s
));
1265 s
->next
= view
->selections
;
1266 if (view
->selections
)
1267 view
->selections
->prev
= s
;
1268 view
->selections
= s
;
1272 void view_selections_free(Selection
*s
) {
1276 s
->prev
->next
= s
->next
;
1278 s
->next
->prev
= s
->prev
;
1279 if (s
->view
->selections
== s
)
1280 s
->view
->selections
= s
->next
;
1281 // XXX: add backlink Selection->Cursor?
1282 for (Cursor
*c
= s
->view
->cursors
; c
; c
= c
->next
) {
1284 c
->lastsel_anchor
= s
->anchor
;
1285 c
->lastsel_cursor
= s
->cursor
;
1292 void view_selections_clear(View
*view
) {
1293 while (view
->selections
)
1294 view_selections_free(view
->selections
);
1298 void view_cursors_clear(View
*view
) {
1299 for (Cursor
*c
= view
->cursors
, *next
; c
; c
= next
) {
1301 if (c
!= view
->cursor
) {
1302 view_selections_free(c
->sel
);
1303 view_cursors_free(c
);
1309 void view_selections_swap(Selection
*s
) {
1310 Mark temp
= s
->anchor
;
1311 s
->anchor
= s
->cursor
;
1315 Selection
*view_selections(View
*view
) {
1316 return view
->selections
;
1319 Selection
*view_selections_prev(Selection
*s
) {
1323 Selection
*view_selections_next(Selection
*s
) {
1327 Filerange
view_selection_get(View
*view
) {
1328 return view_selections_get(view
->cursor
->sel
);
1331 Filerange
view_selections_get(Selection
*s
) {
1333 return text_range_empty();
1334 Text
*txt
= s
->view
->text
;
1335 size_t anchor
= text_mark_get(txt
, s
->anchor
);
1336 size_t cursor
= text_mark_get(txt
, s
->cursor
);
1337 return text_range_new(anchor
, cursor
);
1340 void view_selections_set(Selection
*s
, Filerange
*r
) {
1341 if (!text_range_valid(r
))
1343 Text
*txt
= s
->view
->text
;
1344 size_t anchor
= text_mark_get(txt
, s
->anchor
);
1345 size_t cursor
= text_mark_get(txt
, s
->cursor
);
1346 bool left_extending
= anchor
> cursor
;
1347 if (left_extending
) {
1348 s
->anchor
= text_mark_set(txt
, r
->end
);
1349 s
->cursor
= text_mark_set(txt
, r
->start
);
1351 s
->anchor
= text_mark_set(txt
, r
->start
);
1352 s
->cursor
= text_mark_set(txt
, r
->end
);
1357 Text
*view_text(View
*view
) {