dte test coverage


Directory: ./
File: src/main.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 200 360 55.6%
Functions: 14 16 87.5%
Branches: 82 202 40.6%

Line Branch Exec Source
1 #include <errno.h>
2 #include <fcntl.h>
3 #include <stdbool.h>
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <string.h>
7 #include <sys/stat.h>
8 #include <sys/utsname.h>
9 #include <unistd.h>
10 #include "block.h"
11 #include "command/cache.h"
12 #include "commands.h"
13 #include "compat.h"
14 #include "compiler.h"
15 #include "config.h"
16 #include "editor.h"
17 #include "encoding.h"
18 #include "error.h"
19 #include "file-history.h"
20 #include "frame.h"
21 #include "history.h"
22 #include "load-save.h"
23 #include "move.h"
24 #include "search.h"
25 #include "signals.h"
26 #include "syntax/state.h"
27 #include "syntax/syntax.h"
28 #include "tag.h"
29 #include "terminal/input.h"
30 #include "terminal/ioctl.h"
31 #include "terminal/key.h"
32 #include "terminal/mode.h"
33 #include "terminal/output.h"
34 #include "terminal/paste.h"
35 #include "terminal/terminal.h"
36 #include "ui.h"
37 #include "util/debug.h"
38 #include "util/exitcode.h"
39 #include "util/fd.h"
40 #include "util/log.h"
41 #include "util/macros.h"
42 #include "util/path.h"
43 #include "util/progname.h"
44 #include "util/strtonum.h"
45 #include "util/xmalloc.h"
46 #include "util/xreadwrite.h"
47 #include "util/xsnprintf.h"
48 #include "version.h"
49 #include "view.h"
50 #include "window.h"
51
52 static void cleanup_handler(void *userdata)
53 {
54 EditorState *e = userdata;
55 set_fatal_error_cleanup_handler(NULL, NULL);
56 if (!e->child_controls_terminal) {
57 ui_end(e);
58 term_cooked();
59 }
60 }
61
62 3 static ExitCode write_stdout(const char *str, size_t len)
63 {
64
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (xwrite_all(STDOUT_FILENO, str, len) < 0) {
65 return ec_error("write", EC_IO_ERROR);
66 }
67 return EC_OK;
68 }
69
70 1 static ExitCode list_builtin_configs(void)
71 {
72 1 String str = dump_builtin_configs();
73 1 BUG_ON(!str.buffer);
74 1 ExitCode e = write_stdout(str.buffer, str.len);
75 1 string_free(&str);
76 1 return e;
77 }
78
79 2 static ExitCode dump_builtin_config(const char *name)
80 {
81 2 const BuiltinConfig *cfg = get_builtin_config(name);
82
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (!cfg) {
83 1 fprintf(stderr, "Error: no built-in config with name '%s'\n", name);
84 1 return EC_USAGE_ERROR;
85 }
86 1 return write_stdout(cfg->text.data, cfg->text.length);
87 }
88
89 2 static ExitCode lint_syntax(const char *filename, SyntaxLoadFlags flags)
90 {
91 2 EditorState *e = init_editor_state(EFLAG_HEADLESS);
92 2 int err;
93 2 BUG_ON(e->status != EDITOR_INITIALIZING);
94 2 const Syntax *s = load_syntax_file(e, filename, flags | SYN_MUST_EXIST, &err);
95
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (s) {
96 1 const size_t n = s->states.count;
97
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 const char *plural = (n == 1) ? "" : "s";
98 1 printf("OK: loaded syntax '%s' with %zu state%s\n", s->name, n, plural);
99
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 } else if (err == EINVAL) {
100 1 error_msg("%s: no default syntax found", filename);
101 }
102 2 free_editor_state(e);
103
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 return get_nr_errors() ? EC_DATA_ERROR : EC_OK;
104 }
105
106 static ExitCode showkey_loop(unsigned int terminal_query_level)
107 {
108 if (!term_mode_init()) {
109 return ec_error("tcgetattr", EC_IO_ERROR);
110 }
111 if (unlikely(!term_raw())) {
112 return ec_error("tcsetattr", EC_IO_ERROR);
113 }
114
115 Terminal term;
116 TermOutputBuffer *obuf = &term.obuf;
117 TermInputBuffer *ibuf = &term.ibuf;
118 term_init(&term, getenv("TERM"), getenv("COLORTERM"));
119 term_input_init(ibuf);
120 term_output_init(obuf);
121 term_enable_private_modes(&term);
122 term_put_initial_queries(&term, terminal_query_level);
123 term_put_literal(obuf, "Press any key combination, or use Ctrl+D to exit\r\n");
124 term_output_flush(obuf);
125
126 char buf[KEYCODE_STR_MAX + 4];
127 for (bool loop = true; loop; ) {
128 KeyCode key = term_read_input(&term, 100);
129 switch (key) {
130 case KEY_NONE:
131 case KEY_IGNORE:
132 continue;
133 case KEYCODE_BRACKETED_PASTE:
134 case KEYCODE_DETECTED_PASTE:
135 term_discard_paste(ibuf, key == KEYCODE_BRACKETED_PASTE);
136 continue;
137 case MOD_CTRL | 'd':
138 loop = false;
139 }
140 size_t n = copyliteral(buf, " ");
141 n += keycode_to_string(key, buf + n);
142 n += copyliteral(buf + n, "\r\n");
143 (void)!xwrite_all(STDOUT_FILENO, buf, n);
144 }
145
146 term_restore_private_modes(&term);
147 term_output_flush(obuf);
148 term_cooked();
149 term_input_free(ibuf);
150 term_output_free(obuf);
151 return EC_OK;
152 }
153
154 1 static ExitCode init_std_fds(int std_fds[2])
155 {
156 1 FILE *streams[3] = {stdin, stdout, stderr};
157
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 for (int i = 0; i < ARRAYLEN(streams); i++) {
158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (is_controlling_tty(i)) {
159 continue;
160 }
161
162
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
1 if (i < STDERR_FILENO) {
163 // Try to create a duplicate fd for redirected stdin/stdout; to
164 // allow reading/writing after freopen(3) closes the original
165 1 int fd = fcntl(i, F_DUPFD_CLOEXEC, 3);
166
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 if (fd == -1 && errno != EBADF) {
167 return ec_error("fcntl", EC_OS_ERROR);
168 }
169 1 std_fds[i] = fd;
170 }
171
172 // Ensure standard streams are connected to the terminal during
173 // editor operation, regardless of how they were redirected
174
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 if (unlikely(!freopen("/dev/tty", i ? "w" : "r", streams[i]))) {
175 1 const char *err = strerror(errno);
176 1 fprintf(stderr, "Failed to open /dev/tty for fd %d: %s\n", i, err);
177 1 return EC_IO_ERROR;
178 }
179
180 int new_fd = fileno(streams[i]);
181 if (unlikely(new_fd != i)) {
182 // This should never happen in a single-threaded program.
183 // freopen() should call fclose() followed by open() and
184 // POSIX requires a successful call to open() to return the
185 // lowest available file descriptor.
186 fprintf(stderr, "freopen() changed fd from %d to %d\n", i, new_fd);
187 return EC_OS_ERROR;
188 }
189
190 if (unlikely(!is_controlling_tty(new_fd))) {
191 return ec_error("tcgetpgrp", EC_OS_ERROR);
192 }
193 }
194
195 return EC_OK;
196 }
197
198 5 static ExitCode init_std_fds_headless(int std_fds[2])
199 {
200 5 FILE *streams[] = {stdin, stdout};
201
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 5 times.
15 for (int i = 0; i < ARRAYLEN(streams); i++) {
202
1/2
✓ Branch 0 taken 10 times.
✗ Branch 1 not taken.
10 if (!isatty(i)) {
203 10 int fd = fcntl(i, F_DUPFD_CLOEXEC, 3);
204
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
10 if (fd == -1 && errno != EBADF) {
205 return ec_error("fcntl", EC_OS_ERROR);
206 }
207 10 std_fds[i] = fd;
208 }
209
3/4
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
15 if (!freopen("/dev/null", i ? "w" : "r", streams[i])) {
210 return ec_error("freopen", EC_IO_ERROR);
211 }
212 }
213
214
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!fd_is_valid(STDERR_FILENO)) {
215 // If FD 2 isn't valid (e.g. because it was closed), point it
216 // to /dev/null to ensure open(3) doesn't allocate it to other
217 // opened files
218 if (!freopen("/dev/null", "w", stderr)) {
219 return ec_error("freopen", EC_IO_ERROR);
220 }
221 }
222
223 return EC_OK;
224 }
225
226 5 static Buffer *init_std_buffer(EditorState *e, int fds[2])
227 {
228 5 const char *name = NULL;
229 5 Buffer *buffer = NULL;
230
231
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (fds[STDIN_FILENO] >= 3) {
232 5 buffer = buffer_new(&e->buffers, &e->options, encoding_from_type(UTF8));
233
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (read_blocks(buffer, fds[STDIN_FILENO], false)) {
234 5 name = "(stdin)";
235 5 buffer->temporary = true;
236 } else {
237 error_msg("Unable to read redirected stdin");
238 buffer_remove_unlock_and_free(&e->buffers, buffer, &e->locks_ctx);
239 buffer = NULL;
240 }
241 }
242
243
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (fds[STDOUT_FILENO] >= 3) {
244
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!buffer) {
245 buffer = open_empty_buffer(&e->buffers, &e->options);
246 name = "(stdout)";
247 } else {
248 name = "(stdin|stdout)";
249 }
250 5 buffer->stdout_buffer = true;
251 5 buffer->temporary = true;
252 }
253
254 5 BUG_ON(!buffer != !name);
255
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (name) {
256 5 buffer_set_display_filename(buffer, xstrdup(name));
257 }
258
259 5 return buffer;
260 }
261
262 5 static bool buffer_write_blocks_and_free(Buffer *buffer, int fd)
263 {
264 5 bool r = true;
265 5 const Block *blk;
266
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 block_for_each(blk, &buffer->blocks) {
267
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (xwrite_all(fd, blk->data, blk->size) < 0) {
268 error_msg_errno("failed to write (stdout) buffer");
269 r = false;
270 break;
271 }
272 }
273
274 // Note: the other allocations for buffer should have already been
275 // freed by buffer_unlock_and_free()
276 5 free_blocks(buffer);
277 5 free(buffer);
278 5 return r;
279 }
280
281 5 static ExitCode init_logging(const char *filename, const char *req_level_str)
282 {
283
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 if (!filename || filename[0] == '\0') {
284 return EC_OK;
285 }
286
287 LogLevel req_level = log_level_from_str(req_level_str);
288 if (req_level == LOG_LEVEL_NONE) {
289 return EC_OK;
290 }
291 if (req_level == LOG_LEVEL_INVALID) {
292 fprintf(stderr, "Invalid $DTE_LOG_LEVEL value: '%s'\n", req_level_str);
293 return EC_USAGE_ERROR;
294 }
295
296 // https://no-color.org/
297 const char *no_color = xgetenv("NO_COLOR");
298
299 LogLevel got_level = log_open(filename, req_level, !no_color);
300 if (got_level == LOG_LEVEL_NONE) {
301 const char *err = strerror(errno);
302 fprintf(stderr, "Failed to open $DTE_LOG (%s): %s\n", filename, err);
303 return EC_IO_ERROR;
304 }
305
306 const char *got_level_str = log_level_to_str(got_level);
307 if (got_level != req_level) {
308 const char *r = req_level_str;
309 const char *g = got_level_str;
310 LOG_WARNING("log level '%s' unavailable; falling back to '%s'", r, g);
311 }
312
313 LOG_INFO("logging to '%s' (level: %s)", filename, got_level_str);
314
315 if (no_color) {
316 LOG_INFO("log colors disabled ($NO_COLOR)");
317 }
318
319 return EC_OK;
320 }
321
322 5 static void log_config_counts(const EditorState *e)
323 {
324
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!log_level_enabled(LOG_LEVEL_INFO)) {
325 return;
326 }
327
328 size_t nbinds = 0;
329 size_t nbinds_cached = 0;
330 for (HashMapIter modeiter = hashmap_iter(&e->modes); hashmap_next(&modeiter); ) {
331 const ModeHandler *mode = modeiter.entry->value;
332 const IntMap *binds = &mode->key_bindings;
333 nbinds += binds->count;
334 for (IntMapIter binditer = intmap_iter(binds); intmap_next(&binditer); ) {
335 const CachedCommand *cc = binditer.entry->value;
336 nbinds_cached += !!cc->cmd;
337 }
338 }
339
340 size_t nerrorfmts = 0;
341 for (HashMapIter it = hashmap_iter(&e->compilers); hashmap_next(&it); ) {
342 const Compiler *compiler = it.entry->value;
343 nerrorfmts += compiler->error_formats.count;
344 }
345
346 LOG_INFO (
347 "bind=%zu(%zu) alias=%zu hi=%zu ft=%zu option=%zu errorfmt=%zu(%zu)",
348 nbinds,
349 nbinds_cached,
350 e->aliases.count,
351 e->styles.other.count + NR_BSE,
352 e->filetypes.count,
353 e->file_options.count,
354 e->compilers.count,
355 nerrorfmts
356 );
357 }
358
359 5 static void exec_user_rc(EditorState *e, const char *filename)
360 {
361 5 ConfigFlags flags = CFG_NOFLAGS;
362 5 char buf[8192];
363
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (filename) {
364 flags |= CFG_MUST_EXIST;
365 } else {
366 5 xsnprintf(buf, sizeof buf, "%s/%s", e->user_config_dir, "rc");
367 5 filename = buf;
368 }
369 5 LOG_INFO("loading configuration from %s", filename);
370 5 read_normal_config(e, filename, flags);
371 5 }
372
373 5 static void read_history_files(EditorState *e)
374 {
375 5 const char *dir = e->user_config_dir;
376 5 size_t size_limit = 64u << 20; // 64 MiB
377 5 file_history_load(&e->file_history, path_join(dir, "file-history"), size_limit);
378 5 history_load(&e->command_history, path_join(dir, "command-history"), size_limit);
379 5 history_load(&e->search_history, path_join(dir, "search-history"), size_limit);
380
381 // There's not much sense in saving history for headless sessions, but we
382 // do still load it (above), to make it available to e.g. `exec -i command`
383
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!(e->flags & EFLAG_HEADLESS)) {
384 e->flags |= EFLAG_SAVE_ALL_HIST;
385 }
386
387
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (e->search_history.last) {
388 search_set_regexp(&e->search, e->search_history.last->text);
389 }
390 5 }
391
392 5 static void write_history_files(const EditorState *e)
393 {
394 5 EditorFlags flags = e->flags;
395
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (flags & EFLAG_SAVE_CMD_HIST) {
396 history_save(&e->command_history);
397 }
398
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (flags & EFLAG_SAVE_SEARCH_HIST) {
399 history_save(&e->search_history);
400 }
401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (flags & EFLAG_SAVE_FILE_HIST) {
402 file_history_save(&e->file_history);
403 }
404 5 }
405
406 static const char copyright[] =
407 "dte " VERSION "\n"
408 "(C) 2013-2024 Craig Barnes\n"
409 "(C) 2010-2015 Timo Hirvonen\n"
410 "This program is free software; you can redistribute and/or modify\n"
411 "it under the terms of the GNU General Public License version 2\n"
412 "<https://www.gnu.org/licenses/old-licenses/gpl-2.0.html>.\n"
413 "There is NO WARRANTY, to the extent permitted by law.\n";
414
415 static const char usage[] =
416 "Usage: %s [OPTIONS] [[+LINE] FILE]...\n\n"
417 "Options:\n"
418 " -c COMMAND Run COMMAND after editor starts\n"
419 " -t CTAG Jump to source location of CTAG\n"
420 " -r RCFILE Read user config from RCFILE instead of ~/.dte/rc\n"
421 " -s FILE Validate dte-syntax commands in FILE and exit\n"
422 " -b NAME Print built-in config matching NAME and exit\n"
423 " -B Print list of built-in config names and exit\n"
424 " -H Don't load or save history files\n"
425 " -R Don't read user config file\n"
426 " -K Start editor in \"showkey\" mode\n"
427 " -h Display help summary and exit\n"
428 " -V Display version number and exit\n"
429 "\n";
430
431 // NOLINTNEXTLINE(readability-function-size)
432 17 int main(int argc, char *argv[])
433 {
434 17 static const char optstring[] = "hBHKRVC:Q:S:b:c:t:r:s:";
435 17 const char *rc = NULL;
436 17 const char *commands[8];
437 17 const char *tags[8];
438 17 size_t nr_commands = 0;
439 17 size_t nr_tags = 0;
440 17 bool headless = false;
441 17 bool read_rc = true;
442 17 bool load_and_save_history = true;
443 17 bool explicit_term_query_level = false;
444 17 unsigned int terminal_query_level = 1;
445 17 errors_to_stderr(true);
446
447
2/2
✓ Branch 0 taken 34 times.
✓ Branch 1 taken 6 times.
40 for (int ch; (ch = getopt(argc, argv, optstring)) != -1; ) {
448
9/14
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✓ Branch 11 taken 1 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 2 times.
34 switch (ch) {
449 5 case 'C':
450 5 headless = true;
451 // Fallthrough
452 16 case 'c':
453
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 15 times.
16 if (unlikely(nr_commands >= ARRAYLEN(commands))) {
454 1 fputs("Error: too many -c or -C options used\n", stderr);
455 1 return EC_USAGE_ERROR;
456 }
457 15 commands[nr_commands++] = optarg;
458 15 break;
459 9 case 't':
460
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 8 times.
9 if (unlikely(nr_tags >= ARRAYLEN(tags))) {
461 1 fputs("Error: too many -t options used\n", stderr);
462 1 return EC_USAGE_ERROR;
463 }
464 8 tags[nr_tags++] = optarg;
465 8 break;
466 case 'r':
467 rc = optarg;
468 break;
469 2 case 's':
470 case 'S':
471
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
4 return lint_syntax(optarg, ch == 'S' ? SYN_LINT : 0);
472 case 'R':
473 read_rc = false;
474 break;
475 2 case 'b':
476 2 return dump_builtin_config(optarg);
477 1 case 'B':
478 1 return list_builtin_configs();
479 case 'H':
480 load_and_save_history = false;
481 break;
482 case 'Q':
483 if (!str_to_uint(optarg, &terminal_query_level)) {
484 fprintf(stderr, "Error: invalid argument for -Q: '%s'\n", optarg);
485 return EC_USAGE_ERROR;
486 }
487 explicit_term_query_level = true;
488 break;
489 case 'K':
490 return showkey_loop(terminal_query_level);
491 1 case 'V':
492 1 return write_stdout(copyright, sizeof(copyright));
493 1 case 'h':
494 1 printf(usage, progname(argc, argv, "dte"));
495 1 return EC_OK;
496 default:
497 return EC_USAGE_ERROR;
498 }
499 }
500
501 // This must be done before calling init_logging(), otherwise an
502 // invocation like e.g. `DTE_LOG=/dev/pts/2 dte 0<&-` could
503 // cause the logging fd to be opened as STDIN_FILENO
504 6 int std_fds[2] = {-1, -1};
505
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 ExitCode r = headless ? init_std_fds_headless(std_fds) : init_std_fds(std_fds);
506
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 if (unlikely(r != EC_OK)) {
507 return r;
508 }
509
510 5 r = init_logging(getenv("DTE_LOG"), getenv("DTE_LOG_LEVEL"));
511
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (unlikely(r != EC_OK)) {
512 return r;
513 }
514
515 5 struct utsname u;
516
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (likely(uname(&u) >= 0)) {
517 5 LOG_INFO("system: %s/%s %s", u.sysname, u.machine, u.release);
518
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if (!explicit_term_query_level && str_has_suffix(u.release, "-WSL2")) {
519 // There appears to be an issue on WSL2 where the DA1 query
520 // response is interpreted as pasted text and then inserted
521 // into the buffer at startup. For now, we simply disable
522 // querying entirely on that platform, until the problem
523 // can be investigated.
524 LOG_NOTICE("WSL2 detected; disabling all terminal queries");
525 terminal_query_level = 0;
526 }
527 } else {
528 LOG_ERRNO("uname");
529 }
530
531 5 LOG_INFO("dte version: " VERSION);
532 5 LOG_INFO("build vars:%s", buildvar_string);
533
534
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (headless) {
535 5 set_signal_dispositions_headless();
536 } else {
537 if (!term_mode_init()) {
538 return ec_error("tcgetattr", EC_IO_ERROR);
539 }
540 set_basic_signal_dispositions();
541 }
542
543 5 EditorState *e = init_editor_state(headless ? EFLAG_HEADLESS : 0);
544 5 Terminal *term = &e->terminal;
545
546
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!headless) {
547 term_init(term, getenv("TERM"), getenv("COLORTERM"));
548 }
549
550 5 Buffer *std_buffer = init_std_buffer(e, std_fds);
551
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 bool have_stdout_buffer = std_buffer && std_buffer->stdout_buffer;
552
553 // Create this early (needed if "lock-files" is true)
554 5 const char *cfgdir = e->user_config_dir;
555 5 BUG_ON(!cfgdir);
556
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
5 if (mkdir(cfgdir, 0755) != 0 && errno != EEXIST) {
557 error_msg("Error creating %s: %s", cfgdir, strerror(errno));
558 load_and_save_history = false;
559 e->options.lock_files = false;
560 }
561
562 5 term_save_title(term);
563 5 exec_builtin_rc(e);
564
565
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (read_rc) {
566 5 exec_user_rc(e, rc);
567 }
568
569 5 log_config_counts(e);
570 5 update_all_syntax_styles(&e->syntaxes, &e->styles);
571
572 5 Window *window = new_window(e);
573 5 e->window = window;
574 5 e->root_frame = new_root_frame(window);
575
576
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (load_and_save_history) {
577 5 read_history_files(e);
578 }
579
580
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!headless) {
581 errors_to_stderr(false);
582 // Initialize terminal but don't update screen yet
583 if (unlikely(!term_raw())) {
584 return ec_error("tcsetattr", EC_IO_ERROR);
585 }
586 if (get_nr_errors()) {
587 // Display "Press any key to continue" prompt, if there were
588 // errors while reading config files
589 any_key(term, e->options.esc_timeout);
590 clear_error();
591 }
592 }
593
594 5 e->status = EDITOR_RUNNING;
595
596
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 for (size_t i = optind, line = 0, col = 0; i < argc; i++) {
597 const char *str = argv[i];
598 if (line == 0 && *str == '+' && str_to_filepos(str + 1, &line, &col)) {
599 continue;
600 }
601 View *view = window_open_buffer(window, str, false, NULL);
602 if (line == 0) {
603 continue;
604 }
605 set_view(view);
606 move_to_filepos(view, line, col);
607 line = 0;
608 }
609
610
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (std_buffer) {
611 5 window_add_buffer(window, std_buffer);
612 }
613
614 5 View *dview = NULL;
615
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (window->views.count == 0) {
616 // Open a default buffer, if none were opened for arguments
617 dview = window_open_empty_buffer(window);
618 }
619
620 5 set_view(window_get_first_view(window));
621 5 ui_first_start(e, terminal_query_level);
622
623
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 5 times.
10 for (size_t i = 0; i < nr_commands; i++) {
624 5 handle_normal_command(e, commands[i], false);
625 }
626
627
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (headless) {
628 5 goto exit;
629 }
630
631 size_t opened_tags = 0;
632 for (size_t i = 0; i < nr_tags; i++) {
633 StringView tag_sv = strview_from_cstring(tags[i]);
634 // TODO: Split `tag_lookup()` into multiple functions, to avoid
635 // duplicated clearing/loading in cases like this
636 if (tag_lookup(&e->tagfile, &tag_sv, NULL, &e->messages)) {
637 if (activate_current_message(&e->messages, e->window)) {
638 opened_tags++;
639 }
640 }
641 }
642
643 if (dview && nr_commands == 0 && opened_tags > 0) {
644 // Close default/empty buffer, if `-t` jumped to a tag and no
645 // commands were executed via `-c`
646 BUG_ON(window->views.count < 2);
647 remove_view(dview);
648 dview = NULL;
649 }
650
651 set_fatal_error_cleanup_handler(cleanup_handler, e);
652 set_fatal_signal_handlers();
653 set_sigwinch_handler();
654
655 if (nr_commands + nr_tags > 0) {
656 const ScreenState s = get_screen_state(e->view);
657 update_screen(e, &s);
658 }
659
660 main_loop(e);
661 term_restore_title(term);
662
663 /*
664 * This is normally followed immediately by term_cooked() in other
665 * contexts, but in this case we want to switch back to cooked mode
666 * as late as possible. On underpowered machines, unlocking files and
667 * writing history may take some time. If cooked mode were switched
668 * to here it'd leave a window of time where we've switched back to
669 * the normal screen buffer and reset the termios ECHO flag while
670 * still being the foreground process, which would cause any input
671 * to be echoed to the terminal (before the shell takes over again
672 * and prints its prompt).
673 */
674 ui_end(e);
675
676 5 exit:
677 5 errors_to_stderr(true);
678 5 frame_remove(e, e->root_frame); // Unlock files and add to file history
679 5 write_history_files(e);
680
681 5 int exit_code = e->status;
682
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (headless) {
683 5 exit_code = MAX(exit_code, EDITOR_EXIT_OK);
684 } else {
685 // This must be done before calling buffer_write_blocks_and_free(),
686 // since output modes need to be restored to get proper line ending
687 // translation and std_fds[STDOUT_FILENO] may be a pipe to the
688 // terminal
689 term_cooked();
690 }
691
692
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (have_stdout_buffer) {
693 5 bool ok = buffer_write_blocks_and_free(std_buffer, std_fds[STDOUT_FILENO]);
694
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!ok && exit_code == EDITOR_EXIT_OK) {
695 exit_code = EC_IO_ERROR;
696 }
697 }
698
699 5 if (DEBUG >= 1 || ASAN_ENABLED == 1 || xgetenv(ld_preload_env_var())) {
700 // Only free EditorState in debug builds or when a library/tool is
701 // checking for leaks; otherwise it's pointless to do so immediately
702 // before exiting
703 5 free_editor_state(e);
704 }
705
706 5 LOG_INFO("exiting with status %d", exit_code);
707 5 log_close();
708 5 return exit_code;
709 }
710