dte test coverage


Directory: ./
File: src/buffer.c
Date: 2025-02-14 16:55:22
Exec Total Coverage
Lines: 225 234 96.2%
Functions: 23 23 100.0%
Branches: 89 106 84.0%

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