dte test coverage


Directory: ./
File: src/window.c
Date: 2025-02-14 16:55:22
Exec Total Coverage
Lines: 246 267 92.1%
Functions: 31 32 96.9%
Branches: 90 124 72.6%

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 10 Window *new_window(EditorState *e)
17 {
18 10 Window *window = xnew0(Window, 1);
19 10 window->editor = e;
20 10 return window;
21 }
22
23 81 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 81 static_assert(SELECT_NONE == 0);
28
29 81 View *view = xmalloc(sizeof(*view));
30 81 *view = (View) {
31 .buffer = buffer,
32 .window = window,
33 81 .cursor = block_iter(buffer),
34 };
35
36 81 ptr_array_append(&buffer->views, view);
37 81 ptr_array_append(&window->views, view);
38 81 window->update_tabbar = true;
39 81 return view;
40 }
41
42 49 View *window_open_empty_buffer(Window *window)
43 {
44 49 EditorState *e = window->editor;
45 49 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 0 (2→3) taken 1 times.
✓ Branch 1 (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 0 (6→7) taken 1 times.
✓ Branch 1 (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 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 1 times.
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 0 (12→13) not taken.
✓ Branch 1 (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 0 (21→22) taken 1 times.
✓ Branch 1 (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 0 (27→28) not taken.
✓ Branch 1 (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 0 (32→33) taken 26 times.
✗ Branch 1 (32→38) not taken.
✓ Branch 2 (33→34) taken 26 times.
✗ Branch 3 (33→38) not taken.
✗ Branch 4 (35→36) not taken.
✓ Branch 5 (35→38) taken 26 times.
26 if (buffer->file.mode != 0 && !buffer->readonly && access(filename, W_OK)) {
103 error_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 View *window_find_or_create_view(Window *window, Buffer *buffer)
111 {
112 1 View *view = window_find_view(window, buffer);
113
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→6) taken 1 times.
1 if (!view) {
114 // Buffer isn't open in this window; create a new view of it
115 view = window_add_buffer(window, buffer);
116 view->cursor = buffer_get_first_view(buffer)->cursor;
117 }
118 1 return view;
119 }
120
121 1 View *window_find_view(Window *window, Buffer *buffer)
122 {
123
1/2
✓ Branch 0 (5→3) taken 1 times.
✗ Branch 1 (5→6) not taken.
1 for (size_t i = 0, n = buffer->views.count; i < n; i++) {
124 1 View *view = buffer->views.ptrs[i];
125
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→6) taken 1 times.
1 if (view->window == window) {
126 return view;
127 }
128 }
129 // Buffer isn't open in this window
130 return NULL;
131 }
132
133 4 View *window_find_unclosable_view(Window *window)
134 {
135 // Check active view first
136
3/4
✓ Branch 0 (2→3) taken 4 times.
✗ Branch 1 (2→6) not taken.
✓ Branch 2 (4→5) taken 2 times.
✓ Branch 3 (4→6) taken 2 times.
4 if (window->view && !view_can_close(window->view)) {
137 2 return window->view;
138 }
139
140 2 void **ptrs = window->views.ptrs;
141
2/2
✓ Branch 0 (10→7) taken 2 times.
✓ Branch 1 (10→11) taken 2 times.
4 for (size_t i = 0, n = window->views.count; i < n; i++) {
142 2 View *view = ptrs[i];
143
1/2
✓ Branch 0 (8→9) taken 2 times.
✗ Branch 1 (8→11) not taken.
2 if (!view_can_close(view)) {
144 return view;
145 }
146 }
147
148 return NULL;
149 }
150
151 11 static void window_remove_views(Window *window)
152 {
153
2/2
✓ Branch 0 (4→3) taken 55 times.
✓ Branch 1 (4→5) taken 11 times.
66 while (window->views.count > 0) {
154 55 View *view = window->views.ptrs[window->views.count - 1];
155 55 remove_view(view);
156 }
157 11 }
158
159 // NOTE: window->frame isn't removed
160 10 void window_free(Window *window)
161 {
162 10 window_remove_views(window);
163 10 ptr_array_free_array(&window->views);
164 10 window->frame = NULL;
165 10 free(window);
166 10 }
167
168 // Remove view from view->window and view->buffer->views and free it
169 81 size_t remove_view(View *view)
170 {
171 81 Window *window = view->window;
172 81 window->update_tabbar = true;
173
2/2
✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→4) taken 79 times.
81 if (view == window->prev_view) {
174 2 window->prev_view = NULL;
175 }
176
177 81 EditorState *e = window->editor;
178
2/2
✓ Branch 0 (4→5) taken 34 times.
✓ Branch 1 (4→6) taken 47 times.
81 if (view == e->view) {
179 34 e->view = NULL;
180 34 e->buffer = NULL;
181 }
182
183 81 size_t idx = ptr_array_remove(&window->views, view);
184 81 Buffer *buffer = view->buffer;
185 81 ptr_array_remove(&buffer->views, view);
186
187
2/2
✓ Branch 0 (8→9) taken 80 times.
✓ Branch 1 (8→13) taken 1 times.
81 if (buffer->views.count == 0) {
188
4/4
✓ Branch 0 (9→10) taken 79 times.
✓ Branch 1 (9→12) taken 1 times.
✓ Branch 2 (10→11) taken 44 times.
✓ Branch 3 (10→12) taken 35 times.
80 if (buffer->options.file_history && buffer->abs_filename) {
189 44 FileHistory *hist = &e->file_history;
190 44 file_history_append(hist, view->cy + 1, view->cx_char + 1, buffer->abs_filename);
191 }
192 80 buffer_remove_unlock_and_free(&e->buffers, buffer, &e->err, &e->locks_ctx);
193 }
194
195 81 free(view);
196 81 return idx;
197 }
198
199 26 void window_close_current_view(Window *window)
200 {
201 26 size_t idx = remove_view(window->view);
202
2/2
✓ Branch 0 (3→4) taken 21 times.
✓ Branch 1 (3→5) taken 5 times.
26 if (window->prev_view) {
203 21 window->view = window->prev_view;
204 21 window->prev_view = NULL;
205 21 return;
206 }
207
208 5 const PointerArray *views = &window->views;
209
2/2
✓ Branch 0 (5→6) taken 1 times.
✓ Branch 1 (5→7) taken 4 times.
5 if (views->count == 0) {
210 1 window_open_empty_buffer(window);
211 }
212
213 5 idx -= (views->count == idx);
214 5 window->view = views->ptrs[idx];
215 }
216
217 4 static void restore_cursor_from_history(const FileHistory *hist, View *view)
218 {
219 4 unsigned long row, col;
220
2/2
✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→5) taken 3 times.
4 if (file_history_find(hist, view->buffer->abs_filename, &row, &col)) {
221 1 move_to_filepos(view, row, col);
222 }
223 4 }
224
225 92 void set_view(View *view)
226 {
227 92 EditorState *e = view->window->editor;
228
2/2
✓ Branch 0 (2→3) taken 82 times.
✓ Branch 1 (2→19) taken 10 times.
92 if (e->view == view) {
229 return;
230 }
231
232 // Forget previous view when changing view using any other command but open
233
2/2
✓ Branch 0 (3→4) taken 80 times.
✓ Branch 1 (3→5) taken 2 times.
82 if (e->window) {
234 80 e->window->prev_view = NULL;
235 }
236
237 82 e->view = view;
238 82 e->buffer = view->buffer;
239 82 e->window = view->window;
240 82 e->window->view = view;
241
242
2/2
✓ Branch 0 (5→6) taken 56 times.
✓ Branch 1 (5→10) taken 26 times.
82 if (!view->buffer->setup) {
243 56 buffer_setup(e, view->buffer);
244
3/4
✓ Branch 0 (7→8) taken 56 times.
✗ Branch 1 (7→10) not taken.
✓ Branch 2 (8→9) taken 4 times.
✓ Branch 3 (8→10) taken 52 times.
56 if (view->buffer->options.file_history && view->buffer->abs_filename) {
245 4 restore_cursor_from_history(&e->file_history, view);
246 }
247 }
248
249 // view.cursor can be invalid if same buffer was modified from another view
250
2/2
✓ Branch 0 (10→11) taken 1 times.
✓ Branch 1 (10→13) taken 81 times.
82 if (view->restore_cursor) {
251 1 view->cursor.blk = BLOCK(view->buffer->blocks.next);
252 1 block_iter_goto_offset(&view->cursor, view->saved_cursor_offset);
253 1 view->restore_cursor = false;
254 1 view->saved_cursor_offset = 0;
255 }
256
257 // Save cursor states of views sharing same buffer
258 82 void **ptrs = view->buffer->views.ptrs;
259
2/2
✓ Branch 0 (18→14) taken 83 times.
✓ Branch 1 (18→19) taken 82 times.
165 for (size_t i = 0, n = view->buffer->views.count; i < n; i++) {
260 83 View *other = ptrs[i];
261
2/2
✓ Branch 0 (14→15) taken 1 times.
✓ Branch 1 (14→17) taken 82 times.
83 if (other != view) {
262 1 other->saved_cursor_offset = block_iter_get_offset(&other->cursor);
263 1 other->restore_cursor = true;
264 }
265 }
266 }
267
268 43 View *window_open_new_file(Window *window)
269 {
270 43 View *prev = window->view;
271 43 View *view = window_open_empty_buffer(window);
272 43 set_view(view);
273 43 window->prev_view = prev;
274 43 return view;
275 }
276
277 static bool buffer_is_empty_and_untouched(const Buffer *b)
278 {
279 return !b->abs_filename && b->change_head.nr_prev == 0 && !b->display_filename;
280 }
281
282 // If window contains only one untouched buffer it'll be closed after
283 // opening another file. This is done because closing the last buffer
284 // causes an empty buffer to be opened (windows must contain at least
285 // one buffer).
286 6 static bool is_useless_empty_view(const View *v)
287 {
288
2/6
✓ Branch 0 (2→3) taken 6 times.
✗ Branch 1 (2→6) not taken.
✗ Branch 2 (3→4) not taken.
✓ Branch 3 (3→6) taken 6 times.
✗ Branch 4 (4→5) not taken.
✗ Branch 5 (4→6) not taken.
6 return v && v->window->views.count == 1 && buffer_is_empty_and_untouched(v->buffer);
289 }
290
291 5 View *window_open_file(Window *window, const char *filename, const char *encoding)
292 {
293 5 View *prev = window->view;
294 5 bool useless = is_useless_empty_view(prev);
295 5 View *view = window_open_buffer(window, filename, false, encoding);
296
2/2
✓ Branch 0 (3→4) taken 2 times.
✓ Branch 1 (3→8) taken 3 times.
5 if (view) {
297 2 set_view(view);
298
1/2
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→7) taken 2 times.
2 if (useless) {
299 remove_view(prev);
300 } else {
301 2 window->prev_view = prev;
302 }
303 }
304 5 return view;
305 }
306
307 // Open multiple files in window and return the first opened View
308 1 View *window_open_files(Window *window, char **filenames, const char *encoding)
309 {
310 1 View *empty = window->view;
311 1 bool useless = is_useless_empty_view(empty);
312 1 View *first = NULL;
313
314
2/2
✓ Branch 0 (7→3) taken 23 times.
✓ Branch 1 (7→8) taken 1 times.
24 for (size_t i = 0; filenames[i]; i++) {
315 23 View *view = window_open_buffer(window, filenames[i], false, encoding);
316
2/2
✓ Branch 0 (4→5) taken 1 times.
✓ Branch 1 (4→6) taken 22 times.
23 if (view && !first) {
317 1 set_view(view);
318 1 first = view;
319 }
320 }
321
322
1/4
✗ Branch 0 (8→9) not taken.
✓ Branch 1 (8→11) taken 1 times.
✗ Branch 2 (9→10) not taken.
✗ Branch 3 (9→11) not taken.
1 if (useless && window->view != empty) {
323 remove_view(empty);
324 }
325
326 1 return first;
327 }
328
329 21 void buffer_mark_tabbars_changed(Buffer *buffer)
330 {
331
2/2
✓ Branch 0 (4→3) taken 22 times.
✓ Branch 1 (4→5) taken 21 times.
43 for (size_t i = 0, n = buffer->views.count; i < n; i++) {
332 22 View *view = buffer->views.ptrs[i];
333 22 view->window->update_tabbar = true;
334 }
335 21 }
336
337 62 static int line_numbers_width(const GlobalOptions *options, const View *view)
338 {
339
4/4
✓ Branch 0 (2→3) taken 9 times.
✓ Branch 1 (2→5) taken 53 times.
✓ Branch 2 (3→4) taken 6 times.
✓ Branch 3 (3→5) taken 3 times.
62 if (!options->show_line_numbers || !view) {
340 return 0;
341 }
342 6 size_t width = size_str_width(view->buffer->nl) + 1;
343 6 return MAX(width, LINE_NUMBERS_MIN_WIDTH);
344 }
345
346 41 static int edit_x_offset(const GlobalOptions *options, const View *view)
347 {
348 41 return line_numbers_width(options, view);
349 }
350
351 41 static int edit_y_offset(const GlobalOptions *options)
352 {
353 41 return options->tab_bar ? 1 : 0;
354 }
355
356 21 static void set_edit_size(Window *window, const GlobalOptions *options)
357 {
358 21 int xo = edit_x_offset(options, window->view);
359 21 int yo = edit_y_offset(options);
360 21 window->edit_w = window->w - xo;
361 21 window->edit_h = window->h - yo - 1; // statusline
362 21 window->edit_x = window->x + xo;
363 21 }
364
365 21 void window_calculate_line_numbers(Window *window)
366 {
367 21 const GlobalOptions *options = &window->editor->options;
368 21 int w = line_numbers_width(options, window->view);
369
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 20 times.
21 if (w != window->line_numbers.width) {
370 1 window->line_numbers.width = w;
371 1 window->line_numbers.first = 0;
372 1 window->line_numbers.last = 0;
373 1 mark_all_lines_changed(window->view->buffer);
374 }
375 21 set_edit_size(window, options);
376 21 }
377
378 20 void window_set_coordinates(Window *window, int x, int y)
379 {
380 20 const GlobalOptions *options = &window->editor->options;
381 20 window->x = x;
382 20 window->y = y;
383 20 window->edit_x = x + edit_x_offset(options, window->view);
384 20 window->edit_y = y + edit_y_offset(options);
385 20 }
386
387 21 void window_set_size(Window *window, int w, int h)
388 {
389 21 window->w = w;
390 21 window->h = h;
391 21 window_calculate_line_numbers(window);
392 21 }
393
394 7 unsigned int window_get_scroll_margin(const Window *window)
395 {
396 7 int edit_h = window->edit_h;
397
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 7 times.
7 if (unlikely(edit_h <= 2)) {
398 return 0;
399 }
400
401 unsigned int max = (edit_h - 1) / 2;
402 unsigned int scroll_margin = window->editor->options.scroll_margin;
403 return MIN(max, scroll_margin);
404 }
405
406 // NOLINTNEXTLINE(misc-no-recursion)
407 12 void frame_for_each_window(const Frame *frame, void (*func)(Window*, void*), void *data)
408 {
409
2/2
✓ Branch 0 (2→3) taken 10 times.
✓ Branch 1 (2→5) taken 2 times.
12 if (frame->window) {
410 10 func(frame->window, data);
411 10 return;
412 }
413
414 2 void **ptrs = frame->frames.ptrs;
415
2/2
✓ Branch 0 (8→6) taken 4 times.
✓ Branch 1 (8→9) taken 2 times.
6 for (size_t i = 0, n = frame->frames.count; i < n; i++) {
416 4 frame_for_each_window(ptrs[i], func, data);
417 }
418 }
419
420 typedef struct {
421 const Window *const target; // Window to search for (set at init.)
422 Window *first; // Window passed in first callback invocation
423 Window *last; // Window passed in last callback invocation
424 Window *prev; // Window immediately before target (if any)
425 Window *next; // Window immediately after target (if any)
426 bool found; // Set to true when target is found
427 } WindowCallbackData;
428
429 6 static void find_prev_and_next(Window *window, void *ud)
430 {
431 6 WindowCallbackData *data = ud;
432 6 data->last = window;
433
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→6) taken 5 times.
6 if (data->found) {
434
1/2
✓ Branch 0 (3→4) taken 1 times.
✗ Branch 1 (3→5) not taken.
1 if (!data->next) {
435 1 data->next = window;
436 }
437 1 return;
438 }
439
2/2
✓ Branch 0 (6→7) taken 4 times.
✓ Branch 1 (6→8) taken 1 times.
5 if (!data->first) {
440 4 data->first = window;
441 }
442
2/2
✓ Branch 0 (8→9) taken 4 times.
✓ Branch 1 (8→10) taken 1 times.
5 if (window == data->target) {
443 4 data->found = true;
444 4 return;
445 }
446 1 data->prev = window;
447 }
448
449 1 Window *window_prev(Window *window)
450 {
451 1 WindowCallbackData data = {.target = window};
452 1 frame_for_each_window(window->editor->root_frame, find_prev_and_next, &data);
453 1 BUG_ON(!data.found);
454
1/2
✓ Branch 0 (5→6) taken 1 times.
✗ Branch 1 (5→7) not taken.
1 return data.prev ? data.prev : data.last;
455 }
456
457 1 Window *window_next(Window *window)
458 {
459 1 WindowCallbackData data = {.target = window};
460 1 frame_for_each_window(window->editor->root_frame, find_prev_and_next, &data);
461 1 BUG_ON(!data.found);
462
1/2
✓ Branch 0 (5→6) taken 1 times.
✗ Branch 1 (5→7) not taken.
1 return data.next ? data.next : data.first;
463 }
464
465 3 void window_close(Window *window)
466 {
467 3 EditorState *e = window->editor;
468
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→7) taken 2 times.
3 if (!window->frame->parent) {
469 // Don't close last window
470 1 window_remove_views(window);
471 1 set_view(window_open_empty_buffer(window));
472 1 return;
473 }
474
475 2 WindowCallbackData data = {.target = window};
476 2 frame_for_each_window(e->root_frame, find_prev_and_next, &data);
477 2 BUG_ON(!data.found);
478
2/2
✓ Branch 0 (10→11) taken 1 times.
✓ Branch 1 (10→12) taken 1 times.
2 Window *next_or_prev = data.next ? data.next : data.prev;
479 2 BUG_ON(!next_or_prev);
480
481 2 frame_remove(e, window->frame);
482 2 e->window = NULL;
483 2 set_view(next_or_prev->view);
484
485 2 e->screen_update |= UPDATE_ALL;
486 2 frame_debug(e->root_frame);
487 }
488