dte test coverage


Directory: ./
File: src/indent.c
Date: 2025-12-11 10:43:49
Coverage Exec Excl Total
Lines: 88.8% 95 0 107
Functions: 100.0% 8 0 8
Branches: 82.1% 46 0 56

Line Branch Exec Source
1 #include <string.h>
2 #include <sys/types.h>
3 #include "indent.h"
4 #include "regexp.h"
5 #include "util/xmalloc.h"
6
7 47 String make_indent(const LocalOptions *options, size_t width)
8 {
9
2/2
✓ Branch 2 → 3 taken 20 times.
✓ Branch 2 → 4 taken 27 times.
47 if (width == 0) {
10 20 return string_new(0);
11 }
12
13 27 bool use_spaces = use_spaces_for_indent(options);
14 27 size_t tw = options->tab_width;
15
2/2
✓ Branch 4 → 5 taken 4 times.
✓ Branch 4 → 7 taken 23 times.
27 size_t ntabs = use_spaces ? 0 : indent_level(width, tw);
16 4 size_t nspaces = use_spaces ? width : indent_remainder(width, tw);
17 27 size_t nbytes = ntabs + nspaces;
18 27 BUG_ON(nbytes == 0);
19
20 27 String str = string_new(nbytes + 1); // +1 for efficiency in several callers
21 27 memset(str.buffer, '\t', ntabs);
22 27 memset(str.buffer + ntabs, ' ', nspaces);
23 27 str.len = nbytes;
24 27 return str;
25 }
26
27 // Return true if the contents of `line` triggers an additional level
28 // of auto-indent on the next line
29 37 static bool line_contents_increases_indent (
30 const LocalOptions *options,
31 StringView line
32 ) {
33
2/2
✓ Branch 2 → 3 taken 36 times.
✓ Branch 2 → 16 taken 1 time.
37 if (!line.length) {
34 return false;
35 }
36
37 36 static const regex_t *re1, *re2;
38
2/2
✓ Branch 3 → 4 taken 1 time.
✓ Branch 3 → 7 taken 35 times.
36 if (!re1) {
39 // TODO: Make these patterns configurable via a local option
40 1 re1 = regexp_compile_or_fatal_error("\\{[\t ]*(//.*|/\\*.*\\*/[\t ]*)?$");
41 1 re2 = regexp_compile_or_fatal_error("\\}[\t ]*(//.*|/\\*.*\\*/[\t ]*)?$");
42 }
43
44
2/2
✓ Branch 7 → 8 taken 13 times.
✓ Branch 7 → 12 taken 23 times.
36 if (options->brace_indent) {
45
2/2
✓ Branch 9 → 10 taken 12 times.
✓ Branch 9 → 16 taken 1 time.
13 if (regexp_exec(re1, line.data, line.length, 0, NULL, 0)) {
46 return true;
47 }
48
1/2
✓ Branch 11 → 12 taken 12 times.
✗ Branch 11 → 16 not taken.
12 if (regexp_exec(re2, line.data, line.length, 0, NULL, 0)) {
49 return false;
50 }
51 }
52
53 35 const InternedRegexp *ir = options->indent_regex;
54
2/2
✓ Branch 12 → 13 taken 2 times.
✓ Branch 12 → 16 taken 33 times.
35 if (!ir) {
55 return false;
56 }
57
58 2 BUG_ON(ir->str[0] == '\0');
59 2 return regexp_exec(&ir->re, line.data, line.length, 0, NULL, 0);
60 }
61
62 37 String get_indent_for_next_line(const LocalOptions *options, StringView line)
63 {
64 37 size_t curr_width = get_indent_width(line, options->tab_width);
65 37 size_t next_width = next_indent_width(curr_width, options->indent_width);
66 37 bool increase = line_contents_increases_indent(options, line);
67
2/2
✓ Branch 5 → 6 taken 35 times.
✓ Branch 5 → 7 taken 2 times.
72 return make_indent(options, increase ? next_width : curr_width);
68 }
69
70 64 IndentInfo get_indent_info(const LocalOptions *options, StringView line)
71 {
72 64 const char *buf = line.data;
73 64 const size_t len = line.length;
74 64 const size_t tw = options->tab_width;
75 64 const size_t iw = options->indent_width;
76 64 const bool space_indent = use_spaces_for_indent(options);
77 64 IndentInfo info = {.sane = true};
78 64 size_t spaces = 0;
79 64 size_t tabs = 0;
80 64 size_t pos = 0;
81
82
2/2
✓ Branch 16 → 3 taken 631 times.
✓ Branch 16 → 17 taken 5 times.
636 for (; pos < len; pos++) {
83
2/2
✓ Branch 3 → 4 taken 564 times.
✓ Branch 3 → 5 taken 67 times.
631 if (buf[pos] == ' ') {
84 564 info.width++;
85 564 spaces++;
86
2/2
✓ Branch 5 → 6 taken 8 times.
✓ Branch 5 → 17 taken 59 times.
67 } else if (buf[pos] == '\t') {
87 8 info.width = next_indent_width(info.width, tw);
88 8 tabs++;
89 } else {
90 break;
91 }
92
4/4
✓ Branch 9 → 10 taken 146 times.
✓ Branch 9 → 15 taken 426 times.
✓ Branch 10 → 11 taken 145 times.
✓ Branch 10 → 15 taken 1 time.
572 if (indent_remainder(info.width, iw) == 0 && info.sane) {
93
2/2
✓ Branch 11 → 12 taken 137 times.
✓ Branch 11 → 13 taken 8 times.
145 info.sane = space_indent ? !tabs : !spaces;
94 }
95 }
96
97 64 info.level = indent_level(info.width, iw);
98 64 info.wsonly = (pos == len);
99 64 info.bytes = spaces + tabs;
100 64 return info;
101 }
102
103 45 size_t get_indent_width(StringView line, unsigned int tab_width)
104 {
105 45 const char *buf = line.data;
106 45 size_t width = 0;
107
2/2
✓ Branch 8 → 3 taken 129 times.
✓ Branch 8 → 9 taken 4 times.
133 for (size_t i = 0, n = line.length; i < n; i++) {
108
2/2
✓ Branch 3 → 4 taken 88 times.
✓ Branch 3 → 5 taken 41 times.
129 if (buf[i] == ' ') {
109 88 width++;
110
1/2
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 9 taken 41 times.
41 } else if (buf[i] == '\t') {
111 width = next_indent_width(width, tab_width);
112 } else {
113 break;
114 }
115 }
116 45 return width;
117 }
118
119 22 static ssize_t get_current_indent_bytes (
120 const char *buf,
121 size_t cursor_offset,
122 unsigned int iw,
123 unsigned int tw
124 ) {
125 22 size_t bytes = 0;
126 22 size_t width = 0;
127
128
2/2
✓ Branch 11 → 3 taken 20 times.
✓ Branch 11 → 12 taken 2 times.
22 for (size_t i = 0; i < cursor_offset; i++) {
129
1/2
✓ Branch 4 → 5 taken 20 times.
✗ Branch 4 → 6 not taken.
20 if (indent_remainder(width, iw) == 0) {
130 20 bytes = 0;
131 20 width = 0;
132 }
133
1/3
✗ Branch 6 → 7 not taken.
✗ Branch 6 → 9 not taken.
✓ Branch 6 → 15 taken 20 times.
20 switch (buf[i]) {
134 case '\t':
135 width = next_indent_width(width, tw);
136 break;
137 case ' ':
138 width++;
139 break;
140 default:
141 // Cursor not at indentation
142 return -1;
143 }
144 bytes++;
145 }
146
147
1/2
✓ Branch 13 → 14 taken 2 times.
✗ Branch 13 → 15 not taken.
2 if (indent_remainder(width, iw)) {
148 // Cursor at middle of indentation level
149 return -1;
150 }
151
152 2 return (ssize_t)bytes;
153 }
154
155 3 size_t get_indent_level_bytes_left(const LocalOptions *options, const BlockIter *cursor)
156 {
157 3 BlockIter bol = *cursor;
158 3 size_t cursor_offset = block_iter_bol(&bol);
159
1/2
✓ Branch 3 → 4 taken 3 times.
✗ Branch 3 → 7 not taken.
3 if (cursor_offset == 0) {
160 return 0; // cursor at BOL
161 }
162
163 3 StringView line = block_iter_get_line(&bol);
164 3 unsigned int iw = options->indent_width;
165 3 unsigned int tw = options->tab_width;
166 3 ssize_t ibytes = get_current_indent_bytes(line.data, cursor_offset, iw, tw);
167 3 return MAX(ibytes, 0);
168 }
169
170 19 size_t get_indent_level_bytes_right(const LocalOptions *options, const BlockIter *cursor)
171 {
172 19 unsigned int iw = options->indent_width;
173 19 unsigned int tw = options->tab_width;
174 19 CurrentLineRef lr = get_current_line_and_offset(*cursor);
175 19 ssize_t ibytes = get_current_indent_bytes(lr.line.data, lr.cursor_offset, iw, tw);
176
2/2
✓ Branch 4 → 5 taken 2 times.
✓ Branch 4 → 15 taken 17 times.
19 if (ibytes < 0) {
177 return 0;
178 }
179
180 2 size_t width = 0;
181
1/2
✓ Branch 14 → 6 taken 3 times.
✗ Branch 14 → 15 not taken.
3 for (size_t i = lr.cursor_offset, n = lr.line.length; i < n; i++) {
182
2/3
✗ Branch 6 → 7 not taken.
✓ Branch 6 → 9 taken 1 time.
✓ Branch 6 → 15 taken 2 times.
3 switch (lr.line.data[i]) {
183 case '\t':
184 width = next_indent_width(width, tw);
185 break;
186 1 case ' ':
187 1 width++;
188 1 break;
189 default:
190 // No full indentation level at cursor position
191 return 0;
192 }
193
1/2
✗ Branch 11 → 12 not taken.
✓ Branch 11 → 13 taken 1 time.
1 if (indent_remainder(width, iw) == 0) {
194 return i - lr.cursor_offset + 1;
195 }
196 }
197
198 return 0;
199 }
200