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 |