dte test coverage


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