dte test coverage


Directory: ./
File: src/vars.c
Date: 2025-07-13 15:27:15
Exec Total Coverage
Lines: 49 81 60.5%
Functions: 16 17 94.1%
Branches: 20 52 38.5%

Line Branch Exec Source
1 #include <stddef.h>
2 #include <string.h>
3 #include <unistd.h>
4 #include "vars.h"
5 #include "editor.h"
6 #include "selection.h"
7 #include "util/bsearch.h"
8 #include "util/numtostr.h"
9 #include "util/path.h"
10 #include "util/str-util.h"
11 #include "util/xmalloc.h"
12 #include "view.h"
13
14 // TODO: Make expand() functions return a StringVariant type and
15 // only do dynamic allocations when necessary
16 typedef struct {
17 char name[12];
18 char *(*expand)(const EditorState *e);
19 } BuiltinVar;
20
21 2 static char *expand_dte_home(const EditorState *e)
22 {
23 2 return xstrdup(e->user_config_dir);
24 }
25
26 1 static char *expand_file(const EditorState *e)
27 {
28
1/4
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→5) taken 1 times.
✗ Branch 2 (3→4) not taken.
✗ Branch 3 (3→5) not taken.
1 if (!e->buffer || !e->buffer->abs_filename) {
29 return NULL;
30 }
31 return xstrdup(e->buffer->abs_filename);
32 }
33
34 1 static char *expand_file_dir(const EditorState *e)
35 {
36
1/4
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→5) taken 1 times.
✗ Branch 2 (3→4) not taken.
✗ Branch 3 (3→5) not taken.
1 if (!e->buffer || !e->buffer->abs_filename) {
37 return NULL;
38 }
39 return path_dirname(e->buffer->abs_filename);
40 }
41
42 2 static char *expand_rfile(const EditorState *e)
43 {
44
1/4
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→8) taken 2 times.
✗ Branch 2 (3→4) not taken.
✗ Branch 3 (3→8) not taken.
2 if (!e->buffer || !e->buffer->abs_filename) {
45 return NULL;
46 }
47 char buf[8192];
48 const char *cwd = getcwd(buf, sizeof buf);
49 const char *abs = e->buffer->abs_filename;
50 return likely(cwd) ? path_relative(abs, cwd) : xstrdup(abs);
51 }
52
53 1 static char *expand_rfiledir(const EditorState *e)
54 {
55 1 char *rfile = expand_rfile(e);
56
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→7) taken 1 times.
1 if (!rfile) {
57 return NULL;
58 }
59
60 StringView dir = path_slice_dirname(rfile);
61 if (rfile != (char*)dir.data) {
62 // rfile contains no directory part (i.e. no slashes), so there's
63 // nothing to "slice" and we simply return "." instead
64 free(rfile);
65 return xstrdup(".");
66 }
67
68 // Overwrite the last slash in rfile with a null terminator, so as
69 // to remove the last path component (the filename)
70 rfile[dir.length] = '\0';
71 return rfile;
72 }
73
74 1 static char *expand_filetype(const EditorState *e)
75 {
76
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→4) taken 1 times.
1 return e->buffer ? xstrdup(e->buffer->options.filetype) : NULL;
77 }
78
79 1 static char *expand_colno(const EditorState *e)
80 {
81
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→5) taken 1 times.
1 return e->view ? xstrdup(umax_to_str(e->view->cx_display + 1)) : NULL;
82 }
83
84 1 static char *expand_lineno(const EditorState *e)
85 {
86
1/2
✗ Branch 0 (2→3) not taken.
✓ Branch 1 (2→5) taken 1 times.
1 return e->view ? xstrdup(umax_to_str(e->view->cy + 1)) : NULL;
87 }
88
89 4 static char *msgpos(const EditorState *e, size_t idx)
90 {
91 4 return xstrdup(umax_to_str(e->messages[idx].pos + 1));
92 }
93
94 2 static char *expand_msgpos_a(const EditorState *e) {return msgpos(e, 0);}
95 1 static char *expand_msgpos_b(const EditorState *e) {return msgpos(e, 1);}
96 1 static char *expand_msgpos_c(const EditorState *e) {return msgpos(e, 2);}
97
98 4 static char *expand_word(const EditorState *e)
99 {
100 4 const View *view = e->view;
101
2/2
✓ Branch 0 (2→3) taken 3 times.
✓ Branch 1 (2→14) taken 1 times.
4 if (unlikely(!view)) {
102 return NULL;
103 }
104
105
1/2
✗ Branch 0 (3→4) not taken.
✓ Branch 1 (3→11) taken 3 times.
3 if (view->selection) {
106 SelectionInfo info = init_selection(view);
107 size_t len = info.eo - info.so;
108 if (unlikely(len == 0)) {
109 return NULL;
110 }
111 char *selection = block_iter_get_bytes(&info.si, len);
112 BUG_ON(!selection);
113 selection[len] = '\0'; // See comment in block_iter_get_bytes()
114 return selection;
115 }
116
117 3 StringView word = view_get_word_under_cursor(view);
118
1/2
✓ Branch 0 (12→13) taken 3 times.
✗ Branch 1 (12→14) not taken.
3 return word.length ? xstrcut(word.data, word.length) : NULL;
119 }
120
121 static const BuiltinVar normal_vars[] = {
122 {"COLNO", expand_colno},
123 {"DTE_HOME", expand_dte_home},
124 {"FILE", expand_file},
125 {"FILEDIR", expand_file_dir},
126 {"FILETYPE", expand_filetype},
127 {"LINENO", expand_lineno},
128 {"MSGPOS", expand_msgpos_a},
129 {"MSGPOS_A", expand_msgpos_a},
130 {"MSGPOS_B", expand_msgpos_b},
131 {"MSGPOS_C", expand_msgpos_c},
132 {"RFILE", expand_rfile},
133 {"RFILEDIR", expand_rfiledir},
134 {"WORD", expand_word},
135 };
136
137 24 UNITTEST {
138 24 CHECK_BSEARCH_ARRAY(normal_vars, name);
139 24 }
140
141 // Handles variables like e.g. ${builtin:color/reset} and ${script:file.sh}
142 static char *expand_prefixed_var(const char *name, size_t name_len, StringView prefix)
143 {
144 char buf[64];
145 bool name_fits_buf = (name_len < sizeof(buf));
146
147 if (strview_equal_cstring(&prefix, "builtin")) {
148 // Skip past "builtin:"
149 name += prefix.length + 1;
150 } else if (strview_equal_cstring(&prefix, "script") && name_fits_buf) {
151 // Copy `name` into `buf` and replace ':' with '/'
152 memcpy(buf, name, name_len + 1);
153 buf[prefix.length] = '/';
154 name = buf;
155 } else {
156 return NULL;
157 }
158
159 const BuiltinConfig *cfg = get_builtin_config(name);
160 return cfg ? xmemdup(cfg->text.data, cfg->text.length + 1) : NULL;
161 }
162
163 25 char *expand_normal_var(const EditorState *e, const char *name)
164 {
165 25 size_t name_len = strlen(name);
166
1/2
✓ Branch 0 (2→3) taken 25 times.
✗ Branch 1 (2→12) not taken.
25 if (unlikely(name_len == 0)) {
167 return NULL;
168 }
169
170 25 size_t pos = 0;
171 25 StringView prefix = get_delim(name, &pos, name_len, ':');
172
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→6) taken 25 times.
25 if (prefix.length < name_len) {
173 return expand_prefixed_var(name, name_len, prefix);
174 }
175
176 25 const BuiltinVar *var = BSEARCH(name, normal_vars, vstrcmp);
177
2/2
✓ Branch 0 (7→8) taken 17 times.
✓ Branch 1 (7→9) taken 8 times.
25 if (var) {
178 17 return var->expand(e);
179 }
180
181 8 const char *str = xgetenv(name);
182
2/2
✓ Branch 0 (10→11) taken 7 times.
✓ Branch 1 (10→12) taken 1 times.
8 return str ? xstrdup(str) : NULL;
183 }
184
185 3 void collect_normal_vars (
186 PointerArray *a,
187 StringView prefix, // Prefix to match against
188 const char *suffix // Suffix to append to collected strings
189 ) {
190 3 size_t suffix_len = strlen(suffix) + 1;
191
2/2
✓ Branch 0 (8→3) taken 39 times.
✓ Branch 1 (8→9) taken 3 times.
42 for (size_t i = 0; i < ARRAYLEN(normal_vars); i++) {
192 39 const char *var = normal_vars[i].name;
193 39 size_t var_len = strlen(var);
194
1/2
✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→7) taken 39 times.
39 if (strn_has_strview_prefix(var, var_len, &prefix)) {
195 ptr_array_append(a, xmemjoin(var, var_len, suffix, suffix_len));
196 }
197 }
198 3 }
199