dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 44.3% 86 / 0 / 194
Functions: 41.2% 7 / 0 / 17
Branches: 22.9% 11 / 14 / 62

src/editor.c
Line Branch Exec Source
1 #include <langinfo.h>
2 #include <locale.h>
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <sys/stat.h>
6 #include <unistd.h>
7 #include "editor.h"
8 #include "bind.h"
9 #include "bookmark.h"
10 #include "compiler.h"
11 #include "encoding.h"
12 #include "file-option.h"
13 #include "filetype.h"
14 #include "lock.h"
15 #include "signals.h"
16 #include "syntax/syntax.h"
17 #include "terminal/color.h"
18 #include "terminal/input.h"
19 #include "terminal/key.h"
20 #include "terminal/output.h"
21 #include "terminal/paste.h"
22 #include "ui.h"
23 #include "util/exitcode.h"
24 #include "util/intern.h"
25 #include "util/log.h"
26 #include "util/str-util.h"
27 #include "util/time-util.h"
28 #include "util/xmalloc.h"
29 #include "util/xsnprintf.h"
30 #include "util/xstdio.h"
31 #include "version.h"
32
33 11 static void set_and_check_locale(void)
34 {
35 11 const char *default_locale = setlocale(LC_CTYPE, "");
36
1/2
✓ Branch 3 → 4 taken 11 times.
✗ Branch 3 → 8 not taken.
11 if (likely(default_locale)) {
37 11 const char *codeset = nl_langinfo(CODESET);
38 11 LOG_INFO("locale: %s (codeset: %s)", default_locale, codeset);
39
1/2
✗ Branch 7 → 9 not taken.
✓ Branch 7 → 19 taken 11 times.
11 if (likely(lookup_encoding(codeset) == UTF8)) {
40 return;
41 }
42 } else {
43 ✗ LOG_ERROR("failed to set default locale");
44 }
45
46 ✗ static const char fallbacks[][12] = {"C.UTF-8", "en_US.UTF-8"};
47 ✗ const char *fallback = NULL;
48 ✗ for (size_t i = 0; i < ARRAYLEN(fallbacks) && !fallback; i++) {
49 ✗ fallback = setlocale(LC_CTYPE, fallbacks[i]);
50 }
51 ✗ if (fallback) {
52 ✗ LOG_NOTICE("using fallback locale for LC_CTYPE: %s", fallback);
53 ✗ return;
54 }
55
56 ✗ LOG_ERROR("no UTF-8 fallback locales found");
57 ✗ fputs("setlocale() failed\n", stderr);
58 ✗ exit(EC_CONFIG_ERROR);
59 }
60
61 // Get permissions mask for new files
62 11 static mode_t get_umask(void)
63 {
64 11 mode_t old = umask(0); // Get by setting a dummy value
65 11 umask(old); // Restore previous value
66 11 return old;
67 }
68
69 11 static const char *get_user_config_dir(const char *home, const char *dte_home)
70 {
71
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 4 taken 10 times.
11 if (dte_home) {
72 1 return str_intern(dte_home);
73 }
74
75 // TODO: Use "${XDG_CONFIG_HOME:-$HOME/.config}/dte", either if it
76 // exists or (as a default) if "$HOME/.dte" *doesn't* exist. The
77 // latter case may also require one or more calls to mkdir(3).
78
79 10 char buf[8192];
80 10 return mem_intern(buf, xsnprintf(buf, sizeof buf, "%s/.dte", home));
81 }
82
83 11 EditorState *init_editor_state(const char *home, const char *dte_home)
84 {
85 11 set_and_check_locale();
86
1/2
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 11 times.
11 home = home ? home : "";
87 11 pid_t pid = getpid();
88 11 EditorState *e = xmalloc(sizeof(*e));
89
90 22 *e = (EditorState) {
91 .status = EDITOR_INITIALIZING,
92 11 .home_dir = strview_intern(home),
93 11 .user_config_dir = get_user_config_dir(home, dte_home),
94 .flags = EFLAG_HEADLESS,
95 11 .session_leader = (pid == getsid(0)),
96 11 .regexp_word_tokens = regexp_get_word_boundary_tokens(),
97 11 .modes = hashmap_new(3, HMAP_BORROWED_KEYS),
98 11 .aliases = hashmap_new(32, HMAP_NO_FLAGS),
99 11 .required_syntax_files = hashset_new(0, false),
100 11 .required_syntax_builtins = hashset_new(0, false),
101 .command_history = {
102 .max_entries = 512,
103 },
104 .search_history = {
105 .max_entries = 128,
106 },
107 .terminal = {
108 11 .obuf = term_outbuf(),
109 },
110 .cursor_styles = {
111 [CURSOR_MODE_DEFAULT] = {.type = CURSOR_DEFAULT, .color = COLOR_DEFAULT},
112 [CURSOR_MODE_INSERT] = {.type = CURSOR_KEEP, .color = COLOR_KEEP},
113 [CURSOR_MODE_OVERWRITE] = {.type = CURSOR_KEEP, .color = COLOR_KEEP},
114 [CURSOR_MODE_CMDLINE] = {.type = CURSOR_KEEP, .color = COLOR_KEEP},
115 },
116 .options = {
117 .auto_indent = true,
118 .detect_indent = 0,
119 .editorconfig = false,
120 .emulate_tab = false,
121 .expand_tab = false,
122 .file_history = true,
123 .indent_width = 8,
124 .overwrite = false,
125 .save_unmodified = SAVE_FULL,
126 .syntax = true,
127 .tab_width = 8,
128 .text_width = 72,
129 .ws_error = WSE_SPECIAL,
130
131 // Global-only options
132 .case_sensitive_search = CSS_TRUE,
133 .center_at_eof = false,
134 .crlf_newlines = false,
135 .display_special = false,
136 .esc_timeout = 100,
137 .filesize_limit = 250ULL << 20, // 250MiB
138 .lock_files = true,
139 .msg_compile = 0,
140 .msg_tag = 0,
141 .optimize_true_color = false,
142 .scroll_margin = 0,
143 .select_cursor_char = true,
144 .set_window_title = false,
145 .show_line_numbers = false,
146 11 .statusline_left = str_intern(" %f%s%m%s%r%s%M"),
147 11 .statusline_right = str_intern(" %y,%X %u %o %E%s%b%s%n %t %p "),
148 .syntax_line_limit = 512ULL << 10, // 512KiB
149 .syntax_size_limit = 100ULL << 20, // 100MiB
150 .tab_bar = true,
151 .utf8_bom = false,
152 .window_separator = WINSEP_BAR,
153 }
154 };
155
156 11 sanity_check_global_options(&e->options);
157
1/2
✓ Branch 18 → 19 taken 11 times.
✗ Branch 18 → 20 not taken.
22 LOG_INFO("pid: %jd%s", (intmax_t)pid, e->session_leader ? " (session leader)" : "");
158
159 11 pid_t pgid = getpgrp();
160
1/2
✓ Branch 22 → 23 taken 11 times.
✗ Branch 22 → 24 not taken.
11 if (pgid != pid) {
161 11 LOG_INFO("pgid: %jd", (intmax_t)pgid);
162 }
163
164 11 mode_t mask = get_umask();
165 11 e->new_file_mode = 0666 & ~mask;
166 11 LOG_INFO("umask: %04o", 0777u & (unsigned int)mask);
167
168 11 init_file_locks_context(&e->locks_ctx, e->user_config_dir, pid);
169
170 // Allow child processes to detect that they're running under dte
171 11 xsetenv("DTE_VERSION", VERSION);
172
173 // Initial capacities here should accommodate at least the number
174 // of bindings created by exec_rc_files()
175 11 e->normal_mode = new_mode(&e->modes, "normal", &normal_commands, 150);
176 11 e->command_mode = new_mode(&e->modes, "command", &cmd_mode_commands, 40);
177 11 e->search_mode = new_mode(&e->modes, "search", &search_mode_commands, 40);
178 11 e->mode = e->normal_mode;
179
180 11 return e;
181 }
182
183 34 static void free_mode_handler(ModeHandler *handler)
184 {
185 34 ptr_array_free_array(&handler->fallthrough_modes);
186 34 free_bindings(&handler->key_bindings);
187 34 free(handler);
188 34 }
189
190 12 void clear_all_messages(EditorState *e)
191 {
192
2/2
✓ Branch 5 → 3 taken 36 times.
✓ Branch 5 → 6 taken 12 times.
48 for (size_t i = 0; i < ARRAYLEN(e->messages); i++) {
193 36 clear_messages(&e->messages[i]);
194 }
195 12 }
196
197 11 void free_editor_state(EditorState *e)
198 {
199 11 size_t n = e->terminal.obuf.count;
200
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 11 times.
11 if (n) {
201 ✗ LOG_DEBUG("%zu unflushed bytes in terminal output buffer", n);
202 }
203 11 n = e->terminal.ibuf.len;
204
1/2
✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 11 times.
11 if (n) {
205 ✗ LOG_DEBUG("%zu unprocessed bytes in terminal input buffer", n);
206 }
207
208 11 free(e->clipboard.buf);
209 11 free_file_options(&e->file_options);
210 11 free_filetypes(&e->filetypes);
211 11 free_syntaxes(&e->syntaxes);
212 11 file_history_free(&e->file_history);
213 11 history_free(&e->command_history);
214 11 history_free(&e->search_history);
215 11 search_free_regexp(&e->search);
216 11 clear_all_messages(e);
217 11 cmdline_free(&e->cmdline);
218 11 free_macro(&e->macro);
219 11 tag_file_free(&e->tagfile);
220 11 free_buffers(&e->buffers, &e->err, &e->locks_ctx);
221 11 free_file_locks_context(&e->locks_ctx);
222
223 11 ptr_array_free_cb(&e->bookmarks, FREE_FUNC(file_location_free));
224 11 hashmap_free(&e->compilers, FREE_FUNC(free_compiler));
225 11 hashmap_free(&e->modes, FREE_FUNC(free_mode_handler));
226 11 hashmap_free(&e->styles.other, free);
227 11 hashmap_free(&e->aliases, free);
228 11 hashset_free(&e->required_syntax_files);
229 11 hashset_free(&e->required_syntax_builtins);
230
231 11 free_interned_strings();
232 11 free_interned_regexps();
233 11 free(e);
234 11 }
235
236 ✗ static bool buffer_contains_block(const Buffer *buffer, const Block *ref)
237 {
238 ✗ const Block *blk;
239 ✗ block_for_each(blk, &buffer->blocks) {
240 ✗ if (blk == ref) {
241 return true;
242 }
243 }
244 return false;
245 }
246
247 ✗ static void sanity_check(const View *view)
248 {
249 ✗ if (!DEBUG_ASSERTIONS_ENABLED) {
250 return;
251 }
252 ✗ const BlockIter *cursor = &view->cursor;
253 ✗ BUG_ON(!buffer_contains_block(view->buffer, cursor->blk));
254 ✗ BUG_ON(cursor->offset > cursor->blk->size);
255 }
256
257 ✗ void any_key(Terminal *term, unsigned int esc_timeout)
258 {
259 ✗ KeyCode key;
260 ✗ xfputs("Press any key to continue\r\n", stderr);
261
262 ✗ do {
263 ✗ key = term_read_input(term, esc_timeout);
264 ✗ } while (key == KEY_NONE || key == KEYCODE_REDRAW);
265
266 ✗ bool bpaste = key == KEYCODE_BRACKETED_PASTE;
267 ✗ if (bpaste || key == KEYCODE_DETECTED_PASTE) {
268 ✗ term_discard_paste(&term->ibuf, bpaste);
269 }
270 ✗ }
271
272 NOINLINE
273 ✗ void ui_resize(EditorState *e)
274 {
275 ✗ BUG_ON(e->flags & EFLAG_HEADLESS);
276 ✗ resized = 0;
277 ✗ update_screen_size(&e->terminal, e->root_frame);
278
279 ✗ const ScreenState dummyval = {.id = 0};
280 ✗ e->screen_update |= UPDATE_ALL;
281 ✗ update_screen(e, &dummyval);
282 ✗ }
283
284 ✗ void ui_start(EditorState *e)
285 {
286 ✗ BUG_ON(e->flags & EFLAG_HEADLESS);
287 ✗ Terminal *term = &e->terminal;
288
289 // Note: the order of these calls is important - Kitty saves/restores
290 // some terminal state when switching buffers, so switching to the
291 // alternate screen buffer needs to happen before modes are enabled
292 ✗ term_use_alt_screen_buffer(term);
293 ✗ term_enable_private_modes(term);
294
295 ✗ term_restore_and_save_title(term);
296 ✗ ui_resize(e);
297 ✗ }
298
299 // Like ui_start(), but to be called only the first time the UI is started.
300 // Terminal queries are buffered before ui_resize() is called, so that only
301 // a single term_output_flush() is needed (i.e. as opposed to calling
302 // ui_start() + term_put_initial_queries() + term_output_flush()).
303 ✗ static void ui_first_start(EditorState *e, unsigned int terminal_query_level)
304 {
305 ✗ BUG_ON(e->flags & EFLAG_HEADLESS);
306 ✗ Terminal *term = &e->terminal;
307
308 // The order of these calls is important; see ui_start()
309 ✗ term_use_alt_screen_buffer(term);
310 ✗ term_enable_private_modes(term);
311
312 ✗ term_save_title(term);
313 ✗ term_put_initial_queries(term, terminal_query_level);
314 ✗ ui_resize(e);
315 ✗ }
316
317 ✗ void ui_end(Terminal *term, bool final)
318 {
319 ✗ if (final) {
320 ✗ term_restore_title(term);
321 } else {
322 ✗ term_restore_and_save_title(term);
323 }
324
325 ✗ TermOutputBuffer *obuf = &term->obuf;
326 ✗ term_clear_screen(obuf);
327 ✗ term_move_cursor(obuf, 0, term->height - 1);
328 ✗ term_restore_cursor_style(term);
329 ✗ term_show_cursor(term);
330 ✗ term_restore_private_modes(term);
331 ✗ term_use_normal_screen_buffer(term);
332 ✗ term_end_sync_update(term);
333 ✗ term_output_flush(obuf);
334 ✗ }
335
336 ✗ static ScreenState get_screen_state(const EditorState *e)
337 {
338 ✗ const View *view = e->view;
339 ✗ return (ScreenState) {
340 ✗ .is_modified = buffer_modified(view->buffer),
341 ✗ .set_window_title = e->options.set_window_title,
342 ✗ .id = view->buffer->id,
343 ✗ .cy = view->cy,
344 ✗ .vx = view->vx,
345 ✗ .vy = view->vy
346 };
347 }
348
349 ✗ static void log_timing_info(const struct timespec *start, bool enabled)
350 {
351 ✗ struct timespec end;
352 ✗ if (likely(!enabled) || !xgettime(&end)) {
353 ✗ return;
354 }
355
356 ✗ double ms = timespec_to_fp_milliseconds(timespec_subtract(&end, start));
357 ✗ LOG_INFO("main loop time: %.3f ms", ms);
358 }
359
360 ✗ void main_loop(EditorState *e, unsigned int terminal_query_level, bool timing)
361 {
362 ✗ BUG_ON(e->flags & EFLAG_HEADLESS);
363 ✗ ui_first_start(e, terminal_query_level);
364
365 ✗ while (e->status == EDITOR_RUNNING) {
366 ✗ if (unlikely(resized)) {
367 ✗ LOG_INFO("SIGWINCH received");
368 ✗ ui_resize(e);
369 }
370
371 ✗ KeyCode key = term_read_input(&e->terminal, e->options.esc_timeout);
372 ✗ if (unlikely(key == KEY_NONE)) {
373 ✗ continue;
374 }
375
376 ✗ struct timespec start;
377 ✗ timing = unlikely(timing) && xgettime(&start);
378 ✗ const ScreenState s = get_screen_state(e);
379
380 ✗ if (unlikely(key == KEYCODE_REDRAW)) {
381 ✗ e->screen_update |= UPDATE_ALL_WINDOWS;
382 } else {
383 ✗ clear_error(&e->err);
384 ✗ handle_input(e, key);
385 ✗ sanity_check(e->view);
386 }
387
388 ✗ update_screen(e, &s);
389 ✗ log_timing_info(&start, timing);
390 }
391
392 ✗ BUG_ON(e->status < 0 || e->status > EDITOR_EXIT_MAX);
393 ✗ }
394