dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 53.9% 48 / 0 / 89
Functions: 88.9% 16 / 0 / 18
Branches: 37.0% 20 / 0 / 54

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