dte test coverage


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