dte test coverage


Directory: ./
File: src/view.c
Date: 2025-11-12 12:04:10
Coverage Exec Excl Total
Lines: 71.3% 117 1 165
Functions: 92.3% 12 0 13
Branches: 45.0% 45 0 100

Line Branch Exec Source
1 #include "view.h"
2 #include "block.h"
3 #include "buffer.h"
4 #include "indent.h"
5 #include "util/ascii.h"
6 #include "util/debug.h"
7 #include "util/numtostr.h"
8 #include "util/str-util.h"
9 #include "util/time-util.h"
10 #include "util/utf8.h"
11 #include "window.h"
12
13 475 void view_update_cursor_y(View *view)
14 {
15 475 const Buffer *buffer = view->buffer;
16 475 const Block *blk;
17 475 size_t nl = 0;
18
1/2
✓ Branch 7 → 3 taken 475 times.
✗ Branch 7 → 8 not taken.
475 block_for_each(blk, &buffer->blocks) {
19
1/2
✓ Branch 3 → 4 taken 475 times.
✗ Branch 3 → 6 not taken.
475 if (blk == view->cursor.blk) {
20 475 nl += count_nl(blk->data, view->cursor.offset);
21 475 view->cy = nl;
22 475 return;
23 }
24 nl += blk->nl;
25 }
26 BUG("unreachable");
27 }
28
29 29 void view_update_cursor_x(View *view)
30 {
31 29 const unsigned int tw = view->buffer->options.tab_width;
32 29 const CurrentLineRef lr = get_current_line_and_offset(view->cursor);
33 29 const size_t cx = lr.cursor_offset;
34 29 long cx_char = 0;
35 29 long w = 0;
36
37
2/2
✓ Branch 14 → 4 taken 79 times.
✓ Branch 14 → 15 taken 29 times.
108 for (size_t idx = 0; idx < cx; cx_char++) {
38 79 unsigned char ch = lr.line.data[idx];
39
1/2
✓ Branch 4 → 5 taken 79 times.
✗ Branch 4 → 11 not taken.
79 if (likely(ch < 0x80)) {
40 79 idx++;
41
1/2
✓ Branch 5 → 6 taken 79 times.
✗ Branch 5 → 7 not taken.
79 if (likely(!ascii_iscntrl(ch))) {
42 79 w++;
43 } else if (ch == '\t') {
44 w = next_indent_width(w, tw);
45 } else {
46 w += 2;
47 }
48 } else {
49 CodePoint u = u_get_nonascii(lr.line.data, lr.line.length, &idx);
50 w += u_char_width(u);
51 }
52 }
53
54 29 view->cx = cx;
55 29 view->cx_char = cx_char;
56 29 view->cx_display = w;
57 29 }
58
59 1 static bool view_is_cursor_visible(const View *v)
60 {
61
2/4
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 5 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 return v->cy < v->vy || v->cy > v->vy + v->window->edit_h - 1;
62 }
63
64 1 static void view_center_to_cursor(View *v)
65 {
66 1 size_t lines = v->buffer->nl;
67 1 Window *window = v->window;
68 1 unsigned int hh = window->edit_h / 2;
69
70
2/4
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 4 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
1 if (window->edit_h >= lines || v->cy < hh) {
71 v->vy = 0;
72 return;
73 }
74
75 1 v->vy = v->cy - hh;
76
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 1 time.
1 if (v->vy + window->edit_h > lines) {
77 // -1 makes one ~ line visible so that you know where the EOF is
78 v->vy -= v->vy + window->edit_h - lines - 1;
79 }
80 }
81
82 1 static void view_update_vx(View *v)
83 {
84 1 Window *window = v->window;
85 1 unsigned int c = 8;
86
87
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
1 if (v->cx_display - v->vx >= window->edit_w) {
88 v->vx = (v->cx_display - window->edit_w + c) / c * c;
89 }
90
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
1 if (v->cx_display < v->vx) {
91 v->vx = v->cx_display / c * c;
92 }
93 1 }
94
95 static void view_update_vy(View *v)
96 {
97 Window *window = v->window;
98 long margin = window_get_scroll_margin(window);
99 long max_y = v->vy + window->edit_h - 1 - margin;
100
101 if (v->cy < v->vy + margin) {
102 v->vy = MAX(v->cy - margin, 0);
103 } else if (v->cy > max_y) {
104 v->vy += v->cy - max_y;
105 max_y = v->buffer->nl - window->edit_h + 1;
106 if (v->vy > max_y && max_y >= 0) {
107 v->vy = max_y;
108 }
109 }
110 }
111
112 1 void view_update(View *v)
113 {
114 1 view_update_cursor_x(v);
115 1 view_update_cursor_y(v);
116 1 view_update_vx(v);
117
3/6
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 9 not taken.
1 if (v->force_center || (v->center_on_scroll && view_is_cursor_visible(v))) {
118 1 view_center_to_cursor(v);
119 } else {
120 view_update_vy(v);
121 }
122 1 v->force_center = false;
123 1 v->center_on_scroll = false;
124 1 }
125
126 60 long view_get_preferred_x(View *v)
127 {
128
2/2
✓ Branch 2 → 3 taken 28 times.
✓ Branch 2 → 5 taken 32 times.
60 if (v->preferred_x < 0) {
129 28 view_update_cursor_x(v);
130 28 v->preferred_x = v->cx_display;
131 }
132 60 return v->preferred_x;
133 }
134
135 24 bool view_can_close(const View *view)
136 {
137 24 const Buffer *buffer = view->buffer;
138
4/4
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 5 taken 19 times.
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 5 taken 1 time.
24 return !buffer_modified(buffer) || buffer->views.count > 1;
139 }
140
141 // Like window_remove_view(), but searching for the index of `view` in
142 // `view->window->views` by pointer
143 26 size_t view_remove(View *view)
144 {
145 26 Window *window = view->window;
146 26 size_t view_idx = ptr_array_xindex(&window->views, view);
147 26 window_remove_view_at_index(window, view_idx);
148 26 return view_idx;
149 }
150
151 4 WordBounds get_bounds_for_word_under_cursor(CurrentLineRef lr)
152 {
153 4 const size_t len = lr.line.length;
154 4 size_t si = lr.cursor_offset;
155 4 BUG_ON(si > len);
156
157 // Move right, until over a word char (if not already)
158
1/2
✓ Branch 8 → 4 taken 4 times.
✗ Branch 8 → 9 not taken.
4 while (si < len) {
159 4 size_t i = si;
160
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 4 times.
4 if (u_is_word_char(u_get_char(lr.line.data, len, &i))) {
161 break;
162 }
163 si = i;
164 }
165
166
1/2
✗ Branch 9 → 10 not taken.
✓ Branch 9 → 15 taken 4 times.
4 if (si == len) {
167 // No word char between cursor and EOL; no word
168 return (WordBounds){0};
169 }
170
171 // Move left, to start of word (if cursor is already within one)
172 8 size_t ei = si;
173
2/2
✓ Branch 15 → 11 taken 4 times.
✓ Branch 15 → 20 taken 4 times.
8 while (si) {
174 4 size_t i = si;
175
1/2
✓ Branch 12 → 13 taken 4 times.
✗ Branch 12 → 14 not taken.
4 if (!u_is_word_char(u_prev_char(lr.line.data, &i))) {
176 break;
177 }
178 4 si = i;
179 }
180
181 // Move right, to end of word
182
2/2
✓ Branch 21 → 16 taken 4114 times.
✓ Branch 21 → 22 taken 4 times.
4118 while (ei < len) {
183 4114 size_t i = ei;
184
1/2
✓ Branch 17 → 18 taken 4114 times.
✗ Branch 17 → 19 not taken.
4114 if (!u_is_word_char(u_get_char(lr.line.data, len, &i))) {
185 break;
186 }
187 4114 ei = i;
188 }
189
190 4 bool empty = (si == ei);
191 4 BUG_ON(si > ei);
192
193 4 return (WordBounds) {
194
1/2
✓ Branch 24 → 25 taken 4 times.
✗ Branch 24 → 26 not taken.
4 .start = empty ? 0 : si,
195
1/2
✓ Branch 26 → 27 taken 4 times.
✗ Branch 26 → 28 not taken.
4 .end = empty ? 0 : ei,
196 };
197 }
198
199 4 StringView view_get_word_under_cursor(const View *view)
200 {
201 4 CurrentLineRef lr = get_current_line_and_offset(view->cursor);
202 4 WordBounds wb = get_bounds_for_word_under_cursor(lr);
203
1/2
✓ Branch 4 → 5 taken 4 times.
✗ Branch 4 → 6 not taken.
4 return string_view(lr.line.data + wb.start, wb.end ? wb.end - wb.start : 0);
204 }
205
206 1 String dump_buffer(const View *view)
207 {
208 1 const Buffer *buffer = view->buffer;
209 1 uintmax_t counts[2];
210 1 char sizestr[FILESIZE_STR_MAX];
211 1 buffer_count_blocks_and_bytes(buffer, counts);
212 1 BUG_ON(counts[0] < 1);
213 1 BUG_ON(!buffer->setup);
214 1 String buf = string_new(1024 + (DEBUG ? 24 * counts[0] : 0));
215
216 2 string_sprintf (
217 &buf,
218 "%s %s\n%s %lu\n%s %s\n%s %s\n%s %ju\n%s %zu\n%s %s\n",
219 " Name:", buffer_filename(buffer),
220 1 " ID:", buffer->id,
221 1 " Encoding:", buffer->encoding,
222 1 " Filetype:", buffer->options.filetype,
223 " Blocks:", counts[0],
224 1 " Lines:", buffer->nl,
225 " Size:", filesize_to_str(counts[1], sizestr)
226 );
227
228 1 if (
229
3/6
✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 17 not taken.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 17 not taken.
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 17 not taken.
1 buffer->stdout_buffer || buffer->temporary || buffer->readonly
230
3/6
✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 17 not taken.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 30 taken 1 time.
1 || buffer->locked || buffer->crlf_newlines || buffer->bom
231 ) {
232 string_sprintf (
233 &buf,
234 " Flags:%s%s%s%s%s%s\n",
235 buffer->stdout_buffer ? " STDOUT" : "",
236 buffer->temporary ? " TMP" : "",
237 buffer->readonly ? " RO" : "",
238 buffer->locked ? " LOCKED" : "",
239 buffer->crlf_newlines ? " CRLF" : "",
240 buffer->bom ? " BOM" : ""
241 );
242 }
243
244
1/2
✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 1 time.
1 if (buffer->views.count > 1) {
245 string_sprintf(&buf, " Views: %zu\n", buffer->views.count);
246 }
247
248
1/2
✗ Branch 32 → 33 not taken.
✓ Branch 32 → 40 taken 1 time.
1 if (buffer->abs_filename) {
249 const FileInfo *file = &buffer->file;
250 const struct timespec *mtime = &file->mtime;
251 unsigned int perms = file->mode & 07777;
252 char tstr[TIME_STR_BUFSIZE];
253 char modestr[12];
254 string_sprintf (
255 &buf,
256 "\nLast stat:\n----------\n\n"
257 "%s %s\n%s %s\n%s -%s (%04o)\n%s %jd\n%s %jd\n%s %s\n%s %jd\n%s %ju\n",
258 " Path:", buffer->abs_filename,
259 " Modified:", timespec_to_str(mtime, tstr) ? tstr : "-",
260 " Mode:", file_permissions_to_str(file->mode, modestr), perms,
261 " User:", (intmax_t)file->uid,
262 " Group:", (intmax_t)file->gid,
263 " Size:", filesize_to_str(file->size, sizestr),
264 " Device:", (intmax_t)file->dev,
265 " Inode:", (uintmax_t)file->ino
266 );
267 }
268
269 1 if (DEBUG >= 1) {
270 1 const BlockIter *cursor = &view->cursor;
271 1 string_append_cstring(&buf, "\nBlocks:\n-------\n\n");
272 1 size_t i = 1;
273 1 const Block *b;
274
2/2
✓ Branch 47 → 42 taken 1 time.
✓ Branch 47 → 48 taken 1 time.
2 block_for_each(b, &buffer->blocks) {
275 1 string_sprintf(&buf, "%4zu: %zu/%zu nl=%zu", i++, b->size, b->alloc, b->nl);
276
1/2
✓ Branch 43 → 44 taken 1 time.
✗ Branch 43 → 45 not taken.
1 if (b == cursor->blk) {
277 1 string_sprintf(&buf, " (cursor; offset=%zu)", cursor->offset);
278 }
279 1 string_append_byte(&buf, '\n');
280 }
281 }
282
283 1 return buf;
284 }
285