dte test coverage


Directory: ./
File: src/config.c
Date: 2025-07-19 20:13:10
Exec Total Coverage
Lines: 165 167 98.8%
Functions: 16 16 100.0%
Branches: 60 68 88.2%

Line Branch Exec Source
1 #include "feature.h"
2 #include <errno.h>
3 #include <stdbool.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <sys/types.h>
7 #include "config.h"
8 #include "command/cache.h"
9 #include "command/error.h"
10 #include "commands.h"
11 #include "compiler.h"
12 #include "editor.h"
13 #include "syntax/color.h"
14 #include "util/debug.h"
15 #include "util/hashmap.h"
16 #include "util/intmap.h"
17 #include "util/log.h"
18 #include "util/readfile.h"
19 #include "util/str-util.h"
20 #include "util/xsnprintf.h"
21
22 #if HAVE_EMBED
23 #include "builtin-config-embed.h"
24 #else
25 #include "builtin-config.h"
26 #endif
27
28 24 UNITTEST {
29 // NOLINTBEGIN(bugprone-assert-side-effect)
30 24 BUG_ON(!get_builtin_config("rc"));
31 24 BUG_ON(!get_builtin_config("color/reset"));
32 // NOLINTEND(bugprone-assert-side-effect)
33 24 }
34
35 // Odd number of backslashes at end of line?
36 9814 static bool has_line_continuation(StringView line)
37 {
38 9814 ssize_t pos = line.length - 1;
39
4/4
✓ Branch 0 (4→5) taken 9199 times.
✓ Branch 1 (4→6) taken 1514 times.
✓ Branch 2 (5→3) taken 899 times.
✓ Branch 3 (5→6) taken 8300 times.
10713 while (pos >= 0 && line.data[pos] == '\\') {
40 899 pos--;
41 }
42 9814 return (line.length - 1 - pos) & 1;
43 }
44
45 24 UNITTEST {
46 // NOLINTBEGIN(bugprone-assert-side-effect)
47 24 BUG_ON(has_line_continuation(string_view(NULL, 0)));
48 24 BUG_ON(has_line_continuation(strview("0")));
49 24 BUG_ON(!has_line_continuation(strview("1 \\")));
50 24 BUG_ON(has_line_continuation(strview("2 \\\\")));
51 24 BUG_ON(!has_line_continuation(strview("3 \\\\\\")));
52 24 BUG_ON(has_line_continuation(strview("4 \\\\\\\\")));
53 // NOLINTEND(bugprone-assert-side-effect)
54 24 }
55
56 226 bool exec_config(CommandRunner *runner, StringView config)
57 {
58 226 EditorState *e = runner->e;
59 226 ErrorBuffer *ebuf = runner->ebuf;
60
2/2
✓ Branch 0 (2→3) taken 1 times.
✓ Branch 1 (2→4) taken 225 times.
226 if (unlikely(e->include_recursion_count > 64)) {
61 1 return error_msg_for_cmd(ebuf, NULL, "config recursion limit reached");
62 }
63
64 225 bool stop_at_first_err = (runner->flags & CMDRUNNER_STOP_AT_FIRST_ERROR);
65 225 size_t nfailed = 0;
66 225 String buf = string_new(1024);
67 225 e->include_recursion_count++;
68
69
2/2
✓ Branch 0 (22→6) taken 9907 times.
✓ Branch 1 (22→23) taken 224 times.
10131 for (size_t i = 0, n = config.length; i < n; ebuf->config_line++) {
70 9907 StringView line = buf_slice_next_line(config.data, &i, n);
71 9907 strview_trim_left(&line);
72
4/4
✓ Branch 0 (8→9) taken 9250 times.
✓ Branch 1 (8→12) taken 657 times.
✓ Branch 2 (10→11) taken 237 times.
✓ Branch 3 (10→12) taken 9013 times.
9907 if (buf.len == 0 && strview_has_prefix(&line, "#")) {
73 // Comment line
74 237 continue;
75 }
76
2/2
✓ Branch 0 (12→13) taken 659 times.
✓ Branch 1 (12→14) taken 9011 times.
9670 if (has_line_continuation(line)) {
77 659 line.length--;
78 659 string_append_strview(&buf, &line);
79 } else {
80 9011 string_append_strview(&buf, &line);
81 9011 bool r = handle_command(runner, string_borrow_cstring(&buf));
82 9011 string_clear(&buf);
83 9011 nfailed += !r;
84
2/2
✓ Branch 0 (18→19) taken 1 times.
✓ Branch 1 (18→20) taken 9010 times.
9011 if (unlikely(!r && stop_at_first_err)) {
85 1 goto out;
86 }
87 }
88 }
89
90
2/2
✓ Branch 0 (23→24) taken 222 times.
✓ Branch 1 (23→25) taken 2 times.
224 if (unlikely(buf.len)) {
91 // This can only happen if the last line had a line continuation
92 2 bool r = handle_command(runner, string_borrow_cstring(&buf));
93 2 nfailed += !r;
94 }
95
96 222 out:
97 225 e->include_recursion_count--;
98 225 string_free(&buf);
99 225 return (nfailed == 0);
100 }
101
102 3 String dump_builtin_configs(void)
103 {
104 3 String str = string_new(1024);
105
2/2
✓ Branch 0 (7→4) taken 231 times.
✓ Branch 1 (7→8) taken 3 times.
234 for (size_t i = 0; i < ARRAYLEN(builtin_configs); i++) {
106 231 string_append_cstring(&str, builtin_configs[i].name);
107 231 string_append_byte(&str, '\n');
108 }
109 3 return str;
110 }
111
112 149 const BuiltinConfig *get_builtin_config(const char *name)
113 {
114
2/2
✓ Branch 0 (6→3) taken 5203 times.
✓ Branch 1 (6→7) taken 58 times.
5261 for (size_t i = 0; i < ARRAYLEN(builtin_configs); i++) {
115
2/2
✓ Branch 0 (3→4) taken 91 times.
✓ Branch 1 (3→5) taken 5112 times.
5203 if (streq(name, builtin_configs[i].name)) {
116 91 return &builtin_configs[i];
117 }
118 }
119 return NULL;
120 }
121
122 1 const BuiltinConfig *get_builtin_configs_array(size_t *nconfigs)
123 {
124 1 *nconfigs = ARRAYLEN(builtin_configs);
125 1 return &builtin_configs[0];
126 }
127
128 111 static SystemErrno do_read_config (
129 CommandRunner *runner,
130 const char *filename,
131 ConfigFlags flags
132 ) {
133 111 ErrorBuffer *ebuf = runner->ebuf;
134 111 bool must_exist = flags & CFG_MUST_EXIST;
135 111 bool stop_at_first_err = runner->flags & CMDRUNNER_STOP_AT_FIRST_ERROR;
136
137
2/2
✓ Branch 0 (2→3) taken 42 times.
✓ Branch 1 (2→11) taken 69 times.
111 if (flags & CFG_BUILTIN) {
138 42 const BuiltinConfig *cfg = get_builtin_config(filename);
139
2/2
✓ Branch 0 (3→4) taken 41 times.
✓ Branch 1 (3→8) taken 1 times.
42 if (cfg) {
140 41 ebuf->config_filename = filename;
141 41 ebuf->config_line = 1;
142 41 bool r = exec_config(runner, cfg->text);
143
1/2
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→7) taken 41 times.
41 return (r || !stop_at_first_err) ? 0 : EINVAL;
144 }
145
1/2
✓ Branch 0 (8→9) taken 1 times.
✗ Branch 1 (8→10) not taken.
1 if (must_exist) {
146 1 error_msg(ebuf, "no built-in config with name '%s'", filename);
147 }
148 1 return ENOENT;
149 }
150
151 69 char *buf;
152 69 ssize_t size = read_file(filename, &buf, 0);
153
2/2
✓ Branch 0 (12→13) taken 1 times.
✓ Branch 1 (12→17) taken 68 times.
69 if (size < 0) {
154 1 int err = errno;
155
1/2
✓ Branch 0 (13→14) taken 1 times.
✗ Branch 1 (13→16) not taken.
1 if (err != ENOENT || must_exist) {
156 1 error_msg(ebuf, "Error reading %s: %s", filename, strerror(err));
157 }
158 1 return err;
159 }
160
161 68 ebuf->config_filename = filename;
162 68 ebuf->config_line = 1;
163 68 bool r = exec_config(runner, string_view(buf, size));
164 68 free(buf);
165
1/2
✗ Branch 0 (18→19) not taken.
✓ Branch 1 (18→20) taken 68 times.
68 return (r || !stop_at_first_err) ? 0 : EINVAL;
166 }
167
168 111 SystemErrno read_config(CommandRunner *runner, const char *filename, ConfigFlags flags)
169 {
170 111 ErrorBuffer *ebuf = runner->ebuf;
171 111 const char *saved_file = ebuf->config_filename;
172 111 const unsigned int saved_line = ebuf->config_line;
173 111 SystemErrno ret = do_read_config(runner, filename, flags);
174 111 ebuf->config_filename = saved_file;
175 111 ebuf->config_line = saved_line;
176 111 return ret;
177 }
178
179 27 static void exec_builtin_config(EditorState *e, StringView cfg, const char *name)
180 {
181 27 ErrorBuffer *ebuf = &e->err;
182 27 const char *saved_file = ebuf->config_filename;
183 27 const unsigned int saved_line = ebuf->config_line;
184 27 ebuf->config_filename = name;
185 27 ebuf->config_line = 1;
186 27 exec_normal_config(e, cfg);
187 27 ebuf->config_filename = saved_file;
188 27 ebuf->config_line = saved_line;
189 27 }
190
191 18 void exec_builtin_color_reset(EditorState *e)
192 {
193 18 clear_hl_styles(&e->styles);
194 18 StringView reset = string_view(builtin_color_reset, sizeof(builtin_color_reset) - 1);
195 18 exec_builtin_config(e, reset, "color/reset");
196 18 }
197
198 9 static void log_config_counts(const EditorState *e)
199 {
200
2/2
✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→17) taken 8 times.
9 if (!log_level_enabled(LOG_LEVEL_INFO)) {
201 return;
202 }
203
204 1 size_t nbinds = 0;
205 1 size_t nbinds_cached = 0;
206
2/2
✓ Branch 0 (11→5) taken 3 times.
✓ Branch 1 (11→12) taken 1 times.
4 for (HashMapIter modeiter = hashmap_iter(&e->modes); hashmap_next(&modeiter); ) {
207 3 const ModeHandler *mode = modeiter.entry->value;
208 3 const IntMap *binds = &mode->key_bindings;
209 3 nbinds += binds->count;
210
2/2
✓ Branch 0 (8→6) taken 209 times.
✓ Branch 1 (8→9) taken 3 times.
212 for (IntMapIter binditer = intmap_iter(binds); intmap_next(&binditer); ) {
211 209 const CachedCommand *cc = binditer.entry->value;
212 209 nbinds_cached += !!cc->cmd;
213 }
214 }
215
216 1 size_t nerrorfmts = 0;
217
2/2
✓ Branch 0 (15→13) taken 3 times.
✓ Branch 1 (15→16) taken 1 times.
4 for (HashMapIter it = hashmap_iter(&e->compilers); hashmap_next(&it); ) {
218 3 const Compiler *compiler = it.entry->value;
219 3 nerrorfmts += compiler->error_formats.count;
220 }
221
222 1 LOG_INFO (
223 "bind=%zu(%zu) alias=%zu hi=%zu ft=%zu option=%zu errorfmt=%zu(%zu)",
224 nbinds,
225 nbinds_cached,
226 e->aliases.count,
227 e->styles.other.count + NR_BSE,
228 e->filetypes.count,
229 e->file_options.count,
230 e->compilers.count,
231 nerrorfmts
232 );
233 }
234
235 9 void exec_rc_files(EditorState *e, const char *filename, bool read_user_rc)
236 {
237 9 StringView rc = string_view(builtin_rc, sizeof(builtin_rc) - 1);
238 9 exec_builtin_color_reset(e);
239 9 exec_builtin_config(e, rc, "rc");
240
241
2/2
✓ Branch 0 (4→5) taken 2 times.
✓ Branch 1 (4→10) taken 7 times.
9 if (read_user_rc) {
242 2 ConfigFlags flags = CFG_NOFLAGS;
243 2 char buf[8192];
244
1/2
✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→7) taken 2 times.
2 if (filename) {
245 flags |= CFG_MUST_EXIST;
246 } else {
247 xsnprintf(buf, sizeof buf, "%s/%s", e->user_config_dir, "rc");
248 filename = buf;
249 }
250 2 LOG_INFO("loading configuration from %s", filename);
251 2 read_normal_config(e, filename, flags);
252 }
253
254 9 log_config_counts(e);
255 9 update_all_syntax_styles(&e->syntaxes, &e->styles);
256 9 }
257
258 1 void collect_builtin_configs(PointerArray *a, const char *prefix)
259 {
260 1 size_t prefix_len = strlen(prefix);
261
2/2
✓ Branch 0 (7→3) taken 77 times.
✓ Branch 1 (7→8) taken 1 times.
78 for (size_t i = 0; i < ARRAYLEN(builtin_configs); i++) {
262 77 const char *name = builtin_configs[i].name;
263
2/2
✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→6) taken 76 times.
77 if (str_has_strn_prefix(name, prefix, prefix_len)) {
264 1 ptr_array_append(a, xstrdup(name));
265 }
266 }
267 1 }
268
269 // Collect ${builtin:path/name} style variables (minus the leading "${")
270 // TODO: Support ${script:name} variables
271 2 void collect_builtin_config_variables(PointerArray *a, StringView prefix)
272 {
273 2 static const char builtin[] = "builtin:";
274 2 const size_t blen = sizeof(builtin) - 1;
275 2 const size_t cmplen = MIN(prefix.length, blen);
276
277
1/2
✓ Branch 0 (3→9) taken 2 times.
✗ Branch 1 (3→10) not taken.
2 if (!strview_remove_matching_strn_prefix(&prefix, builtin, cmplen)) {
278 return;
279 }
280
281
2/2
✓ Branch 0 (9→4) taken 154 times.
✓ Branch 1 (9→10) taken 2 times.
156 for (size_t i = 0; i < ARRAYLEN(builtin_configs); i++) {
282 154 const char *name = builtin_configs[i].name;
283 154 size_t namelen = strlen(name);
284
2/2
✓ Branch 0 (5→6) taken 82 times.
✓ Branch 1 (5→8) taken 72 times.
154 if (strn_has_strview_prefix(name, namelen, &prefix)) {
285 82 ptr_array_append(a, xmemjoin3(builtin, blen, name, namelen, "}", 2));
286 }
287 }
288 }
289
290 1 void collect_builtin_includes(PointerArray *a, const char *prefix)
291 {
292 1 size_t prefix_len = strlen(prefix);
293
2/2
✓ Branch 0 (9→3) taken 77 times.
✓ Branch 1 (9→10) taken 1 times.
78 for (size_t i = 0; i < ARRAYLEN(builtin_configs); i++) {
294 77 const char *name = builtin_configs[i].name;
295 77 if (
296
2/2
✓ Branch 0 (3→4) taken 1 times.
✓ Branch 1 (3→8) taken 76 times.
77 str_has_strn_prefix(name, prefix, prefix_len)
297
1/2
✓ Branch 0 (4→5) taken 1 times.
✗ Branch 1 (4→8) not taken.
1 && !str_has_prefix(name, "syntax/")
298
1/2
✓ Branch 0 (5→6) taken 1 times.
✗ Branch 1 (5→8) not taken.
1 && !str_has_prefix(name, "script/")
299 ) {
300 1 ptr_array_append(a, xstrdup(name));
301 }
302 }
303 1 }
304