9 #include "text-motions.h"
10 #include "text-util.h"
20 SYNTAX_SYMBOL_TAB_FILL
,
25 /* A selection is made up of two marks named cursor and anchor.
26 * While the anchor remains fixed the cursor mark follows cursor motions.
27 * For a selection (indicated by []), the marks (^) are placed as follows:
33 * That is the marks point to the *start* of the first and last character
34 * of the selection. In particular for a single character selection (as
35 * depicted on the right above) both marks point to the same location.
37 * The view_selections_{get,set} functions take care of adding/removing
38 * the necessary offset for the last character.
41 Mark anchor
; /* position where the selection was created */
42 Mark cursor
; /* other selection endpoint where it changes */
43 View
*view
; /* associated view to which this selection belongs */
44 Cursor
*cur
; /* cursor whose movement will affect this selection */
45 Selection
*prev
, *next
; /* previsous/next selections in no particular order */
48 struct Cursor
{ /* cursor position */
49 size_t pos
; /* in bytes from the start of the file */
50 int row
, col
; /* in terms of zero based screen coordinates */
51 int lastcol
; /* remembered column used when moving across lines */
52 Line
*line
; /* screen line on which cursor currently resides */
53 Mark mark
; /* mark used to keep track of current cursor position */
54 Mark mark_old
; /* previous value of the mark, used to recover cursor position */
55 Selection
*sel
; /* selection (if any) which folows the cursor upon movement */
56 Mark lastsel_anchor
;/* previously used selection data, */
57 Mark lastsel_cursor
;/* used to restore it */
58 Register reg
; /* per cursor register to support yank/put operation */
59 int generation
; /* used to filter out newly created cursors during iteration */
60 int number
; /* how many cursors are located before this one */
61 View
*view
; /* associated view to which this cursor belongs */
62 Cursor
*prev
, *next
;/* previous/next cursors ordered by location at creation time */
65 /* Viewable area, showing part of a file. Keeps track of cursors and selections.
66 * At all times there exists at least one cursor, which is placed in the visible viewport.
67 * Additional cursors can be created and positioned anywhere in the file. */
69 Text
*text
; /* underlying text management */
71 Cell cell_blank
; /* used for empty/blank cells */
72 int width
, height
; /* size of display area */
73 size_t start
, end
; /* currently displayed area [start, end] in bytes from the start of the file */
74 size_t start_last
; /* previously used start of visible area, used to update the mark */
75 Mark start_mark
; /* mark to keep track of the start of the visible area */
76 size_t lines_size
; /* number of allocated bytes for lines (grows only) */
77 Line
*lines
; /* view->height number of lines representing view content */
78 Line
*topline
; /* top of the view, first line currently shown */
79 Line
*lastline
; /* last currently used line, always <= bottomline */
80 Line
*bottomline
; /* bottom of view, might be unused if lastline < bottomline */
81 Cursor
*cursor
; /* main cursor, always placed within the visible viewport */
82 Cursor
*cursor_latest
; /* most recently created cursor */
83 Cursor
*cursor_dead
;/* main cursor which was disposed, will be removed when another cursor is created */
84 int cursor_count
; /* how many cursors do currently exist */
85 Line
*line
; /* used while drawing view content, line where next char will be drawn */
86 int col
; /* used while drawing view content, column where next char will be drawn */
87 const SyntaxSymbol
*symbols
[SYNTAX_SYMBOL_LAST
]; /* symbols to use for white spaces etc */
88 int tabwidth
; /* how many spaces should be used to display a tab character */
89 Cursor
*cursors
; /* all cursors currently active */
90 Selection
*selections
; /* all selected regions */
91 int cursor_generation
; /* used to filter out newly created cursors during iteration */
92 bool need_update
; /* whether view has been redrawn */
93 bool large_file
; /* optimize for displaying large files */
97 static const SyntaxSymbol symbols_none
[] = {
98 [SYNTAX_SYMBOL_SPACE
] = { " " },
99 [SYNTAX_SYMBOL_TAB
] = { " " },
100 [SYNTAX_SYMBOL_TAB_FILL
] = { " " },
101 [SYNTAX_SYMBOL_EOL
] = { " " },
104 static const SyntaxSymbol symbols_default
[] = {
105 [SYNTAX_SYMBOL_SPACE
] = { "·" /* Middle Dot U+00B7 */ },
106 [SYNTAX_SYMBOL_TAB
] = { "›" /* Single Right-Pointing Angle Quotation Mark U+203A */ },
107 [SYNTAX_SYMBOL_TAB_FILL
] = { " " },
108 [SYNTAX_SYMBOL_EOL
] = { "↵" /* Downwards Arrow with Corner Leftwards U+21B5 */ },
111 static Cell cell_unused
;
113 static void view_clear(View
*view
);
114 static bool view_addch(View
*view
, Cell
*cell
);
115 static void view_cursors_free(Cursor
*c
);
116 /* set/move current cursor position to a given (line, column) pair */
117 static size_t cursor_set(Cursor
*cursor
, Line
*line
, int col
);
119 void view_tabwidth_set(View
*view
, int tabwidth
) {
120 view
->tabwidth
= tabwidth
;
124 /* reset internal view data structures (cell matrix, line offsets etc.) */
125 static void view_clear(View
*view
) {
126 memset(view
->lines
, 0, view
->lines_size
);
127 if (view
->start
!= view
->start_last
) {
128 if (view
->start
== 0)
129 view
->start_mark
= EMARK
;
131 view
->start_mark
= text_mark_set(view
->text
, view
->start
);
134 if (view
->start_mark
== EMARK
)
137 start
= text_mark_get(view
->text
, view
->start_mark
);
142 view
->start_last
= view
->start
;
143 view
->topline
= view
->lines
;
144 view
->topline
->lineno
= view
->large_file
? 1 : text_lineno_by_pos(view
->text
, view
->start
);
145 view
->lastline
= view
->topline
;
147 size_t line_size
= sizeof(Line
) + view
->width
*sizeof(Cell
);
148 size_t end
= view
->height
* line_size
;
150 for (size_t i
= 0; i
< end
; i
+= line_size
) {
151 Line
*line
= (Line
*)(((char*)view
->lines
) + i
);
157 view
->bottomline
= prev
? prev
: view
->topline
;
158 view
->bottomline
->next
= NULL
;
159 view
->line
= view
->topline
;
162 view
->cell_blank
.style
= view
->ui
->style_get(view
->ui
, UI_STYLE_DEFAULT
);
165 Filerange
view_viewport_get(View
*view
) {
166 return (Filerange
){ .start
= view
->start
, .end
= view
->end
};
169 /* try to add another character to the view, return whether there was space left */
170 static bool view_addch(View
*view
, Cell
*cell
) {
175 size_t lineno
= view
->line
->lineno
;
176 unsigned char ch
= (unsigned char)cell
->data
[0];
177 cell
->style
= view
->cell_blank
.style
;
182 width
= view
->tabwidth
- (view
->col
% view
->tabwidth
);
183 for (int w
= 0; w
< width
; w
++) {
184 if (view
->col
+ 1 > view
->width
) {
185 view
->line
= view
->line
->next
;
189 view
->line
->lineno
= lineno
;
192 cell
->len
= w
== 0 ? 1 : 0;
193 int t
= w
== 0 ? SYNTAX_SYMBOL_TAB
: SYNTAX_SYMBOL_TAB_FILL
;
194 strncpy(cell
->data
, view
->symbols
[t
]->symbol
, sizeof(cell
->data
)-1);
195 view
->line
->cells
[view
->col
] = *cell
;
196 view
->line
->len
+= cell
->len
;
197 view
->line
->width
+= cell
->width
;
204 if (view
->col
+ cell
->width
> view
->width
) {
205 view
->line
= view
->line
->next
;
209 view
->line
->lineno
= lineno
;
212 strncpy(cell
->data
, view
->symbols
[SYNTAX_SYMBOL_EOL
]->symbol
, sizeof(cell
->data
)-1);
214 view
->line
->cells
[view
->col
] = *cell
;
215 view
->line
->len
+= cell
->len
;
216 view
->line
->width
+= cell
->width
;
217 for (int i
= view
->col
+ 1; i
< view
->width
; i
++)
218 view
->line
->cells
[i
] = view
->cell_blank
;
220 view
->line
= view
->line
->next
;
222 view
->line
->lineno
= lineno
+ 1;
226 if (ch
< 128 && !isprint(ch
)) {
227 /* non-printable ascii char, represent it as ^(char + 64) */
229 .data
= { '^', ch
== 127 ? '?' : ch
+ 64, '\0' },
232 .style
= cell
->style
,
237 strncpy(cell
->data
, view
->symbols
[SYNTAX_SYMBOL_SPACE
]->symbol
, sizeof(cell
->data
)-1);
241 if (view
->col
+ cell
->width
> view
->width
) {
242 for (int i
= view
->col
; i
< view
->width
; i
++)
243 view
->line
->cells
[i
] = view
->cell_blank
;
244 view
->line
= view
->line
->next
;
249 view
->line
->width
+= cell
->width
;
250 view
->line
->len
+= cell
->len
;
251 view
->line
->lineno
= lineno
;
252 view
->line
->cells
[view
->col
] = *cell
;
254 /* set cells of a character which uses multiple columns */
255 for (int i
= 1; i
< cell
->width
; i
++)
256 view
->line
->cells
[view
->col
++] = cell_unused
;
263 static void cursor_to(Cursor
*c
, size_t pos
) {
264 Text
*txt
= c
->view
->text
;
265 c
->mark_old
= c
->mark
;
266 c
->mark
= text_mark_set(txt
, pos
);
271 c
->sel
->cursor
= text_mark_set(txt
, pos
);
272 if (!view_coord_get(c
->view
, pos
, &c
->line
, &c
->row
, &c
->col
)) {
273 if (c
->view
->cursor
== c
) {
274 c
->line
= c
->view
->topline
;
280 // TODO: minimize number of redraws
284 bool view_coord_get(View
*view
, size_t pos
, Line
**retline
, int *retrow
, int *retcol
) {
285 int row
= 0, col
= 0;
286 size_t cur
= view
->start
;
287 Line
*line
= view
->topline
;
289 if (pos
< view
->start
|| pos
> view
->end
) {
290 if (retline
) *retline
= NULL
;
291 if (retrow
) *retrow
= -1;
292 if (retcol
) *retcol
= -1;
296 while (line
&& line
!= view
->lastline
&& cur
< pos
) {
297 if (cur
+ line
->len
> pos
)
305 int max_col
= MIN(view
->width
, line
->width
);
306 while (cur
< pos
&& col
< max_col
) {
307 cur
+= line
->cells
[col
].len
;
308 /* skip over columns occupied by the same character */
309 while (++col
< max_col
&& line
->cells
[col
].len
== 0);
312 line
= view
->bottomline
;
313 row
= view
->height
- 1;
316 if (retline
) *retline
= line
;
317 if (retrow
) *retrow
= row
;
318 if (retcol
) *retcol
= col
;
322 /* move the cursor to the character at pos bytes from the begining of the file.
323 * if pos is not in the current viewport, redraw the view to make it visible */
324 void view_cursor_to(View
*view
, size_t pos
) {
325 view_cursors_to(view
->cursor
, pos
);
328 /* redraw the complete with data starting from view->start bytes into the file.
329 * stop once the screen is full, update view->end, view->lastline */
330 void view_draw(View
*view
) {
332 /* read a screenful of text considering each character as 4-byte UTF character*/
333 const size_t size
= view
->width
* view
->height
* 4;
334 /* current buffer to work with */
336 /* remaining bytes to process in buffer */
337 size_t rem
= text_bytes_get(view
->text
, view
->start
, size
, text
);
338 /* NUL terminate text section */
340 /* absolute position of character currently being added to display */
341 size_t pos
= view
->start
;
342 /* current position into buffer from which to interpret a character */
344 /* start from known multibyte state */
345 mbstate_t mbstate
= { 0 };
347 Cell cell
= { .data
= "", .len
= 0, .width
= 0, }, prev_cell
= cell
;
351 /* current 'parsed' character' */
354 size_t len
= mbrtowc(&wchar
, cur
, rem
, &mbstate
);
355 if (len
== (size_t)-1 && errno
== EILSEQ
) {
356 /* ok, we encountered an invalid multibyte sequence,
357 * replace it with the Unicode Replacement Character
358 * (FFFD) and skip until the start of the next utf8 char */
359 for (len
= 1; rem
> len
&& !ISUTF8(cur
[len
]); len
++);
360 cell
= (Cell
){ .data
= "\xEF\xBF\xBD", .len
= len
, .width
= 1 };
361 } else if (len
== (size_t)-2) {
362 /* not enough bytes available to convert to a
363 * wide character. advance file position and read
364 * another junk into buffer.
366 rem
= text_bytes_get(view
->text
, pos
, size
, text
);
370 } else if (len
== 0) {
371 /* NUL byte encountered, store it and continue */
372 cell
= (Cell
){ .data
= "\x00", .len
= 1, .width
= 2 };
374 if (len
>= sizeof(cell
.data
))
375 len
= sizeof(cell
.data
)-1;
376 for (size_t i
= 0; i
< len
; i
++)
377 cell
.data
[i
] = cur
[i
];
378 cell
.data
[len
] = '\0';
380 cell
.width
= wcwidth(wchar
);
381 if (cell
.width
== -1)
385 if (cell
.width
== 0 && prev_cell
.len
+ cell
.len
< sizeof(cell
.data
)) {
386 prev_cell
.len
+= cell
.len
;
387 strcat(prev_cell
.data
, cell
.data
);
389 if (prev_cell
.len
&& !view_addch(view
, &prev_cell
))
391 pos
+= prev_cell
.len
;
398 memset(&cell
, 0, sizeof cell
);
401 if (prev_cell
.len
&& view_addch(view
, &prev_cell
))
402 pos
+= prev_cell
.len
;
404 /* set end of viewing region */
407 bool eof
= view
->end
== text_size(view
->text
);
408 if (view
->line
->len
== 0 && eof
&& view
->line
->prev
)
409 view
->lastline
= view
->line
->prev
;
411 view
->lastline
= view
->line
;
413 view
->lastline
= view
->bottomline
;
416 /* clear remaining of line, important to show cursor at end of file */
418 for (int x
= view
->col
; x
< view
->width
; x
++)
419 view
->line
->cells
[x
] = view
->cell_blank
;
422 /* resync position of cursors within visible area */
423 for (Cursor
*c
= view
->cursors
; c
; c
= c
->next
) {
424 size_t pos
= view_cursors_pos(c
);
425 if (!view_coord_get(view
, pos
, &c
->line
, &c
->row
, &c
->col
) &&
427 c
->line
= view
->topline
;
433 view
->need_update
= true;
436 void view_invalidate(View
*view
) {
437 view
->need_update
= true;
440 bool view_update(View
*view
) {
441 if (!view
->need_update
)
443 for (Line
*l
= view
->lastline
->next
; l
; l
= l
->next
) {
444 for (int x
= 0; x
< view
->width
; x
++)
445 l
->cells
[x
] = view
->cell_blank
;
447 view
->need_update
= false;
451 bool view_resize(View
*view
, int width
, int height
) {
456 if (view
->width
== width
&& view
->height
== height
) {
457 view
->need_update
= true;
460 size_t lines_size
= height
*(sizeof(Line
) + width
*sizeof(Cell
));
461 if (lines_size
> view
->lines_size
) {
462 Line
*lines
= realloc(view
->lines
, lines_size
);
466 view
->lines_size
= lines_size
;
469 view
->height
= height
;
470 memset(view
->lines
, 0, view
->lines_size
);
475 int view_height_get(View
*view
) {
479 int view_width_get(View
*view
) {
483 void view_free(View
*view
) {
486 while (view
->cursors
)
487 view_cursors_free(view
->cursors
);
488 while (view
->selections
)
489 view_selections_free(view
->selections
);
494 void view_reload(View
*view
, Text
*text
) {
496 view_selections_clear(view
);
497 view_cursor_to(view
, 0);
500 View
*view_new(Text
*text
) {
503 View
*view
= calloc(1, sizeof(View
));
506 if (!view_cursors_new(view
, 0)) {
511 view
->cell_blank
= (Cell
) {
518 view_options_set(view
, 0);
520 if (!view_resize(view
, 1, 1)) {
525 view_cursor_to(view
, 0);
530 void view_ui(View
*view
, UiWin
* ui
) {
534 static size_t cursor_set(Cursor
*cursor
, Line
*line
, int col
) {
536 View
*view
= cursor
->view
;
537 size_t pos
= view
->start
;
538 /* get row number and file offset at start of the given line */
539 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
) {
544 /* for characters which use more than 1 column, make sure we are on the left most */
545 while (col
> 0 && line
->cells
[col
].len
== 0)
547 /* calculate offset within the line */
548 for (int i
= 0; i
< col
; i
++)
549 pos
+= line
->cells
[i
].len
;
555 cursor_to(cursor
, pos
);
560 bool view_viewport_down(View
*view
, int n
) {
562 if (view
->end
>= text_size(view
->text
))
564 if (n
>= view
->height
) {
565 view
->start
= view
->end
;
567 for (line
= view
->topline
; line
&& n
> 0; line
= line
->next
, n
--)
568 view
->start
+= line
->len
;
574 bool view_viewport_up(View
*view
, int n
) {
575 /* scrolling up is somewhat tricky because we do not yet know where
576 * the lines start, therefore scan backwards but stop at a reasonable
577 * maximum in case we are dealing with a file without any newlines
579 if (view
->start
== 0)
581 size_t max
= view
->width
* view
->height
;
583 Iterator it
= text_iterator_get(view
->text
, view
->start
- 1);
585 if (!text_iterator_byte_get(&it
, &c
))
588 /* skip newlines immediately before display area */
589 if (c
== '\n' && text_iterator_byte_prev(&it
, &c
))
592 if (c
== '\n' && --n
== 0)
596 } while (text_iterator_byte_prev(&it
, &c
));
597 view
->start
-= MIN(view
->start
, off
);
602 void view_redraw_top(View
*view
) {
603 Line
*line
= view
->cursor
->line
;
604 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
)
605 view
->start
+= cur
->len
;
607 view_cursor_to(view
, view
->cursor
->pos
);
610 void view_redraw_center(View
*view
) {
611 int center
= view
->height
/ 2;
612 size_t pos
= view
->cursor
->pos
;
613 for (int i
= 0; i
< 2; i
++) {
615 Line
*line
= view
->cursor
->line
;
616 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
; cur
= cur
->next
)
618 if (linenr
< center
) {
619 view_slide_down(view
, center
- linenr
);
622 for (Line
*cur
= view
->topline
; cur
&& cur
!= line
&& linenr
> center
; cur
= cur
->next
) {
623 view
->start
+= cur
->len
;
629 view_cursor_to(view
, pos
);
632 void view_redraw_bottom(View
*view
) {
633 Line
*line
= view
->cursor
->line
;
634 if (line
== view
->lastline
)
636 size_t pos
= view
->cursor
->pos
;
637 view_viewport_up(view
, view
->height
);
638 while (pos
>= view
->end
&& view_viewport_down(view
, 1));
639 cursor_to(view
->cursor
, pos
);
642 size_t view_slide_up(View
*view
, int lines
) {
643 Cursor
*cursor
= view
->cursor
;
644 if (view_viewport_down(view
, lines
)) {
645 if (cursor
->line
== view
->topline
)
646 cursor_set(cursor
, view
->topline
, cursor
->col
);
648 view_cursor_to(view
, cursor
->pos
);
650 view_screenline_down(cursor
);
655 size_t view_slide_down(View
*view
, int lines
) {
656 Cursor
*cursor
= view
->cursor
;
657 bool lastline
= cursor
->line
== view
->lastline
;
658 size_t col
= cursor
->col
;
659 if (view_viewport_up(view
, lines
)) {
661 cursor_set(cursor
, view
->lastline
, col
);
663 view_cursor_to(view
, cursor
->pos
);
665 view_screenline_up(cursor
);
670 size_t view_scroll_up(View
*view
, int lines
) {
671 Cursor
*cursor
= view
->cursor
;
672 if (view_viewport_up(view
, lines
)) {
673 Line
*line
= cursor
->line
< view
->lastline
? cursor
->line
: view
->lastline
;
674 cursor_set(cursor
, line
, view
->cursor
->col
);
676 view_cursor_to(view
, 0);
681 size_t view_scroll_page_up(View
*view
) {
682 Cursor
*cursor
= view
->cursor
;
683 if (view
->start
== 0) {
684 view_cursor_to(view
, 0);
686 view_cursor_to(view
, view
->start
-1);
687 view_redraw_bottom(view
);
688 view_screenline_begin(cursor
);
693 size_t view_scroll_page_down(View
*view
) {
694 view_scroll_down(view
, view
->height
);
695 return view_screenline_begin(view
->cursor
);
698 size_t view_scroll_halfpage_up(View
*view
) {
699 Cursor
*cursor
= view
->cursor
;
700 if (view
->start
== 0) {
701 view_cursor_to(view
, 0);
703 view_cursor_to(view
, view
->start
-1);
704 view_redraw_center(view
);
705 view_screenline_begin(cursor
);
710 size_t view_scroll_halfpage_down(View
*view
) {
711 size_t end
= view
->end
;
712 size_t pos
= view_scroll_down(view
, view
->height
/2);
713 if (pos
< text_size(view
->text
))
714 view_cursor_to(view
, end
);
715 return view
->cursor
->pos
;
718 size_t view_scroll_down(View
*view
, int lines
) {
719 Cursor
*cursor
= view
->cursor
;
720 if (view_viewport_down(view
, lines
)) {
721 Line
*line
= cursor
->line
> view
->topline
? cursor
->line
: view
->topline
;
722 cursor_set(cursor
, line
, cursor
->col
);
724 view_cursor_to(view
, text_size(view
->text
));
729 size_t view_line_up(Cursor
*cursor
) {
730 View
*view
= cursor
->view
;
731 int lastcol
= cursor
->lastcol
;
733 lastcol
= cursor
->col
;
734 size_t pos
= text_line_up(cursor
->view
->text
, cursor
->pos
);
735 bool offscreen
= view
->cursor
== cursor
&& pos
< view
->start
;
736 view_cursors_to(cursor
, pos
);
738 view_redraw_top(view
);
740 cursor_set(cursor
, cursor
->line
, lastcol
);
741 cursor
->lastcol
= lastcol
;
745 size_t view_line_down(Cursor
*cursor
) {
746 View
*view
= cursor
->view
;
747 int lastcol
= cursor
->lastcol
;
749 lastcol
= cursor
->col
;
750 size_t pos
= text_line_down(cursor
->view
->text
, cursor
->pos
);
751 bool offscreen
= view
->cursor
== cursor
&& pos
> view
->end
;
752 view_cursors_to(cursor
, pos
);
754 view_redraw_bottom(view
);
756 cursor_set(cursor
, cursor
->line
, lastcol
);
757 cursor
->lastcol
= lastcol
;
761 size_t view_screenline_up(Cursor
*cursor
) {
763 return view_line_up(cursor
);
764 int lastcol
= cursor
->lastcol
;
766 lastcol
= cursor
->col
;
767 if (!cursor
->line
->prev
)
768 view_scroll_up(cursor
->view
, 1);
769 if (cursor
->line
->prev
)
770 cursor_set(cursor
, cursor
->line
->prev
, lastcol
);
771 cursor
->lastcol
= lastcol
;
775 size_t view_screenline_down(Cursor
*cursor
) {
777 return view_line_down(cursor
);
778 int lastcol
= cursor
->lastcol
;
780 lastcol
= cursor
->col
;
781 if (!cursor
->line
->next
&& cursor
->line
== cursor
->view
->bottomline
)
782 view_scroll_down(cursor
->view
, 1);
783 if (cursor
->line
->next
)
784 cursor_set(cursor
, cursor
->line
->next
, lastcol
);
785 cursor
->lastcol
= lastcol
;
789 size_t view_screenline_begin(Cursor
*cursor
) {
792 return cursor_set(cursor
, cursor
->line
, 0);
795 size_t view_screenline_middle(Cursor
*cursor
) {
798 return cursor_set(cursor
, cursor
->line
, cursor
->line
->width
/ 2);
801 size_t view_screenline_end(Cursor
*cursor
) {
804 int col
= cursor
->line
->width
- 1;
805 return cursor_set(cursor
, cursor
->line
, col
>= 0 ? col
: 0);
808 size_t view_cursor_get(View
*view
) {
809 return view_cursors_pos(view
->cursor
);
812 Line
*view_lines_first(View
*view
) {
813 return view
->topline
;
816 Line
*view_lines_last(View
*view
) {
817 return view
->lastline
;
820 Line
*view_cursors_line_get(Cursor
*c
) {
824 void view_scroll_to(View
*view
, size_t pos
) {
825 view_cursors_scroll_to(view
->cursor
, pos
);
828 void view_options_set(View
*view
, enum UiOption options
) {
829 const int mapping
[] = {
830 [SYNTAX_SYMBOL_SPACE
] = UI_OPTION_SYMBOL_SPACE
,
831 [SYNTAX_SYMBOL_TAB
] = UI_OPTION_SYMBOL_TAB
,
832 [SYNTAX_SYMBOL_TAB_FILL
] = UI_OPTION_SYMBOL_TAB_FILL
,
833 [SYNTAX_SYMBOL_EOL
] = UI_OPTION_SYMBOL_EOL
,
836 for (int i
= 0; i
< LENGTH(mapping
); i
++) {
837 view
->symbols
[i
] = (options
& mapping
[i
]) ? &symbols_default
[i
] :
841 if (options
& UI_OPTION_LINE_NUMBERS_ABSOLUTE
)
842 options
&= ~UI_OPTION_LARGE_FILE
;
844 view
->large_file
= (options
& UI_OPTION_LARGE_FILE
);
847 view
->ui
->options_set(view
->ui
, options
);
850 enum UiOption
view_options_get(View
*view
) {
851 return view
->ui
? view
->ui
->options_get(view
->ui
) : 0;
854 void view_colorcolumn_set(View
*view
, int col
) {
856 view
->colorcolumn
= col
;
859 int view_colorcolumn_get(View
*view
) {
860 return view
->colorcolumn
;
863 size_t view_screenline_goto(View
*view
, int n
) {
864 size_t pos
= view
->start
;
865 for (Line
*line
= view
->topline
; --n
> 0 && line
!= view
->lastline
; line
= line
->next
)
870 static Cursor
*cursors_new(View
*view
, size_t pos
, bool force
) {
871 Cursor
*c
= calloc(1, sizeof(*c
));
875 c
->generation
= view
->cursor_generation
;
876 if (!view
->cursors
) {
878 view
->cursor_latest
= c
;
880 view
->cursor_count
= 1;
884 Cursor
*prev
= NULL
, *next
= NULL
;
885 Cursor
*latest
= view
->cursor_latest
? view
->cursor_latest
: view
->cursor
;
886 size_t cur
= view_cursors_pos(latest
);
890 } else if (pos
> cur
) {
892 for (next
= prev
->next
; next
; prev
= next
, next
= next
->next
) {
893 cur
= view_cursors_pos(next
);
897 } else if (pos
< cur
) {
899 for (prev
= next
->prev
; prev
; next
= prev
, prev
= prev
->prev
) {
900 cur
= view_cursors_pos(prev
);
906 if (pos
== cur
&& !force
)
909 for (Cursor
*after
= next
; after
; after
= after
->next
)
918 c
->number
= prev
->number
+ 1;
922 view
->cursor_latest
= c
;
923 view
->cursor_count
++;
924 view_cursors_dispose(view
->cursor_dead
);
925 view_cursors_to(c
, pos
);
932 Cursor
*view_cursors_new(View
*view
, size_t pos
) {
933 return cursors_new(view
, pos
, false);
936 Cursor
*view_cursors_new_force(View
*view
, size_t pos
) {
937 return cursors_new(view
, pos
, true);
940 int view_cursors_count(View
*view
) {
941 return view
->cursor_count
;
944 int view_cursors_number(Cursor
*c
) {
948 int view_cursors_column_count(View
*view
) {
949 Text
*txt
= view
->text
;
950 int cpl_max
= 0, cpl
= 0; /* cursors per line */
951 size_t line_prev
= 0;
952 for (Cursor
*c
= view
->cursors
; c
; c
= c
->next
) {
953 size_t pos
= view_cursors_pos(c
);
954 size_t line
= text_lineno_by_pos(txt
, pos
);
955 if (line
== line_prev
)
966 static Cursor
*cursors_column_next(View
*view
, Cursor
*c
, int column
) {
969 Text
*txt
= view
->text
;
971 size_t pos
= view_cursors_pos(c
);
972 line_cur
= text_lineno_by_pos(txt
, pos
);
973 column_cur
= INT_MIN
;
978 for (; c
; c
= c
->next
) {
979 size_t pos
= view_cursors_pos(c
);
980 size_t line
= text_lineno_by_pos(txt
, pos
);
981 if (line
!= line_cur
) {
987 if (column
== column_cur
)
993 Cursor
*view_cursors_column(View
*view
, int column
) {
994 return cursors_column_next(view
, NULL
, column
);
997 Cursor
*view_cursors_column_next(Cursor
*c
, int column
) {
998 return cursors_column_next(c
->view
, c
, column
);
1001 bool view_cursors_multiple(View
*view
) {
1002 return view
->cursors
&& view
->cursors
->next
;
1005 static void view_cursors_free(Cursor
*c
) {
1008 register_release(&c
->reg
);
1009 for (Cursor
*after
= c
->next
; after
; after
= after
->next
)
1012 c
->prev
->next
= c
->next
;
1014 c
->next
->prev
= c
->prev
;
1015 if (c
->view
->cursors
== c
)
1016 c
->view
->cursors
= c
->next
;
1017 if (c
->view
->cursor
== c
)
1018 c
->view
->cursor
= c
->next
? c
->next
: c
->prev
;
1019 if (c
->view
->cursor_dead
== c
)
1020 c
->view
->cursor_dead
= NULL
;
1021 if (c
->view
->cursor_latest
== c
)
1022 c
->view
->cursor_latest
= c
->prev
? c
->prev
: c
->next
;
1023 c
->view
->cursor_count
--;
1027 bool view_cursors_dispose(Cursor
*c
) {
1030 View
*view
= c
->view
;
1031 if (!view
->cursors
|| !view
->cursors
->next
)
1033 view_selections_free(c
->sel
);
1034 view_cursors_free(c
);
1035 view_cursors_primary_set(view
->cursor
);
1039 bool view_cursors_dispose_force(Cursor
*c
) {
1040 if (view_cursors_dispose(c
))
1042 View
*view
= c
->view
;
1043 if (view
->cursor_dead
)
1045 view_cursors_selection_clear(c
);
1046 view
->cursor_dead
= c
;
1050 Cursor
*view_cursor_disposed(View
*view
) {
1051 Cursor
*c
= view
->cursor_dead
;
1052 view
->cursor_dead
= NULL
;
1056 Cursor
*view_cursors(View
*view
) {
1057 view
->cursor_generation
++;
1058 return view
->cursors
;
1061 Cursor
*view_cursors_primary_get(View
*view
) {
1062 view
->cursor_generation
++;
1063 return view
->cursor
;
1066 void view_cursors_primary_set(Cursor
*c
) {
1069 View
*view
= c
->view
;
1071 Filerange sel
= view_cursors_selection_get(c
);
1072 view_cursors_to(c
, view_cursors_pos(c
));
1073 view_cursors_selection_set(c
, &sel
);
1076 Cursor
*view_cursors_prev(Cursor
*c
) {
1077 View
*view
= c
->view
;
1078 for (c
= c
->prev
; c
; c
= c
->prev
) {
1079 if (c
->generation
!= view
->cursor_generation
)
1082 view
->cursor_generation
++;
1086 Cursor
*view_cursors_next(Cursor
*c
) {
1087 View
*view
= c
->view
;
1088 for (c
= c
->next
; c
; c
= c
->next
) {
1089 if (c
->generation
!= view
->cursor_generation
)
1092 view
->cursor_generation
++;
1096 size_t view_cursors_pos(Cursor
*c
) {
1097 size_t pos
= text_mark_get(c
->view
->text
, c
->mark
);
1098 return pos
!= EPOS
? pos
: text_mark_get(c
->view
->text
, c
->mark_old
);
1101 size_t view_cursors_line(Cursor
*c
) {
1102 size_t pos
= view_cursors_pos(c
);
1103 return text_lineno_by_pos(c
->view
->text
, pos
);
1106 size_t view_cursors_col(Cursor
*c
) {
1107 size_t pos
= view_cursors_pos(c
);
1108 return text_line_char_get(c
->view
->text
, pos
) + 1;
1111 int view_cursors_cell_get(Cursor
*c
) {
1112 return c
->line
? c
->col
: -1;
1115 int view_cursors_cell_set(Cursor
*c
, int cell
) {
1116 if (!c
->line
|| cell
< 0)
1118 cursor_set(c
, c
->line
, cell
);
1122 Register
*view_cursors_register(Cursor
*c
) {
1126 void view_cursors_scroll_to(Cursor
*c
, size_t pos
) {
1127 View
*view
= c
->view
;
1128 if (view
->cursor
== c
) {
1130 while (pos
< view
->start
&& view_viewport_up(view
, 1));
1131 while (pos
> view
->end
&& view_viewport_down(view
, 1));
1133 view_cursors_to(c
, pos
);
1136 void view_cursors_to(Cursor
*c
, size_t pos
) {
1137 View
*view
= c
->view
;
1140 size_t size
= text_size(view
->text
);
1143 if (c
->view
->cursor
== c
) {
1144 /* make sure we redraw changes to the very first character of the window */
1145 if (view
->start
== pos
)
1146 view
->start_last
= 0;
1148 if (view
->end
== pos
&& view
->lastline
== view
->bottomline
) {
1149 view
->start
+= view
->topline
->len
;
1153 if (pos
< view
->start
|| pos
> view
->end
) {
1155 view_viewport_up(view
, view
->height
/ 2);
1158 if (pos
<= view
->start
|| pos
> view
->end
) {
1159 view
->start
= text_line_begin(view
->text
, pos
);
1163 if (pos
<= view
->start
|| pos
> view
->end
) {
1172 void view_cursors_place(Cursor
*c
, size_t line
, size_t col
) {
1173 Text
*txt
= c
->view
->text
;
1174 size_t pos
= text_pos_by_lineno(txt
, line
);
1175 pos
= text_line_char_set(txt
, pos
, col
> 0 ? col
-1 : col
);
1176 view_cursors_to(c
, pos
);
1179 void view_cursors_selection_start(Cursor
*c
) {
1182 size_t pos
= view_cursors_pos(c
);
1183 if (pos
== EPOS
|| !(c
->sel
= view_selections_new(c
->view
, c
)))
1185 Text
*txt
= c
->view
->text
;
1186 c
->sel
->anchor
= text_mark_set(txt
, pos
);
1187 c
->sel
->cursor
= c
->sel
->anchor
;
1188 c
->view
->need_update
= true;
1191 void view_cursors_selection_restore(Cursor
*c
) {
1192 Text
*txt
= c
->view
->text
;
1195 Filerange sel
= text_range_new(
1196 text_mark_get(txt
, c
->lastsel_anchor
),
1197 text_mark_get(txt
, c
->lastsel_cursor
)
1199 if (!text_range_valid(&sel
))
1201 view_cursors_to(c
, sel
.end
);
1202 sel
.end
= text_char_next(txt
, sel
.end
);
1203 if (!(c
->sel
= view_selections_new(c
->view
, c
)))
1205 view_selections_set(c
->sel
, &sel
);
1208 void view_cursors_selection_stop(Cursor
*c
) {
1212 void view_cursors_selection_clear(Cursor
*c
) {
1213 view_selections_free(c
->sel
);
1214 c
->view
->need_update
= true;
1217 void view_cursors_selection_swap(Cursor
*c
) {
1220 view_selections_swap(c
->sel
);
1221 view_cursors_selection_sync(c
);
1224 void view_cursors_selection_sync(Cursor
*c
) {
1227 Text
*txt
= c
->view
->text
;
1228 size_t pos
= text_mark_get(txt
, c
->sel
->cursor
);
1229 view_cursors_to(c
, pos
);
1232 Filerange
view_cursors_selection_get(Cursor
*c
) {
1233 return view_selections_get(c
->sel
);
1236 void view_cursors_selection_set(Cursor
*c
, const Filerange
*r
) {
1237 if (!text_range_valid(r
))
1240 if (new && !(c
->sel
= view_selections_new(c
->view
, c
)))
1242 view_selections_set(c
->sel
, r
);
1244 size_t pos
= view_cursors_pos(c
);
1245 if (!text_range_contains(r
, pos
))
1246 view_cursors_selection_sync(c
);
1250 Selection
*view_selections_new(View
*view
, Cursor
*c
) {
1251 Selection
*s
= calloc(1, sizeof(*s
));
1255 s
->next
= view
->selections
;
1256 if (view
->selections
)
1257 view
->selections
->prev
= s
;
1258 view
->selections
= s
;
1263 void view_selections_free(Selection
*s
) {
1267 s
->prev
->next
= s
->next
;
1269 s
->next
->prev
= s
->prev
;
1270 if (s
->view
->selections
== s
)
1271 s
->view
->selections
= s
->next
;
1274 c
->lastsel_anchor
= s
->anchor
;
1275 c
->lastsel_cursor
= s
->cursor
;
1281 void view_selections_clear(View
*view
) {
1282 while (view
->selections
)
1283 view_selections_free(view
->selections
);
1287 void view_cursors_clear(View
*view
) {
1288 for (Cursor
*c
= view
->cursors
, *next
; c
; c
= next
) {
1290 if (c
!= view
->cursor
) {
1291 view_selections_free(c
->sel
);
1292 view_cursors_free(c
);
1298 void view_selections_swap(Selection
*s
) {
1299 Mark temp
= s
->anchor
;
1300 s
->anchor
= s
->cursor
;
1304 Selection
*view_selections(View
*view
) {
1305 return view
->selections
;
1308 Selection
*view_selections_prev(Selection
*s
) {
1312 Selection
*view_selections_next(Selection
*s
) {
1316 Filerange
view_selection_get(View
*view
) {
1317 return view_selections_get(view
->cursor
->sel
);
1320 Filerange
view_selections_get(Selection
*s
) {
1322 return text_range_empty();
1323 Text
*txt
= s
->view
->text
;
1324 size_t anchor
= text_mark_get(txt
, s
->anchor
);
1325 size_t cursor
= text_mark_get(txt
, s
->cursor
);
1326 Filerange sel
= text_range_new(anchor
, cursor
);
1327 if (text_range_valid(&sel
))
1328 sel
.end
= text_char_next(txt
, sel
.end
);
1332 void view_selections_set(Selection
*s
, const Filerange
*r
) {
1333 if (!text_range_valid(r
))
1335 Text
*txt
= s
->view
->text
;
1336 size_t anchor
= text_mark_get(txt
, s
->anchor
);
1337 size_t cursor
= text_mark_get(txt
, s
->cursor
);
1338 bool left_extending
= anchor
!= EPOS
&& anchor
> cursor
;
1339 size_t end
= r
->end
;
1340 if (r
->start
!= end
)
1341 end
= text_char_prev(txt
, end
);
1342 if (left_extending
) {
1343 s
->anchor
= text_mark_set(txt
, end
);
1344 s
->cursor
= text_mark_set(txt
, r
->start
);
1346 s
->anchor
= text_mark_set(txt
, r
->start
);
1347 s
->cursor
= text_mark_set(txt
, end
);
1349 s
->view
->need_update
= true;
1352 Text
*view_text(View
*view
) {
1356 bool view_style_define(View
*view
, enum UiStyle id
, const char *style
) {
1357 return view
->ui
->style_define(view
->ui
, id
, style
);
1360 void view_style(View
*view
, enum UiStyle style_id
, size_t start
, size_t end
) {
1361 if (end
< view
->start
|| start
> view
->end
)
1364 CellStyle style
= view
->ui
->style_get(view
->ui
, style_id
);
1365 size_t pos
= view
->start
;
1366 Line
*line
= view
->topline
;
1368 /* skip lines before range to be styled */
1369 while (line
&& pos
+ line
->len
<= start
) {
1377 int col
= 0, width
= view
->width
;
1379 /* skip columns before range to be styled */
1380 while (pos
< start
&& col
< width
)
1381 pos
+= line
->cells
[col
++].len
;
1384 while (pos
<= end
&& col
< width
) {
1385 pos
+= line
->cells
[col
].len
;
1386 line
->cells
[col
++].style
= style
;
1389 } while (pos
<= end
&& (line
= line
->next
));