dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 93.0% 252 / 0 / 271
Functions: 100.0% 32 / 0 / 32
Branches: 81.7% 98 / 10 / 130

src/window.c
Line Branch Exec Source
1 #include <errno.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include "window.h"
5 #include "command/error.h"
6 #include "editor.h"
7 #include "file-history.h"
8 #include "load-save.h"
9 #include "lock.h"
10 #include "move.h"
11 #include "util/path.h"
12 #include "util/strtonum.h"
13 #include "util/xmalloc.h"
14 #include "util/xstring.h"
15
16 13 Window *new_window(EditorState *e)
17 {
18 13 Window *window = xcalloc1(sizeof(Window));
19 13 window->editor = e;
20 13 return window;
21 }
22
23 86 View *window_add_buffer(Window *window, Buffer *buffer)
24 {
25 // We rely on this being 0, for implicit initialization of
26 // View::selection and View::select_mode
27 86 static_assert(SELECT_NONE == 0);
28
29 86 View *view = xmalloc(sizeof(*view));
30 86 *view = (View) {
31 .buffer = buffer,
32 .window = window,
33 86 .cursor = block_iter(buffer),
34 };
35
36 86 ptr_array_append(&buffer->views, view);
37 86 ptr_array_append(&window->views, view);
38 86 window->update_tabbar = true;
39 86 return view;
40 }
41
42 51 View *window_open_empty_buffer(Window *window)
43 {
44 51 EditorState *e = window->editor;
45 51 return window_add_buffer(window, open_empty_buffer(&e->buffers, &e->options));
46 }
47
48 29 View *window_open_buffer (
49 Window *window,
50 const char *filename,
51 bool must_exist,
52 const char *encoding
53 ) {
54 29 ErrorBuffer *ebuf = &window->editor->err;
55
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 5 taken 28 times.
29 if (unlikely(filename[0] == '\0')) {
56 1 error_msg(ebuf, "Empty filename not allowed");
57 1 return NULL;
58 }
59
60 28 char *absolute = path_absolute(filename);
61
2/2
✓ Branch 6 → 7 taken 1 time.
✓ Branch 6 → 11 taken 27 times.
28 if (!absolute) {
62 1 bool nodir = (errno == ENOENT); // New file in non-existing dir (usually a mistake)
63
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 1 time.
1 const char *err = nodir ? "Directory does not exist" : strerror(errno);
64 1 error_msg(ebuf, "Error opening %s: %s", filename, err);
65 1 return NULL;
66 }
67
68 27 EditorState *e = window->editor;
69 27 Buffer *buffer = find_buffer(&e->buffers, absolute);
70
1/2
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 19 taken 27 times.
27 if (buffer) {
71 // File already open in editor
72 if (!streq(absolute, buffer->abs_filename)) {
73 const char *bufname = buffer_filename(buffer);
74 char *s = short_filename(absolute, e->home_dir);
75 info_msg(ebuf, "%s and %s are the same file", s, bufname); // Hard links
76 free(s);
77 }
78 free(absolute);
79 return window_find_or_create_view(window, buffer);
80 }
81
82 27 buffer = buffer_new(&e->buffers, &e->options, encoding);
83
2/2
✓ Branch 21 → 22 taken 1 time.
✓ Branch 21 → 24 taken 26 times.
27 if (!load_buffer(buffer, filename, &e->options, &e->err, must_exist)) {
84 1 buffer_remove_unlock_and_free(&e->buffers, buffer, &e->err, &e->locks_ctx);
85 1 free(absolute);
86 1 return NULL;
87 }
88
89 26 BUG_ON(!absolute);
90 26 BUG_ON(!path_is_absolute(absolute));
91 26 buffer->abs_filename = absolute;
92 26 buffer_update_short_filename(buffer, e->home_dir);
93
94
1/2
✗ Branch 27 → 28 not taken.
✓ Branch 27 → 32 taken 26 times.
26 if (e->options.lock_files) {
95 if (!lock_file(&e->locks_ctx, ebuf, buffer->abs_filename)) {
96 buffer->readonly = true;
97 } else {
98 buffer->locked = true;
99 }
100 }
101
102
3/6
✓ Branch 32 → 33 taken 26 times.
✗ Branch 32 → 38 not taken.
✓ Branch 33 → 34 taken 26 times.
✗ Branch 33 → 38 not taken.
✗ Branch 35 → 36 not taken.
✓ Branch 35 → 38 taken 26 times.
26 if (buffer->file.mode != 0 && !buffer->readonly && access(filename, W_OK)) {
103 info_msg(ebuf, "No write permission to %s, marking read-only", filename);
104 buffer->readonly = true;
105 }
106
107 26 return window_add_buffer(window, buffer);
108 }
109
110 1 static View *window_find_view(Window *window, Buffer *buffer)
111 {
112
1/2
✓ Branch 5 → 3 taken 1 time.
✗ Branch 5 → 6 not taken.
1 for (size_t i = 0, n = buffer->views.count; i < n; i++) {
113 1 View *view = buffer->views.ptrs[i];
114
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 6 taken 1 time.
1 if (view->window == window) {
115 return view;
116 }
117 }
118
119 // Buffer isn't open in this window
120 return NULL;
121 }
122
123 1 View *window_find_or_create_view(Window *window, Buffer *buffer)
124 {
125 1 View *view = window_find_view(window, buffer);
126
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 6 taken 1 time.
1 if (view) {
127 return view;
128 }
129
130 // `buffer` isn't open in this window; create a new view of it
131 view = window_add_buffer(window, buffer);
132 view->cursor = buffer_get_first_view(buffer)->cursor;
133 return view;
134 }
135
136 4 size_t window_count_uncloseable_views(const Window *window, View **first_uncloseable)
137 {
138 4 const View *current_view = window->view;
139 4 View *first = NULL;
140 4 void **ptrs = window->views.ptrs;
141 4 size_t nr_uncloseable = 0;
142
143
2/2
✓ Branch 9 → 3 taken 6 times.
✓ Branch 9 → 10 taken 4 times.
10 for (size_t i = 0, n = window->views.count; i < n; i++) {
144 6 View *view = ptrs[i];
145
2/2
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 6 taken 2 times.
6 if (view_can_close(view)) {
146 4 continue;
147 }
148 2 nr_uncloseable++;
149 // Always use the current view as the out-param, if uncloseable
150
1/2
✓ Branch 6 → 7 taken 2 times.
✗ Branch 6 → 8 not taken.
2 if (!first || view == current_view) {
151 2 first = view;
152 }
153 }
154
155 4 *first_uncloseable = first;
156 4 return nr_uncloseable;
157 }
158
159 // Remove the View found at `window->views.ptrs[view_idx]`, then update
160 // history/locks/etc. (if applicable) and free the allocation
161 86 void window_remove_view_at_index(Window *window, size_t view_idx)
162 {
163 86 View *view = ptr_array_remove_index(&window->views, view_idx);
164
2/2
✓ Branch 3 → 4 taken 2 times.
✓ Branch 3 → 5 taken 84 times.
86 if (view == window->prev_view) {
165 2 window->prev_view = NULL;
166 }
167
168 86 EditorState *e = window->editor;
169
2/2
✓ Branch 5 → 6 taken 39 times.
✓ Branch 5 → 7 taken 47 times.
86 if (view == e->view) {
170 39 e->view = NULL;
171 39 e->buffer = NULL;
172 }
173
174 86 Buffer *buffer = view->buffer;
175 86 ptr_array_remove(&buffer->views, view);
176
177
2/2
✓ Branch 8 → 9 taken 85 times.
✓ Branch 8 → 13 taken 1 time.
86 if (buffer->views.count == 0) {
178
4/4
✓ Branch 9 → 10 taken 84 times.
✓ Branch 9 → 12 taken 1 time.
✓ Branch 10 → 11 taken 45 times.
✓ Branch 10 → 12 taken 39 times.
85 if (buffer->options.file_history && buffer->abs_filename) {
179 45 const char *path = buffer->abs_filename;
180 45 FileHistory *hist = &e->file_history;
181 45 file_history_append(hist, view->cy + 1, view->cx_char + 1, path);
182 }
183 85 buffer_remove_unlock_and_free(&e->buffers, buffer, &e->err, &e->locks_ctx);
184 }
185
186 86 window->update_tabbar = true;
187 86 free(view);
188 86 }
189
190 14 static void window_remove_views(Window *window)
191 {
192 // This loop terminates when `0--` wraps around to SIZE_MAX
193
2/2
✓ Branch 5 → 3 taken 58 times.
✓ Branch 5 → 6 taken 14 times.
72 for (size_t n = window->views.count, i = n - 1; i < n; i--) {
194 58 window_remove_view_at_index(window, i);
195 }
196 14 }
197
198 // NOTE: window->frame isn't removed
199 13 void window_free(Window *window)
200 {
201 13 window_remove_views(window);
202 13 ptr_array_free_array(&window->views);
203 13 window->frame = NULL;
204 13 free(window);
205 13 }
206
207 28 void window_close_current_view(Window *window)
208 {
209 28 size_t idx = view_remove(window->view);
210
2/2
✓ Branch 3 → 4 taken 22 times.
✓ Branch 3 → 5 taken 6 times.
28 if (window->prev_view) {
211 22 window->view = window->prev_view;
212 22 window->prev_view = NULL;
213 22 return;
214 }
215
216 6 const PointerArray *views = &window->views;
217
2/2
✓ Branch 5 → 6 taken 1 time.
✓ Branch 5 → 7 taken 5 times.
6 if (views->count == 0) {
218 1 window_open_empty_buffer(window);
219 }
220
221 6 idx -= (views->count == idx);
222 6 window->view = views->ptrs[idx];
223 }
224
225 4 static void restore_cursor_from_history(const FileHistory *hist, View *view)
226 {
227 4 unsigned long row, col;
228
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 3 times.
4 if (file_history_find(hist, view->buffer->abs_filename, &row, &col)) {
229 1 move_to_filepos(view, row, col);
230 }
231 4 }
232
233 // Make `view` the current View in the editor, so that it takes input
234 // focus and becomes the active tab in its Window
235 98 void set_view(View *view)
236 {
237 98 EditorState *e = view->window->editor;
238
2/2
✓ Branch 2 → 3 taken 88 times.
✓ Branch 2 → 19 taken 10 times.
98 if (e->view == view) {
239 return;
240 }
241
242 // Forget `prev_view` when switching views. cmd_open(), cmd_reopen(),
243 // cmd_show() and cmd_exec() subsequently set it as appropriate, via
244 // window_open_new_file() or maybe_set_view().
245
2/2
✓ Branch 3 → 4 taken 86 times.
✓ Branch 3 → 5 taken 2 times.
88 if (e->window) {
246 86 e->window->prev_view = NULL;
247 }
248
249 88 e->view = view;
250 88 e->buffer = view->buffer;
251 88 e->window = view->window; // Parent Window becomes current Window
252 88 e->window->view = view;
253
254
2/2
✓ Branch 5 → 6 taken 61 times.
✓ Branch 5 → 10 taken 27 times.
88 if (!view->buffer->setup) {
255 // Run deferred initialization for view->buffer. This is done when
256 // buffers are first viewed instead of when they're opened, so as
257 // to allow opening many files without a sudden flood of extra work
258 // (e.g. loading multiple dte-syntax(5) files).
259 61 buffer_setup(e, view->buffer);
260
3/4
✓ Branch 7 → 8 taken 61 times.
✗ Branch 7 → 10 not taken.
✓ Branch 8 → 9 taken 4 times.
✓ Branch 8 → 10 taken 57 times.
61 if (view->buffer->options.file_history && view->buffer->abs_filename) {
261 4 restore_cursor_from_history(&e->file_history, view);
262 }
263 }
264
265 // view.cursor can be invalid if same buffer was modified from another view
266
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 13 taken 87 times.
88 if (view->restore_cursor) {
267 1 view->cursor.blk = buffer_get_first_block(view->buffer);
268 1 block_iter_goto_offset(&view->cursor, view->saved_cursor_offset);
269 1 view->restore_cursor = false;
270 1 view->saved_cursor_offset = 0;
271 }
272
273 // Save cursor states of views sharing same buffer
274 88 void **ptrs = view->buffer->views.ptrs;
275
2/2
✓ Branch 18 → 14 taken 89 times.
✓ Branch 18 → 19 taken 88 times.
177 for (size_t i = 0, n = view->buffer->views.count; i < n; i++) {
276 89 View *other = ptrs[i];
277
2/2
✓ Branch 14 → 15 taken 1 time.
✓ Branch 14 → 17 taken 88 times.
89 if (other != view) {
278 1 other->saved_cursor_offset = block_iter_get_offset(&other->cursor);
279 1 other->restore_cursor = true;
280 }
281 }
282 }
283
284 44 View *window_open_new_file(Window *window)
285 {
286 44 View *prev = window->view;
287 44 View *view = window_open_empty_buffer(window);
288 44 set_view(view);
289 44 window->prev_view = prev;
290 44 return view;
291 }
292
293 3 static bool buffer_is_untouched(const Buffer *b)
294 {
295
5/6
✓ Branch 2 → 3 taken 2 times.
✓ Branch 2 → 5 taken 1 time.
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 5 taken 1 time.
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
3 return !b->abs_filename && b->change_head.nr_prev == 0 && !b->display_filename;
296 }
297
298 6 static View *maybe_set_view(Window *window, View *view, View *prev, bool set_prev)
299 {
300
2/2
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 12 taken 3 times.
6 if (view && view != prev) {
301
3/4
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 1 time.
3 bool useless_prev = prev && buffer_is_untouched(prev->buffer);
302 3 set_view(view);
303
3/4
✓ Branch 7 → 8 taken 1 time.
✓ Branch 7 → 10 taken 2 times.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 10 taken 1 time.
3 if (useless_prev && window->views.count == 2) {
304 // If window contains only one untouched buffer it'll be closed
305 // after opening another view. This is done because closing the
306 // last view (with window_close_current_view()) causes an empty
307 // view to be opened (windows must contain at least one buffer).
308 view_remove(prev);
309
2/2
✓ Branch 10 → 11 taken 2 times.
✓ Branch 10 → 12 taken 1 time.
3 } else if (set_prev) {
310 2 window->prev_view = prev;
311 }
312 }
313 6 return view;
314 }
315
316 1 View *window_open_file(Window *window, const char *filename, const char *encoding)
317 {
318 1 View *prev = window->view;
319 1 View *view = window_open_buffer(window, filename, false, encoding);
320 1 return maybe_set_view(window, view, prev, true);
321 }
322
323 // Open multiple files in window and return the first opened View
324 5 View *window_open_files(Window *window, char **filenames, const char *encoding)
325 {
326 5 View *prev = window->view;
327 5 View *first = NULL;
328 5 size_t prev_nr_views = window->views.count;
329
330
2/2
✓ Branch 7 → 3 taken 27 times.
✓ Branch 7 → 8 taken 5 times.
32 for (size_t i = 0; filenames[i]; i++) {
331 27 View *view = window_open_buffer(window, filenames[i], false, encoding);
332
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 6 taken 25 times.
27 if (view && !first) {
333 2 first = view;
334 }
335 }
336
337 // `Window::prev_view` is set only if a single new view was opened
338 5 size_t nr_new_views = window->views.count - prev_nr_views;
339 5 return maybe_set_view(window, first, prev, nr_new_views == 1);
340 }
341
342 22 void buffer_mark_tabbars_changed(Buffer *buffer)
343 {
344
2/2
✓ Branch 4 → 3 taken 23 times.
✓ Branch 4 → 5 taken 22 times.
45 for (size_t i = 0, n = buffer->views.count; i < n; i++) {
345 23 View *view = buffer->views.ptrs[i];
346 23 view->window->update_tabbar = true;
347 }
348 22 }
349
350 62 static unsigned int line_numbers_width(const GlobalOptions *options, const View *view)
351 {
352
4/4
✓ Branch 2 → 3 taken 9 times.
✓ Branch 2 → 5 taken 53 times.
✓ Branch 3 → 4 taken 6 times.
✓ Branch 3 → 5 taken 3 times.
62 if (!options->show_line_numbers || !view) {
353 return 0;
354 }
355 6 unsigned int width = size_str_width(view->buffer->nl) + 1;
356 6 return MAX(width, LINE_NUMBERS_MIN_WIDTH);
357 }
358
359 41 static unsigned int edit_x_offset(const GlobalOptions *options, const View *view)
360 {
361 41 return line_numbers_width(options, view);
362 }
363
364 41 static unsigned int edit_y_offset(const GlobalOptions *options)
365 {
366 41 return options->tab_bar ? 1 : 0;
367 }
368
369 21 static void set_edit_size(Window *window, const GlobalOptions *options)
370 {
371 21 int xo = edit_x_offset(options, window->view);
372 21 int yo = edit_y_offset(options);
373 21 window->edit_w = window->w - xo;
374 21 window->edit_h = window->h - yo - 1; // statusline
375 21 window->edit_x = window->x + xo;
376 21 }
377
378 21 void window_calculate_line_numbers(Window *window)
379 {
380 21 const GlobalOptions *options = &window->editor->options;
381 21 unsigned int w = line_numbers_width(options, window->view);
382
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 20 times.
21 if (w != window->lineno_width) {
383 1 window->lineno_width = w;
384 1 window->lineno_first = 0;
385 1 window->lineno_last = 0;
386 1 mark_all_lines_changed(window->view->buffer);
387 }
388 21 set_edit_size(window, options);
389 21 }
390
391 20 void window_set_coordinates(Window *window, int x, int y)
392 {
393 20 const GlobalOptions *options = &window->editor->options;
394 20 window->x = x;
395 20 window->y = y;
396 20 window->edit_x = x + edit_x_offset(options, window->view);
397 20 window->edit_y = y + edit_y_offset(options);
398 20 }
399
400 21 void window_set_size(Window *window, int w, int h)
401 {
402 21 window->w = w;
403 21 window->h = h;
404 21 window_calculate_line_numbers(window);
405 21 }
406
407 7 unsigned int window_get_scroll_margin(const Window *window)
408 {
409 7 int edit_h = window->edit_h;
410
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 7 times.
7 if (unlikely(edit_h <= 2)) {
411 return 0;
412 }
413
414 unsigned int max = (edit_h - 1) / 2;
415 unsigned int scroll_margin = window->editor->options.scroll_margin;
416 return MIN(max, scroll_margin);
417 }
418
419 // Recursion is bounded by the number of descendant frames, which is
420 // typically not more than 5 or so
421 // NOLINTNEXTLINE(misc-no-recursion)
422 12 void frame_for_each_window(const Frame *frame, void (*func)(Window*, void*), void *data)
423 {
424
2/2
✓ Branch 2 → 3 taken 10 times.
✓ Branch 2 → 5 taken 2 times.
12 if (frame->window) {
425 10 func(frame->window, data);
426 10 return;
427 }
428
429 2 void **ptrs = frame->frames.ptrs;
430
2/2
✓ Branch 8 → 6 taken 4 times.
✓ Branch 8 → 9 taken 2 times.
6 for (size_t i = 0, n = frame->frames.count; i < n; i++) {
431 4 frame_for_each_window(ptrs[i], func, data);
432 }
433 }
434
435 typedef struct {
436 const Window *const target; // Window to search for (set at init.)
437 Window *first; // Window passed in first callback invocation
438 Window *last; // Window passed in last callback invocation
439 Window *prev; // Window immediately before target (if any)
440 Window *next; // Window immediately after target (if any)
441 bool found; // Set to true when target is found
442 } WindowCallbackData;
443
444 6 static void find_prev_and_next(Window *window, void *ud)
445 {
446 6 WindowCallbackData *data = ud;
447 6 data->last = window;
448
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 5 taken 5 times.
6 if (data->found) {
449
1/2
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 10 not taken.
1 if (!data->next) {
450 1 data->next = window;
451 }
452 return;
453 }
454
2/2
✓ Branch 5 → 6 taken 4 times.
✓ Branch 5 → 7 taken 1 time.
5 if (!data->first) {
455 4 data->first = window;
456 }
457
2/2
✓ Branch 7 → 8 taken 4 times.
✓ Branch 7 → 9 taken 1 time.
5 if (window == data->target) {
458 4 data->found = true;
459 4 return;
460 }
461 1 data->prev = window;
462 }
463
464 1 Window *window_prev(Window *window)
465 {
466 1 WindowCallbackData data = {.target = window};
467 1 frame_for_each_window(window->editor->root_frame, find_prev_and_next, &data);
468 1 BUG_ON(!data.found);
469
1/2
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 7 not taken.
1 return data.prev ? data.prev : data.last;
470 }
471
472 1 Window *window_next(Window *window)
473 {
474 1 WindowCallbackData data = {.target = window};
475 1 frame_for_each_window(window->editor->root_frame, find_prev_and_next, &data);
476 1 BUG_ON(!data.found);
477
1/2
✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 7 not taken.
1 return data.next ? data.next : data.first;
478 }
479
480 3 void window_close(Window *window)
481 {
482 3 EditorState *e = window->editor;
483
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 7 taken 2 times.
3 if (!window->frame->parent) {
484 // Don't close last window
485 1 window_remove_views(window);
486 1 set_view(window_open_empty_buffer(window));
487 1 return;
488 }
489
490 2 WindowCallbackData data = {.target = window};
491 2 frame_for_each_window(e->root_frame, find_prev_and_next, &data);
492 2 BUG_ON(!data.found);
493
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 1 time.
2 Window *next_or_prev = data.next ? data.next : data.prev;
494 2 BUG_ON(!next_or_prev);
495
496 2 frame_remove(e, window->frame);
497 2 e->window = NULL;
498 2 set_view(next_or_prev->view);
499
500 2 e->screen_update |= UPDATE_ALL;
501 2 frame_debug(e->root_frame);
502 }
503