2 * Copyright (c) 2014 Marc André Tanner <mat at brain-dump.org>
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26 #include "text-motions.h"
27 #include "text-util.h"
31 Mark anchor
; /* position where the selection was created */
32 Mark cursor
; /* other selection endpoint where it changes */
33 View
*view
; /* associated view to which this selection belongs */
34 Selection
*prev
, *next
; /* previsous/next selections in no particular order */
37 struct Cursor
{ /* cursor position */
38 Filepos pos
; /* in bytes from the start of the file */
39 int row
, col
; /* in terms of zero based screen coordinates */
40 int lastcol
; /* remembered column used when moving across lines */
41 Line
*line
; /* screen line on which cursor currently resides */
42 Mark mark
; /* mark used to keep track of current cursor position */
43 Selection
*sel
; /* selection (if any) which folows the cursor upon movement */
44 Mark lastsel_anchor
;/* previously used selection data, */
45 Mark lastsel_cursor
;/* used to restore it */
46 Register reg
; /* per cursor register to support yank/put operation */
47 View
*view
; /* associated view to which this cursor belongs */
48 Cursor
*prev
, *next
;/* previous/next cursors in no particular order */
51 /* Viewable area, showing part of a file. Keeps track of cursors and selections.
52 * At all times there exists at least one cursor, which is placed in the visible viewport.
53 * Additional cursors can be created and positioned anywhere in the file. */
55 Text
*text
; /* underlying text management */
58 int width
, height
; /* size of display area */
59 Filepos start
, end
; /* currently displayed area [start, end] in bytes from the start of the file */
60 Filepos start_last
; /* previously used start of visible area, used to update the mark */
61 Mark start_mark
; /* mark to keep track of the start of the visible area */
62 size_t lines_size
; /* number of allocated bytes for lines (grows only) */
63 Line
*lines
; /* view->height number of lines representing view content */
64 Line
*topline
; /* top of the view, first line currently shown */
65 Line
*lastline
; /* last currently used line, always <= bottomline */
66 Line
*bottomline
; /* bottom of view, might be unused if lastline < bottomline */
67 Cursor
*cursor
; /* main cursor, always placed within the visible viewport */
68 Line
*line
; /* used while drawing view content, line where next char will be drawn */
69 int col
; /* used while drawing view content, column where next char will be drawn */
70 Syntax
*syntax
; /* syntax highlighting definitions for this view or NULL */
71 const SyntaxSymbol
*symbols
[SYNTAX_SYMBOL_LAST
]; /* symbols to use for white spaces etc */
72 int tabwidth
; /* how many spaces should be used to display a tab character */
73 Cursor
*cursors
; /* all cursors currently active */
74 Selection
*selections
; /* all selected regions */
77 static const SyntaxSymbol symbols_none
[] = {
78 [SYNTAX_SYMBOL_SPACE
] = { " " },
79 [SYNTAX_SYMBOL_TAB
] = { " " },
80 [SYNTAX_SYMBOL_TAB_FILL
] = { " " },
81 [SYNTAX_SYMBOL_EOL
] = { " " },
82 [SYNTAX_SYMBOL_EOF
] = { "~" },
85 static const SyntaxSymbol symbols_default
[] = {
86 [SYNTAX_SYMBOL_SPACE
] = { "\xC2\xB7" },
87 [SYNTAX_SYMBOL_TAB
] = { "\xE2\x96\xB6" },
88 [SYNTAX_SYMBOL_TAB_FILL
] = { " " },
89 [SYNTAX_SYMBOL_EOL
] = { "\xE2\x8F\x8E" },
90 [SYNTAX_SYMBOL_EOF
] = { "~" },
93 static Cell cell_unused
;
94 static Cell cell_blank
= { .data
= " " };
96 static void view_clear(View
*view
);
97 static bool view_addch(View
*view
, Cell
*cell
);
98 static bool view_coord_get(View
*view
, size_t pos
, Line
**retline
, int *retrow
, int *retcol
);
99 static void view_cursors_free(Cursor
*c
);
100 /* set/move current cursor position to a given (line, column) pair */
101 static size_t cursor_set(Cursor
*cursor
, Line
*line
, int col
);
103 void view_tabwidth_set(View
*view
, int tabwidth
) {
104 view
->tabwidth
= tabwidth
;
108 /* reset internal view data structures (cell matrix, line offsets etc.) */
109 static void view_clear(View
*view
) {
110 if (view
->start
!= view
->start_last
) {
111 view
->start_mark
= text_mark_set(view
->text
, view
->start
);
112 view
->start_last
= view
->start
;
114 size_t start
= text_mark_get(view
->text
, view
->start_mark
);
118 view
->topline
= view
->lines
;
119 view
->topline
->lineno
= text_lineno_by_pos(view
->text
, view
->start
);
120 view
->lastline
= view
->topline
;
122 /* reset all other lines */
123 size_t line_size
= sizeof(Line
) + view
->width
*sizeof(Cell
);
124 size_t end
= view
->height
* line_size
;
126 for (size_t i
= 0; i
< end
; i
+= line_size
) {
127 Line
*line
= (Line
*)(((char*)view
->lines
) + i
);
135 view
->bottomline
= prev
? prev
: view
->topline
;
136 view
->bottomline
->next
= NULL
;
137 view
->line
= view
->topline
;
141 Filerange
view_viewport_get(View
*view
) {
142 return (Filerange
){ .start
= view
->start
, .end
= view
->end
};
145 /* try to add another character to the view, return whether there was space left */
146 static bool view_addch(View
*view
, Cell
*cell
) {
151 size_t lineno
= view
->line
->lineno
;
153 switch (cell
->data
[0]) {
157 width
= view
->tabwidth
- (view
->col
% view
->tabwidth
);
158 for (int w
= 0; w
< width
; w
++) {
159 if (view
->col
+ 1 > view
->width
) {
160 view
->line
= view
->line
->next
;
164 view
->line
->lineno
= lineno
;
167 cell
->len
= w
== 0 ? 1 : 0;
168 int t
= w
== 0 ? SYNTAX_SYMBOL_TAB
: SYNTAX_SYMBOL_TAB_FILL
;
169 strncpy(cell
->data
, view
->symbols
[t
]->symbol
, sizeof(cell
->data
)-1);
170 cell
->attr
= view
->symbols
[t
]->style
;
171 view
->line
->cells
[view
->col
] = *cell
;
172 view
->line
->len
+= cell
->len
;
173 view
->line
->width
+= cell
->width
;
180 if (view
->col
+ cell
->width
> view
->width
) {
181 view
->line
= view
->line
->next
;
185 view
->line
->lineno
= lineno
;
188 strncpy(cell
->data
, view
->symbols
[SYNTAX_SYMBOL_EOL
]->symbol
, sizeof(cell
->data
)-1);
189 cell
->attr
= view
->symbols
[SYNTAX_SYMBOL_EOL
]->style
;
191 view
->line
->cells
[view
->col
] = *cell
;
192 view
->line
->len
+= cell
->len
;
193 view
->line
->width
+= cell
->width
;
194 for (int i
= view
->col
+ 1; i
< view
->width
; i
++)
195 view
->line
->cells
[i
] = cell_blank
;
197 view
->line
= view
->line
->next
;
199 view
->line
->lineno
= lineno
+ 1;
203 if ((unsigned char)cell
->data
[0] < 128 && !isprint((unsigned char)cell
->data
[0])) {
204 /* non-printable ascii char, represent it as ^(char + 64) */
206 .data
= { '^', cell
->data
[0] + 64, '\0' },
214 if (cell
->data
[0] == ' ') {
215 strncpy(cell
->data
, view
->symbols
[SYNTAX_SYMBOL_SPACE
]->symbol
, sizeof(cell
->data
)-1);
216 cell
->attr
= view
->symbols
[SYNTAX_SYMBOL_SPACE
]->style
;
220 if (view
->col
+ cell
->width
> view
->width
) {
221 for (int i
= view
->col
; i
< view
->width
; i
++)
222 view
->line
->cells
[i
] = cell_blank
;
223 view
->line
= view
->line
->next
;
228 view
->line
->width
+= cell
->width
;
229 view
->line
->len
+= cell
->len
;
230 view
->line
->lineno
= lineno
;
231 view
->line
->cells
[view
->col
] = *cell
;
233 /* set cells of a character which uses multiple columns */
234 for (int i
= 1; i
< cell
->width
; i
++)
235 view
->line
->cells
[view
->col
++] = cell_unused
;
242 CursorPos
view_cursor_getpos(View
*view
) {
243 Cursor
*cursor
= view
->cursor
;
244 Line
*line
= cursor
->line
;
245 CursorPos pos
= { .line
= line
->lineno
, .col
= cursor
->col
};
246 while (line
->prev
&& line
->prev
->lineno
== pos
.line
) {
248 pos
.col
+= line
->width
;
254 static void cursor_to(Cursor
*c
, size_t pos
) {
255 Text
*txt
= c
->view
->text
;
256 c
->mark
= text_mark_set(txt
, pos
);
261 size_t anchor
= text_mark_get(txt
, c
->sel
->anchor
);
262 size_t cursor
= text_mark_get(txt
, c
->sel
->cursor
);
263 /* do we have to change the orientation of the selection? */
264 if (pos
< anchor
&& anchor
< cursor
) {
265 /* right extend -> left extend */
266 anchor
= text_char_next(txt
, anchor
);
267 c
->sel
->anchor
= text_mark_set(txt
, anchor
);
268 } else if (cursor
< anchor
&& anchor
<= pos
) {
269 /* left extend -> right extend */
270 anchor
= text_char_prev(txt
, anchor
);
271 c
->sel
->anchor
= text_mark_set(txt
, anchor
);
274 pos
= text_char_next(txt
, pos
);
275 c
->sel
->cursor
= text_mark_set(txt
, pos
);
277 if (!view_coord_get(c
->view
, pos
, &c
->line
, &c
->row
, &c
->col
)) {
278 if (c
->view
->cursor
== c
) {
279 c
->line
= c
->view
->topline
;
285 // TODO: minimize number of redraws
289 static bool view_coord_get(View
*view
, size_t pos
, Line
**retline
, int *retrow
, int *retcol
) {
290 int row
= 0, col
= 0;
291 size_t cur
= view
->start
;
292 Line
*line
= view
->topline
;
294 if (pos
< view
->start
|| pos
> view
->end
) {
295 if (retline
) *retline
= NULL
;
296 if (retrow
) *retrow
= -1;
297 if (retcol
) *retcol
= -1;
301 while (line
&& line
!= view
->lastline
&& cur
< pos
) {
302 if (cur
+ line
->len
> pos
)
310 int max_col
= MIN(view
->width
, line
->width
);
311 while (cur
< pos
&& col
< max_col
) {
312 cur
+= line
->cells
[col
].len
;
313 /* skip over columns occupied by the same character */
314 while (++col
< max_col
&& line
->cells
[col
].len
== 0);
317 line
= view
->bottomline
;
318 row
= view
->height
- 1;
321 if (retline
) *retline
= line
;
322 if (retrow
) *retrow
= row
;
323 if (retcol
) *retcol
= col
;
327 /* move the cursor to the character at pos bytes from the begining of the file.
328 * if pos is not in the current viewport, redraw the view to make it visible */
329 void view_cursor_to(View
*view
, size_t pos
) {
330 view_cursors_to(view
->cursor
, pos
);
333 /* redraw the complete with data starting from view->start bytes into the file.
334 * stop once the screen is full, update view->end, view->lastline */
335 void view_draw(View
*view
) {
337 /* current absolute file position */
338 size_t pos
= view
->start
;
339 /* number of bytes to read in one go */
340 size_t text_len
= view
->width
* view
->height
;
341 /* current buffer to work with */
342 char text
[text_len
+1];
343 /* remaining bytes to process in buffer*/
344 size_t rem
= text_bytes_get(view
->text
, pos
, text_len
, text
);
345 /* NUL terminate because regex(3) function expect it */
347 /* current position into buffer from which to interpret a character */
349 /* syntax definition to use */
350 Syntax
*syntax
= view
->syntax
;
351 /* matched tokens for each syntax rule */
352 regmatch_t match
[syntax
? LENGTH(syntax
->rules
) : 1][1], *matched
= NULL
;
353 memset(match
, 0, sizeof match
);
354 /* default and current curses attributes to use */
355 int default_attrs
= 0, attrs
= default_attrs
;
356 /* start from known multibyte state */
357 mbstate_t mbstate
= { 0 };
361 /* current 'parsed' character' */
364 memset(&cell
, 0, sizeof cell
);
367 if (matched
&& cur
>= text
+ matched
->rm_eo
) {
368 /* end of current match */
370 attrs
= default_attrs
;
371 for (int i
= 0; i
< LENGTH(syntax
->rules
); i
++) {
372 if (match
[i
][0].rm_so
== -1)
373 continue; /* no match on whole text */
374 /* reset matches which overlap with matched */
375 if (text
+ match
[i
][0].rm_so
<= cur
&& cur
< text
+ match
[i
][0].rm_eo
) {
376 match
[i
][0].rm_so
= 0;
377 match
[i
][0].rm_eo
= 0;
383 size_t off
= cur
- text
; /* number of already processed bytes */
384 for (int i
= 0; i
< LENGTH(syntax
->rules
); i
++) {
385 SyntaxRule
*rule
= &syntax
->rules
[i
];
388 if (match
[i
][0].rm_so
== -1)
389 continue; /* no match on whole text */
390 if (off
>= (size_t)match
[i
][0].rm_eo
) {
391 /* past match, continue search from current position */
392 if (regexec(&rule
->regex
, cur
, 1, match
[i
], 0) ||
393 match
[i
][0].rm_so
== match
[i
][0].rm_eo
) {
394 match
[i
][0].rm_so
= -1;
395 match
[i
][0].rm_eo
= -1;
398 match
[i
][0].rm_so
+= off
;
399 match
[i
][0].rm_eo
+= off
;
402 if (text
+ match
[i
][0].rm_so
<= cur
&& cur
< text
+ match
[i
][0].rm_eo
) {
403 /* within matched expression */
404 matched
= &match
[i
][0];
406 break; /* first match views */
412 size_t len
= mbrtowc(&wchar
, cur
, rem
, &mbstate
);
413 if (len
== (size_t)-1 && errno
== EILSEQ
) {
414 /* ok, we encountered an invalid multibyte sequence,
415 * replace it with the Unicode Replacement Character
416 * (FFFD) and skip until the start of the next utf8 char */
417 for (len
= 1; rem
> len
&& !ISUTF8(cur
[len
]); len
++);
418 cell
= (Cell
){ .data
= "\xEF\xBF\xBD", .len
= len
, .width
= 1, .istab
= false };
419 } else if (len
== (size_t)-2) {
420 /* not enough bytes available to convert to a
421 * wide character. advance file position and read
422 * another junk into buffer.
424 rem
= text_bytes_get(view
->text
, pos
, text_len
, text
);
428 } else if (len
== 0) {
429 /* NUL byte encountered, store it and continue */
430 cell
= (Cell
){ .data
= "\x00", .len
= 1, .width
= 0, .istab
= false };
432 for (size_t i
= 0; i
< len
; i
++)
433 cell
.data
[i
] = cur
[i
];
434 cell
.data
[len
] = '\0';
437 cell
.width
= wcwidth(wchar
);
438 if (cell
.width
== -1)
442 if (cur
[0] == '\r' && rem
> 1 && cur
[1] == '\n') {
443 /* convert views style newline \r\n into a single char with len = 2 */
444 cell
= (Cell
){ .data
= "\n", .len
= 2, .width
= 1, .istab
= false };
448 if (!view_addch(view
, &cell
))
456 /* set end of vieviewg region */
458 view
->lastline
= view
->line
? view
->line
: view
->bottomline
;
460 for (int x
= view
->col
; x
< view
->width
; x
++)
461 view
->line
->cells
[x
] = cell_blank
;
464 for (Line
*l
= view
->lastline
->next
; l
; l
= l
->next
) {
465 strncpy(l
->cells
[0].data
, view
->symbols
[SYNTAX_SYMBOL_EOF
]->symbol
, sizeof(l
->cells
[0].data
));
466 l
->cells
[0].attr
= view
->symbols
[SYNTAX_SYMBOL_EOF
]->style
;
467 for (int x
= 1; x
< view
->width
; x
++)
468 l
->cells
[x
] = cell_blank
;
473 for (Selection
*s
= view
->selections
; s
; s
= s
->next
) {
474 Filerange sel
= view_selections_get(s
);
475 if (text_range_valid(&sel
)) {
476 Line
*start_line
; int start_col
;
477 Line
*end_line
; int end_col
;
478 view_coord_get(view
, sel
.start
, &start_line
, NULL
, &start_col
);
479 view_coord_get(view
, sel
.end
, &end_line
, NULL
, &end_col
);
480 if (start_line
|| end_line
) {
482 start_line
= view
->topline
;
486 end_line
= view
->lastline
;
487 end_col
= end_line
->width
;
489 for (Line
*l
= start_line
; l
!= end_line
->next
; l
= l
->next
) {
490 int col
= (l
== start_line
) ? start_col
: 0;
491 int end
= (l
== end_line
) ? end_col
: l
->width
;
493 l
->cells
[col
++].selected
= true;
498 if (view
->events
&& view
->events
->selection
)
499 view
->events
->selection(view
->events
->data
, &sel
);
503 for (Cursor
*c
= view
->cursors
; c
; c
= c
->next
) {
504 size_t pos
= view_cursors_pos(c
);
505 if (view_coord_get(view
, pos
, &c
->line
, &c
->row
, &c
->col
)) {
506 c
->line
->cells
[c
->col
].cursor
= true;
507 if (view
->ui
&& view
->syntax
) {
508 Line
*line_match
; int col_match
;
509 size_t pos_match
= text_bracket_match_except(view
->text
, pos
, "<>");
510 if (pos
!= pos_match
&& view_coord_get(view
, pos_match
, &line_match
, NULL
, &col_match
)) {
511 line_match
->cells
[col_match
].selected
= true;
514 } else if (c
== view
->cursor
) {
515 c
->line
= view
->topline
;
522 view
->ui
->draw(view
->ui
);
525 bool view_resize(View
*view
, int width
, int height
) {
530 size_t lines_size
= height
*(sizeof(Line
) + width
*sizeof(Cell
));
531 if (lines_size
> view
->lines_size
) {
532 Line
*lines
= realloc(view
->lines
, lines_size
);
536 view
->lines_size
= lines_size
;
539 view
->height
= height
;
541 memset(view
->lines
, 0, view
->lines_size
);
546 int view_height_get(View
*view
) {
550 int view_width_get(View
*view
) {
554 void view_free(View
*view
) {
557 while (view
->cursors
)
558 view_cursors_free(view
->cursors
);
559 while (view
->selections
)
560 view_selections_free(view
->selections
);
565 void view_reload(View
*view
, Text
*text
) {
567 view_selections_clear(view
);
568 view_cursor_to(view
, 0);
571 View
*view_new(Text
*text
, ViewEvent
*events
) {
574 View
*view
= calloc(1, sizeof(View
));
577 if (!view_cursors_new(view
)) {
583 view
->events
= events
;
585 view_options_set(view
, 0);
587 if (!view_resize(view
, 1, 1)) {
592 view_cursor_to(view
, 0);
597 void view_ui(View
*view
, UiWin
* ui
) {
601 static size_t cursor_set(Cursor
*cursor
, Line
*line
, int col
) {
603 View
*view
= cursor
->view
;
604 size_t pos
= view
->start
;
605 /* get row number and file offset at start of the given line */
606 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
) {
611 /* for characters which use more than 1 column, make sure we are on the left most */
612 while (col
> 0 && line
->cells
[col
].len
== 0)
614 while (col
< line
->width
&& line
->cells
[col
].istab
)
617 /* calculate offset within the line */
618 for (int i
= 0; i
< col
; i
++)
619 pos
+= line
->cells
[i
].len
;
625 cursor_to(cursor
, pos
);
630 bool view_viewport_down(View
*view
, int n
) {
632 if (view
->end
== text_size(view
->text
))
634 if (n
>= view
->height
) {
635 view
->start
= view
->end
;
637 for (line
= view
->topline
; line
&& n
> 0; line
= line
->next
, n
--)
638 view
->start
+= line
->len
;
644 bool view_viewport_up(View
*view
, int n
) {
645 /* scrolling up is somewhat tricky because we do not yet know where
646 * the lines start, therefore scan backwards but stop at a reasonable
647 * maximum in case we are dealing with a file without any newlines
649 if (view
->start
== 0)
651 size_t max
= view
->width
* view
->height
;
653 Iterator it
= text_iterator_get(view
->text
, view
->start
- 1);
655 if (!text_iterator_byte_get(&it
, &c
))
658 /* skip newlines immediately before display area */
659 if (c
== '\n' && text_iterator_byte_prev(&it
, &c
))
661 if (c
== '\r' && text_iterator_byte_prev(&it
, &c
))
664 if (c
== '\n' && --n
== 0)
668 } while (text_iterator_byte_prev(&it
, &c
));
676 void view_redraw_top(View
*view
) {
677 Line
*line
= view
->cursor
->line
;
678 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
)
679 view
->start
+= cur
->len
;
681 view_cursor_to(view
, view
->cursor
->pos
);
684 void view_redraw_center(View
*view
) {
685 int center
= view
->height
/ 2;
686 size_t pos
= view
->cursor
->pos
;
687 for (int i
= 0; i
< 2; i
++) {
689 Line
*line
= view
->cursor
->line
;
690 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
)
692 if (linenr
< center
) {
693 view_slide_down(view
, center
- linenr
);
696 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
&& linenr
> center
; cur
= cur
->next
) {
697 view
->start
+= cur
->len
;
703 view_cursor_to(view
, pos
);
706 void view_redraw_bottom(View
*view
) {
707 Line
*line
= view
->cursor
->line
;
708 if (line
== view
->lastline
)
710 size_t pos
= view
->cursor
->pos
;
711 view_viewport_up(view
, view
->height
);
712 while (pos
> view
->end
&& view_viewport_down(view
, 1));
713 view_cursor_to(view
, pos
);
716 size_t view_slide_up(View
*view
, int lines
) {
717 Cursor
*cursor
= view
->cursor
;
718 if (view_viewport_down(view
, lines
)) {
719 if (cursor
->line
== view
->topline
)
720 cursor_set(cursor
, view
->topline
, cursor
->col
);
722 view_cursor_to(view
, cursor
->pos
);
724 view_screenline_down(cursor
);
729 size_t view_slide_down(View
*view
, int lines
) {
730 Cursor
*cursor
= view
->cursor
;
731 if (view_viewport_up(view
, lines
)) {
732 if (cursor
->line
== view
->lastline
)
733 cursor_set(cursor
, view
->lastline
, cursor
->col
);
735 view_cursor_to(view
, cursor
->pos
);
737 view_screenline_up(cursor
);
742 size_t view_scroll_up(View
*view
, int lines
) {
743 Cursor
*cursor
= view
->cursor
;
744 if (view_viewport_up(view
, lines
)) {
745 Line
*line
= cursor
->line
< view
->lastline
? cursor
->line
: view
->lastline
;
746 cursor_set(cursor
, line
, view
->cursor
->col
);
748 view_cursor_to(view
, 0);
753 size_t view_scroll_down(View
*view
, int lines
) {
754 Cursor
*cursor
= view
->cursor
;
755 if (view_viewport_down(view
, lines
)) {
756 Line
*line
= cursor
->line
> view
->topline
? cursor
->line
: view
->topline
;
757 cursor_set(cursor
, line
, cursor
->col
);
759 view_cursor_to(view
, text_size(view
->text
));
764 size_t view_line_up(Cursor
*cursor
) {
765 if (cursor
->line
&& cursor
->line
->prev
&& cursor
->line
->prev
->prev
&&
766 cursor
->line
->lineno
!= cursor
->line
->prev
->lineno
&&
767 cursor
->line
->prev
->lineno
!= cursor
->line
->prev
->prev
->lineno
)
768 return view_screenline_up(cursor
);
769 size_t pos
= text_line_up(cursor
->view
->text
, cursor
->pos
);
770 view_cursors_to(cursor
, pos
);
774 size_t view_line_down(Cursor
*cursor
) {
775 if (cursor
->line
&& (!cursor
->line
->next
|| cursor
->line
->next
->lineno
!= cursor
->line
->lineno
))
776 return view_screenline_down(cursor
);
777 size_t pos
= text_line_down(cursor
->view
->text
, cursor
->pos
);
778 view_cursors_to(cursor
, pos
);
782 size_t view_screenline_up(Cursor
*cursor
) {
783 int lastcol
= cursor
->lastcol
;
785 lastcol
= cursor
->col
;
786 if (!cursor
->line
->prev
)
787 view_scroll_up(cursor
->view
, 1);
788 if (cursor
->line
->prev
)
789 cursor_set(cursor
, cursor
->line
->prev
, lastcol
);
790 cursor
->lastcol
= lastcol
;
794 size_t view_screenline_down(Cursor
*cursor
) {
795 int lastcol
= cursor
->lastcol
;
797 lastcol
= cursor
->col
;
798 if (!cursor
->line
->next
&& cursor
->line
== cursor
->view
->bottomline
)
799 view_scroll_down(cursor
->view
, 1);
800 if (cursor
->line
->next
)
801 cursor_set(cursor
, cursor
->line
->next
, lastcol
);
802 cursor
->lastcol
= lastcol
;
806 size_t view_screenline_begin(Cursor
*cursor
) {
809 return cursor_set(cursor
, cursor
->line
, 0);
812 size_t view_screenline_middle(Cursor
*cursor
) {
815 return cursor_set(cursor
, cursor
->line
, cursor
->line
->width
/ 2);
818 size_t view_screenline_end(Cursor
*cursor
) {
821 int col
= cursor
->line
->width
- 1;
822 return cursor_set(cursor
, cursor
->line
, col
>= 0 ? col
: 0);
825 size_t view_cursor_get(View
*view
) {
826 return view_cursors_pos(view
->cursor
);
829 const Line
*view_lines_get(View
*view
) {
830 return view
->topline
;
833 void view_scroll_to(View
*view
, size_t pos
) {
834 view_cursors_scroll_to(view
->cursor
, pos
);
837 void view_syntax_set(View
*view
, Syntax
*syntax
) {
838 view
->syntax
= syntax
;
839 for (int i
= 0; i
< LENGTH(view
->symbols
); i
++) {
840 if (syntax
&& syntax
->symbols
[i
].symbol
)
841 view
->symbols
[i
] = &syntax
->symbols
[i
];
843 view
->symbols
[i
] = &symbols_none
[i
];
846 for (const char **style
= syntax
->styles
; *style
; style
++) {
847 view
->ui
->syntax_style(view
->ui
, style
- syntax
->styles
, *style
);
852 Syntax
*view_syntax_get(View
*view
) {
856 void view_options_set(View
*view
, enum UiOption options
) {
858 [SYNTAX_SYMBOL_SPACE
] = UI_OPTION_SYMBOL_SPACE
,
859 [SYNTAX_SYMBOL_TAB
] = UI_OPTION_SYMBOL_TAB
,
860 [SYNTAX_SYMBOL_TAB_FILL
] = UI_OPTION_SYMBOL_TAB_FILL
,
861 [SYNTAX_SYMBOL_EOL
] = UI_OPTION_SYMBOL_EOL
,
862 [SYNTAX_SYMBOL_EOF
] = UI_OPTION_SYMBOL_EOF
,
864 for (int i
= 0; i
< LENGTH(mapping
); i
++) {
865 if (options
& mapping
[i
]) {
866 if (view
->syntax
&& view
->syntax
->symbols
[i
].symbol
)
867 view
->symbols
[i
] = &view
->syntax
->symbols
[i
];
869 view
->symbols
[i
] = &symbols_default
[i
];
871 view
->symbols
[i
] = &symbols_none
[i
];
875 view
->ui
->options_set(view
->ui
, options
);
878 enum UiOption
view_options_get(View
*view
) {
879 return view
->ui
? view
->ui
->options_get(view
->ui
) : 0;
882 size_t view_screenline_goto(View
*view
, int n
) {
883 size_t pos
= view
->start
;
884 for (Line
*line
= view
->topline
; --n
> 0 && line
!= view
->lastline
; line
= line
->next
)
889 Cursor
*view_cursors_new(View
*view
) {
890 Cursor
*c
= calloc(1, sizeof(*c
));
895 c
->next
= view
->cursors
;
897 view
->cursors
->prev
= c
;
903 int view_cursors_count(View
*view
) {
905 for (Cursor
*c
= view_cursors(view
); c
; c
= view_cursors_next(c
))
910 static void view_cursors_free(Cursor
*c
) {
913 register_release(&c
->reg
);
915 c
->prev
->next
= c
->next
;
917 c
->next
->prev
= c
->prev
;
918 if (c
->view
->cursors
== c
)
919 c
->view
->cursors
= c
->next
;
920 if (c
->view
->cursor
== c
)
921 c
->view
->cursor
= c
->next
? c
->next
: c
->prev
;
925 void view_cursors_dispose(Cursor
*c
) {
928 View
*view
= c
->view
;
929 if (view
->cursors
&& view
->cursors
->next
) {
930 view_selections_free(c
->sel
);
931 view_cursors_free(c
);
936 Cursor
*view_cursors(View
*view
) {
937 return view
->cursors
;
940 Cursor
*view_cursor(View
*view
) {
944 Cursor
*view_cursors_prev(Cursor
*c
) {
948 Cursor
*view_cursors_next(Cursor
*c
) {
952 size_t view_cursors_pos(Cursor
*c
) {
953 return text_mark_get(c
->view
->text
, c
->mark
);
956 Register
*view_cursors_register(Cursor
*c
) {
960 void view_cursors_scroll_to(Cursor
*c
, size_t pos
) {
961 View
*view
= c
->view
;
962 if (view
->cursor
== c
) {
963 while (pos
< view
->start
&& view_viewport_up(view
, 1));
964 while (pos
> view
->end
&& view_viewport_down(view
, 1));
966 view_cursors_to(c
, pos
);
969 void view_cursors_to(Cursor
*c
, size_t pos
) {
970 View
*view
= c
->view
;
971 if (c
->view
->cursors
== c
) {
972 c
->mark
= text_mark_set(view
->text
, pos
);
974 size_t max
= text_size(view
->text
);
975 if (pos
== max
&& view
->end
!= max
) {
976 /* do not display an empty screen when shoviewg the end of the file */
978 view_viewport_up(view
, view
->height
/ 2);
980 /* set the start of the viewable region to the start of the line on which
981 * the cursor should be placed. if this line requires more space than
982 * available in the view then simply start displaying text at the new
984 for (int i
= 0; i
< 2 && (pos
< view
->start
|| pos
> view
->end
); i
++) {
985 view
->start
= i
== 0 ? text_line_begin(view
->text
, pos
) : pos
;
994 void view_cursors_selection_start(Cursor
*c
) {
997 size_t pos
= view_cursors_pos(c
);
998 if (pos
== EPOS
|| !(c
->sel
= view_selections_new(c
->view
)))
1000 Text
*txt
= c
->view
->text
;
1001 c
->sel
->anchor
= text_mark_set(txt
, pos
);
1002 c
->sel
->cursor
= text_mark_set(txt
, text_char_next(txt
, pos
));
1006 void view_cursors_selection_restore(Cursor
*c
) {
1007 Text
*txt
= c
->view
->text
;
1010 Filerange sel
= text_range_new(
1011 text_mark_get(txt
, c
->lastsel_anchor
),
1012 text_mark_get(txt
, c
->lastsel_cursor
)
1014 if (!text_range_valid(&sel
))
1016 if (!(c
->sel
= view_selections_new(c
->view
)))
1018 view_selections_set(c
->sel
, &sel
);
1019 view_cursors_selection_sync(c
);
1023 void view_cursors_selection_stop(Cursor
*c
) {
1027 void view_cursors_selection_clear(Cursor
*c
) {
1028 view_selections_free(c
->sel
);
1032 void view_cursors_selection_swap(Cursor
*c
) {
1035 view_selections_swap(c
->sel
);
1036 view_cursors_selection_sync(c
);
1039 void view_cursors_selection_sync(Cursor
*c
) {
1042 Text
*txt
= c
->view
->text
;
1043 size_t anchor
= text_mark_get(txt
, c
->sel
->anchor
);
1044 size_t cursor
= text_mark_get(txt
, c
->sel
->cursor
);
1045 bool right_extending
= anchor
< cursor
;
1046 if (right_extending
)
1047 cursor
= text_char_prev(txt
, cursor
);
1048 view_cursors_to(c
, cursor
);
1051 Filerange
view_cursors_selection_get(Cursor
*c
) {
1052 return view_selections_get(c
->sel
);
1055 void view_cursors_selection_set(Cursor
*c
, Filerange
*r
) {
1056 if (!text_range_valid(r
))
1059 c
->sel
= view_selections_new(c
->view
);
1063 view_selections_set(c
->sel
, r
);
1066 Selection
*view_selections_new(View
*view
) {
1067 Selection
*s
= calloc(1, sizeof(*s
));
1072 s
->next
= view
->selections
;
1073 if (view
->selections
)
1074 view
->selections
->prev
= s
;
1075 view
->selections
= s
;
1079 void view_selections_free(Selection
*s
) {
1083 s
->prev
->next
= s
->next
;
1085 s
->next
->prev
= s
->prev
;
1086 if (s
->view
->selections
== s
)
1087 s
->view
->selections
= s
->next
;
1088 // XXX: add backlink Selection->Cursor?
1089 for (Cursor
*c
= s
->view
->cursors
; c
; c
= c
->next
) {
1091 c
->lastsel_anchor
= s
->anchor
;
1092 c
->lastsel_cursor
= s
->cursor
;
1099 void view_selections_clear(View
*view
) {
1100 while (view
->selections
)
1101 view_selections_free(view
->selections
);
1105 void view_cursors_clear(View
*view
) {
1106 for (Cursor
*c
= view
->cursors
, *next
; c
; c
= next
) {
1108 if (c
!= view
->cursor
) {
1109 view_selections_free(c
->sel
);
1110 view_cursors_free(c
);
1116 void view_selections_swap(Selection
*s
) {
1117 Mark temp
= s
->anchor
;
1118 s
->anchor
= s
->cursor
;
1122 Selection
*view_selections(View
*view
) {
1123 return view
->selections
;
1126 Selection
*view_selections_prev(Selection
*s
) {
1130 Selection
*view_selections_next(Selection
*s
) {
1134 Filerange
view_selections_get(Selection
*s
) {
1136 return text_range_empty();
1137 Text
*txt
= s
->view
->text
;
1138 size_t anchor
= text_mark_get(txt
, s
->anchor
);
1139 size_t cursor
= text_mark_get(txt
, s
->cursor
);
1140 return text_range_new(anchor
, cursor
);
1143 void view_selections_set(Selection
*s
, Filerange
*r
) {
1144 if (!text_range_valid(r
))
1146 Text
*txt
= s
->view
->text
;
1147 size_t anchor
= text_mark_get(txt
, s
->anchor
);
1148 size_t cursor
= text_mark_get(txt
, s
->cursor
);
1149 bool left_extending
= anchor
> cursor
;
1150 if (left_extending
) {
1151 s
->anchor
= text_mark_set(txt
, r
->end
);
1152 s
->cursor
= text_mark_set(txt
, r
->start
);
1154 s
->anchor
= text_mark_set(txt
, r
->start
);
1155 s
->cursor
= text_mark_set(txt
, r
->end
);