dte test coverage


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 50.0% high: ≥ 85.0%
Coverage Exec / Excl / Total
Lines: 96.2% 229 / 0 / 238
Functions: 100.0% 24 / 0 / 24
Branches: 84.3% 91 / 28 / 136

src/buffer.c
Line Branch Exec Source
1 #include <stdlib.h>
2 #include <string.h>
3 #include <sys/stat.h>
4 #include "buffer.h"
5 #include "editor.h"
6 #include "encoding.h"
7 #include "file-option.h"
8 #include "filetype.h"
9 #include "syntax/state.h"
10 #include "util/intern.h"
11 #include "util/path.h"
12 #include "util/xmalloc.h"
13 #include "util/xstring.h"
14
15 130 void buffer_set_display_filename(Buffer *buffer, char *name)
16 {
17 130 free(buffer->display_filename);
18 130 buffer->display_filename = name;
19 130 }
20
21 /*
22 * Mark line range min...max (inclusive) "changed". These lines will be
23 * redrawn when screen is updated. This is called even when content has not
24 * been changed, but selection has or line has been deleted and all lines
25 * after the deleted line move up.
26 *
27 * Syntax highlighter has different logic. It cares about contents of the
28 * lines, not about selection or if the lines have been moved up or down.
29 */
30 487 void buffer_mark_lines_changed(Buffer *buffer, long min, long max)
31 {
32 487 buffer->changed_line_min = MIN3(min, max, buffer->changed_line_min);
33 487 buffer->changed_line_max = MAX3(min, max, buffer->changed_line_max);
34 487 }
35
36 4 const char *buffer_filename(const Buffer *buffer)
37 {
38 4 const char *name = buffer->display_filename;
39
2/2
✓ Branch 2 → 3 taken 3 times.
✓ Branch 2 → 4 taken 1 time.
4 return name ? name : "(No name)";
40 }
41
42 62 void buffer_set_encoding(Buffer *buffer, const char *encoding, bool utf8_bom)
43 {
44 62 if (DEBUG_ASSERTIONS_ENABLED) {
45 62 const char *nenc = encoding_normalize(encoding);
46 62 BUG_ON(encoding != nenc);
47 }
48
49
2/2
✓ Branch 5 → 6 taken 59 times.
✓ Branch 5 → 10 taken 3 times.
62 if (interned_strings_equal(buffer->encoding, encoding)) {
50 return;
51 }
52
53 59 EncodingType type = lookup_encoding(encoding);
54
1/2
✗ Branch 7 → 8 not taken.
✓ Branch 7 → 9 taken 59 times.
59 buffer->bom = (type == UTF8) ? utf8_bom : encoding_type_has_bom(type);
55 59 buffer->encoding = encoding;
56 }
57
58 86 Buffer *buffer_new(PointerArray *buffers, const GlobalOptions *gopts, const char *encoding)
59 {
60 86 static unsigned long id;
61 86 Buffer *buffer = xcalloc1(sizeof(*buffer));
62 86 list_init(&buffer->blocks);
63 86 buffer->cur_change = &buffer->change_head;
64 86 buffer->saved_change = &buffer->change_head;
65 86 buffer->id = ++id;
66 86 buffer->crlf_newlines = gopts->crlf_newlines;
67
68
2/2
✓ Branch 4 → 5 taken 59 times.
✓ Branch 4 → 6 taken 27 times.
86 if (encoding) {
69 59 buffer_set_encoding(buffer, encoding, gopts->utf8_bom);
70 }
71
72 // Note that using sizeof(CommonOptions) may cause this memcpy() to
73 // overwrite some LocalOptions-specific members with padding bytes.
74 // Thus, all LocalOptions members that are not also in CommonOptions
75 // *must* be explicitly initialized after this memcpy() and the fact
76 // they were originally zeroed by xcalloc() cannot be relied upon.
77 86 static_assert(sizeof(*gopts) >= sizeof(CommonOptions));
78 86 memcpy(&buffer->options, gopts, sizeof(CommonOptions));
79 86 buffer->options.brace_indent = 0;
80 86 buffer->options.filetype = str_intern("none");
81 86 buffer->options.indent_regex = NULL;
82
83 86 ptr_array_append(buffers, buffer);
84 86 return buffer;
85 }
86
87 51 Buffer *open_empty_buffer(PointerArray *buffers, const GlobalOptions *gopts)
88 {
89 51 Buffer *buffer = buffer_new(buffers, gopts, encoding_from_type(UTF8));
90
91 // At least one block required
92 51 Block *blk = block_new(1);
93 51 list_insert_before(&blk->node, &buffer->blocks);
94
95 51 return buffer;
96 }
97
98 86 void free_blocks(Buffer *buffer)
99 {
100
2/2
✓ Branch 4 → 3 taken 116 times.
✓ Branch 4 → 5 taken 86 times.
202 for (ListHead *head = &buffer->blocks, *item = head->next; item != head; ) {
101 116 ListHead *next = item->next;
102 116 Block *blk = BLOCK(item);
103 116 free(blk->data);
104 116 free(blk);
105 116 item = next;
106 }
107 86 }
108
109 86 static void buffer_unlock_and_free (
110 Buffer *buffer,
111 ErrorBuffer *ebuf,
112 const FileLocksContext *locks_ctx
113 ) {
114
1/2
✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 86 times.
86 if (buffer->locked) {
115 unlock_file(locks_ctx, ebuf, buffer->abs_filename);
116 }
117
118 86 free_changes(&buffer->change_head);
119 86 ptr_array_free_array(&buffer->line_start_states);
120 86 ptr_array_free_array(&buffer->views);
121 86 free(buffer->display_filename);
122 86 free(buffer->abs_filename);
123
124
2/2
✓ Branch 7 → 8 taken 78 times.
✓ Branch 7 → 10 taken 8 times.
86 if (buffer->stdout_buffer) {
125 /*
126 * If this buffer is to be piped to stdout on exit, retain just
127 * the blocks and the buffer itself. After this point, the pointer
128 * in main() takes ownership and is responsible for freeing the
129 * remaining allocations.
130 *
131 * See also:
132 * • init_std_buffer()
133 * • buffer_write_blocks_and_free()
134 * • main()
135 * • cmd_save()
136 */
137 return;
138 }
139
140 78 free_blocks(buffer);
141 78 free(buffer);
142 }
143
144 11 void free_buffers (
145 PointerArray *buffers,
146 ErrorBuffer *ebuf,
147 const FileLocksContext *locks_ctx
148 ) {
149
1/2
✗ Branch 5 → 3 not taken.
✓ Branch 5 → 6 taken 11 times.
11 for (size_t i = 0, n = buffers->count; i < n; i++) {
150 Buffer *buffer = buffers->ptrs[i];
151 buffer_unlock_and_free(buffer, ebuf, locks_ctx);
152 buffers->ptrs[i] = NULL;
153 }
154 11 ptr_array_free_array(buffers);
155 11 }
156
157 86 void buffer_remove_unlock_and_free (
158 PointerArray *buffers,
159 Buffer *buffer,
160 ErrorBuffer *ebuf,
161 const FileLocksContext *locks_ctx
162 ) {
163 86 ptr_array_remove(buffers, buffer);
164 86 buffer_unlock_and_free(buffer, ebuf, locks_ctx);
165 86 }
166
167 873 static bool same_file(const Buffer *buffer, const struct stat *st)
168 {
169
3/4
✓ Branch 2 → 3 taken 349 times.
✓ Branch 2 → 4 taken 524 times.
✓ Branch 3 → 4 taken 349 times.
✗ Branch 3 → 5 not taken.
873 return (st->st_dev == buffer->file.dev) && (st->st_ino == buffer->file.ino);
170 }
171
172 27 Buffer *find_buffer(const PointerArray *buffers, const char *abs_filename)
173 {
174 27 struct stat st;
175 27 bool st_ok = stat(abs_filename, &st) == 0;
176
2/2
✓ Branch 9 → 4 taken 873 times.
✓ Branch 9 → 10 taken 27 times.
900 for (size_t i = 0, n = buffers->count; i < n; i++) {
177 873 Buffer *buffer = buffers->ptrs[i];
178 873 const char *f = buffer->abs_filename;
179
5/8
✓ Branch 4 → 5 taken 349 times.
✓ Branch 4 → 6 taken 524 times.
✓ Branch 5 → 6 taken 349 times.
✗ Branch 5 → 10 not taken.
✓ Branch 6 → 7 taken 873 times.
✗ Branch 6 → 8 not taken.
✓ Branch 7 → 8 taken 873 times.
✗ Branch 7 → 10 not taken.
873 if ((f && streq(f, abs_filename)) || (st_ok && same_file(buffer, &st))) {
180 return buffer;
181 }
182 }
183 return NULL;
184 }
185
186 11 Buffer *find_buffer_by_id(const PointerArray *buffers, unsigned long id)
187 {
188
2/2
✓ Branch 5 → 3 taken 72 times.
✓ Branch 5 → 6 taken 7 times.
79 for (size_t i = 0, n = buffers->count; i < n; i++) {
189 72 Buffer *buffer = buffers->ptrs[i];
190
2/2
✓ Branch 3 → 4 taken 68 times.
✓ Branch 3 → 6 taken 4 times.
72 if (buffer->id == id) {
191 return buffer;
192 }
193 }
194 return NULL;
195 }
196
197 81 bool buffer_detect_filetype(Buffer *buffer, const PointerArray *filetypes)
198 {
199 81 StringView line = STRING_VIEW_INIT;
200
2/2
✓ Branch 2 → 3 taken 26 times.
✓ Branch 2 → 5 taken 55 times.
81 if (buffer_get_first_block(buffer)->size) {
201 26 BlockIter bi = block_iter(buffer);
202 26 line = block_iter_get_line(&bi);
203
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 11 taken 55 times.
55 } else if (!buffer->abs_filename) {
204 return false;
205 }
206
207 26 const char *ft = find_ft(filetypes, buffer->abs_filename, line);
208
3/4
✓ Branch 7 → 8 taken 4 times.
✓ Branch 7 → 11 taken 22 times.
✓ Branch 8 → 9 taken 4 times.
✗ Branch 8 → 11 not taken.
26 if (ft && !streq(ft, buffer->options.filetype)) {
209 4 buffer->options.filetype = str_intern(ft);
210 4 return true;
211 }
212
213 return false;
214 }
215
216 136 void buffer_update_short_filename_cwd(Buffer *buffer, StringView home, const char *cwd)
217 {
218 136 const char *abs = buffer->abs_filename;
219
2/2
✓ Branch 2 → 3 taken 73 times.
✓ Branch 2 → 7 taken 63 times.
136 if (!abs) {
220 return;
221 }
222
1/2
✓ Branch 3 → 4 taken 73 times.
✗ Branch 3 → 5 not taken.
73 char *name = cwd ? short_filename_cwd(abs, cwd, home) : xstrdup(abs);
223 73 buffer_set_display_filename(buffer, name);
224 }
225
226 48 void buffer_update_short_filename(Buffer *buffer, StringView home)
227 {
228 48 const char *abs = buffer->abs_filename;
229 48 BUG_ON(!abs);
230 48 buffer_set_display_filename(buffer, short_filename(abs, home));
231 48 }
232
233 63 void buffer_update_syntax(EditorState *e, Buffer *buffer)
234 {
235 63 Syntax *syn = NULL;
236
2/2
✓ Branch 2 → 3 taken 62 times.
✓ Branch 2 → 6 taken 1 time.
63 if (buffer->options.syntax) {
237 // Even "none" can have syntax
238 62 syn = find_syntax(&e->syntaxes, buffer->options.filetype);
239
2/2
✓ Branch 4 → 5 taken 57 times.
✓ Branch 4 → 6 taken 5 times.
62 if (!syn) {
240 57 syn = load_syntax_by_filetype(e, buffer->options.filetype);
241 }
242 }
243
2/2
✓ Branch 6 → 7 taken 6 times.
✓ Branch 6 → 12 taken 57 times.
63 if (syn == buffer->syntax) {
244 return;
245 }
246
247 6 buffer->syntax = syn;
248
2/2
✓ Branch 7 → 8 taken 5 times.
✓ Branch 7 → 11 taken 1 time.
6 if (syn) {
249 // Start state of first line is constant
250 5 PointerArray *s = &buffer->line_start_states;
251
1/2
✓ Branch 8 → 9 taken 5 times.
✗ Branch 8 → 10 not taken.
5 if (!s->alloc) {
252 5 ptr_array_init(s, 64);
253 }
254 5 s->ptrs[0] = syn->start_state;
255 5 s->count = 1;
256 }
257
258 6 mark_all_lines_changed(buffer);
259 }
260
261 48 static bool allow_odd_indent(uint8_t indents_bitmask)
262 {
263 48 static_assert(INDENT_WIDTH_MAX == 8);
264 48 return !!(indents_bitmask & 0x55); // 0x55 == 0b01010101
265 }
266
267 211 static int indent_len(StringView line, uint8_t indents_bitmask, bool *tab_indent)
268 {
269 211 bool space_before_tab = false;
270 211 size_t spaces = 0;
271 211 size_t tabs = 0;
272 211 size_t pos = 0;
273
274
2/2
✓ Branch 9 → 3 taken 855 times.
✓ Branch 9 → 10 taken 26 times.
881 for (size_t n = line.length; pos < n; pos++) {
275
3/3
✓ Branch 3 → 4 taken 120 times.
✓ Branch 3 → 7 taken 550 times.
✓ Branch 3 → 10 taken 185 times.
855 switch (line.data[pos]) {
276 120 case '\t':
277 120 tabs++;
278
2/2
✓ Branch 4 → 5 taken 48 times.
✓ Branch 4 → 6 taken 72 times.
120 if (spaces) {
279 48 space_before_tab = true;
280 }
281 120 continue;
282 550 case ' ':
283 550 spaces++;
284 550 continue;
285 }
286 break;
287 }
288
289 211 *tab_indent = false;
290
2/2
✓ Branch 10 → 11 taken 185 times.
✓ Branch 10 → 21 taken 26 times.
211 if (pos == line.length) {
291 return -1; // Whitespace only
292 }
293
2/2
✓ Branch 11 → 12 taken 151 times.
✓ Branch 11 → 21 taken 34 times.
185 if (pos == 0) {
294 return 0; // Not indented
295 }
296
2/2
✓ Branch 12 → 13 taken 127 times.
✓ Branch 12 → 21 taken 24 times.
151 if (space_before_tab) {
297 return -2; // Mixed indent
298 }
299
2/2
✓ Branch 13 → 14 taken 24 times.
✓ Branch 13 → 15 taken 103 times.
127 if (tabs) {
300 // Tabs and possible spaces after tab for alignment
301 24 *tab_indent = true;
302 24 return tabs * 8;
303 }
304
3/4
✓ Branch 15 → 16 taken 103 times.
✗ Branch 15 → 20 not taken.
✓ Branch 16 → 17 taken 72 times.
✓ Branch 16 → 20 taken 31 times.
103 if (line.length > spaces && line.data[spaces] == '*') {
305 // '*' after indent, could be long C style comment
306
4/4
✓ Branch 17 → 18 taken 48 times.
✓ Branch 17 → 19 taken 24 times.
✓ Branch 18 → 19 taken 24 times.
✓ Branch 18 → 20 taken 24 times.
72 if (spaces & 1 || allow_odd_indent(indents_bitmask)) {
307 48 return spaces - 1;
308 }
309 }
310 55 return spaces;
311 }
312
313 24 UNITTEST {
314 24 bool tab;
315 24 int len = indent_len(strview(" 4 space"), 0, &tab);
316 24 BUG_ON(len != 4);
317 24 BUG_ON(tab);
318
319 24 len = indent_len(strview("\t\t2 tab"), 0, &tab);
320 24 BUG_ON(len != 16);
321 24 BUG_ON(!tab);
322
323 24 len = indent_len(strview("no indent"), 0, &tab);
324 24 BUG_ON(len != 0);
325
326 24 len = indent_len(strview(" \t mixed"), 0, &tab);
327 24 BUG_ON(len != -2);
328
329 24 len = indent_len(strview("\t \t "), 0, &tab);
330 24 BUG_ON(len != -1); // whitespace only
331
332 24 len = indent_len(strview(" * 5 space"), 0, &tab);
333 24 BUG_ON(len != 4);
334
335 24 StringView line = strview(" * 4 space");
336 24 len = indent_len(line, 0, &tab);
337 24 BUG_ON(len != 4);
338 24 len = indent_len(line, 1 << 2, &tab);
339 24 BUG_ON(len != 3);
340 24 }
341
342 3 static bool detect_indent(Buffer *buffer)
343 {
344 3 LocalOptions *options = &buffer->options;
345 3 unsigned int bitset = options->detect_indent;
346 3 BlockIter bi = block_iter(buffer);
347 3 unsigned int tab_count = 0;
348 3 unsigned int space_count = 0;
349 3 int current_indent = 0;
350 3 int counts[INDENT_WIDTH_MAX + 1] = {0};
351 3 BUG_ON((bitset & ((1u << INDENT_WIDTH_MAX) - 1)) != bitset);
352
353
2/2
✓ Branch 19 → 4 taken 19 times.
✓ Branch 19 → 20 taken 3 times.
22 for (size_t i = 0, j = 1; i < 200 && j > 0; i++, j = block_iter_next_line(&bi)) {
354 19 StringView line = block_iter_get_line(&bi);
355 19 bool tab;
356 19 int indent = indent_len(line, bitset, &tab);
357
3/3
✓ Branch 6 → 7 taken 2 times.
✓ Branch 6 → 8 taken 10 times.
✓ Branch 6 → 9 taken 7 times.
19 switch (indent) {
358 2 case -2: // Ignore mixed indent because tab width might not be 8
359 case -1: // Empty line; no change in indent
360 12 continue;
361 10 case 0:
362 10 current_indent = 0;
363 10 continue;
364 }
365
366 7 BUG_ON(indent <= 0);
367 7 int change = indent - current_indent;
368
2/2
✓ Branch 11 → 12 taken 3 times.
✓ Branch 11 → 13 taken 4 times.
7 if (change >= 1 && change <= INDENT_WIDTH_MAX) {
369 3 counts[change]++;
370 }
371
372
1/2
✗ Branch 13 → 14 not taken.
✓ Branch 13 → 15 taken 7 times.
7 if (tab) {
373 tab_count++;
374 } else {
375 7 space_count++;
376 }
377 7 current_indent = indent;
378 }
379
380
1/2
✓ Branch 20 → 21 taken 3 times.
✗ Branch 20 → 30 not taken.
3 if (tab_count == 0 && space_count == 0) {
381 return false;
382 }
383
384
1/2
✗ Branch 21 → 22 not taken.
✓ Branch 21 → 27 taken 3 times.
3 if (tab_count > space_count) {
385 options->emulate_tab = false;
386 options->expand_tab = false;
387 options->indent_width = options->tab_width;
388 return true;
389 }
390
391 size_t m = 0;
392
2/2
✓ Branch 27 → 23 taken 24 times.
✓ Branch 27 → 28 taken 3 times.
27 for (size_t i = 1; i < ARRAYLEN(counts); i++) {
393 24 unsigned int bit = 1u << (i - 1);
394
4/4
✓ Branch 23 → 24 taken 9 times.
✓ Branch 23 → 26 taken 15 times.
✓ Branch 24 → 25 taken 3 times.
✓ Branch 24 → 26 taken 6 times.
24 if ((bitset & bit) && counts[i] > counts[m]) {
395 3 m = i;
396 }
397 }
398
399
1/2
✓ Branch 28 → 29 taken 3 times.
✗ Branch 28 → 30 not taken.
3 if (m == 0) {
400 return false;
401 }
402
403 3 options->emulate_tab = true;
404 3 options->expand_tab = true;
405 3 options->indent_width = m;
406 3 return true;
407 }
408
409 61 void buffer_setup(EditorState *e, Buffer *buffer)
410 {
411 61 const char *filename = buffer->abs_filename;
412 61 buffer->setup = true;
413 61 buffer_detect_filetype(buffer, &e->filetypes);
414 61 set_file_options(e, buffer);
415 61 set_editorconfig_options(buffer);
416 61 buffer_update_syntax(e, buffer);
417
3/4
✓ Branch 6 → 7 taken 3 times.
✓ Branch 6 → 9 taken 58 times.
✓ Branch 7 → 8 taken 3 times.
✗ Branch 7 → 9 not taken.
61 if (buffer->options.detect_indent && filename) {
418 3 detect_indent(buffer);
419 }
420 61 sanity_check_local_options(&buffer->options);
421 61 }
422
423 4 void buffer_count_blocks_and_bytes(const Buffer *buffer, uintmax_t counts[static 2])
424 {
425 4 uintmax_t blocks = 0;
426 4 uintmax_t bytes = 0;
427 4 const Block *blk;
428
2/2
✓ Branch 4 → 3 taken 8 times.
✓ Branch 4 → 5 taken 4 times.
12 block_for_each(blk, &buffer->blocks) {
429 8 blocks += 1;
430 8 bytes += blk->size;
431 }
432 4 counts[0] = blocks;
433 4 counts[1] = bytes;
434 4 }
435
436 21 bool buffer_filetype_is_none(const Buffer *buffer)
437 {
438 21 static const char *none;
439
2/2
✓ Branch 2 → 3 taken 1 time.
✓ Branch 2 → 5 taken 20 times.
21 if (unlikely(!none)) {
440 1 none = str_intern("none");
441 }
442
443 21 return interned_strings_equal(buffer->options.filetype, none);
444 }
445