dte test coverage


Directory: ./
File: src/misc.c
Date: 2024-12-21 16:03:22
Exec Total Coverage
Lines: 108 185 58.4%
Functions: 7 10 70.0%
Branches: 38 100 38.0%

Line Branch Exec Source
1 #include <stdlib.h>
2 #include <string.h>
3 #include "misc.h"
4 #include "buffer.h"
5 #include "change.h"
6 #include "indent.h"
7 #include "options.h"
8 #include "regexp.h"
9 #include "selection.h"
10 #include "util/ascii.h"
11 #include "util/debug.h"
12 #include "util/macros.h"
13 #include "util/string.h"
14 #include "util/string-view.h"
15 #include "util/utf8.h"
16
17 bool line_has_opening_brace(StringView line)
18 {
19 static regex_t re;
20 static bool compiled;
21 if (!compiled) {
22 // TODO: Reimplement without using regex
23 static const char pat[] = "\\{[ \t]*(//.*|/\\*.*\\*/[ \t]*)?$";
24 regexp_compile_or_fatal_error(&re, pat, REG_NEWLINE | REG_NOSUB);
25 compiled = true;
26 }
27
28 regmatch_t m;
29 return regexp_exec(&re, line.data, line.length, 0, &m, 0);
30 }
31
32 bool line_has_closing_brace(StringView line)
33 {
34 strview_trim_left(&line);
35 return line.length > 0 && line.data[0] == '}';
36 }
37
38 /*
39 * Stupid { ... } block selector.
40 *
41 * Because braces can be inside strings or comments and writing real
42 * parser for many programming languages does not make sense the rules
43 * for selecting a block are made very simple. Line that matches \{\s*$
44 * starts a block and line that matches ^\s*\} ends it.
45 */
46 void select_block(View *view)
47 {
48 BlockIter bi = view->cursor;
49 StringView line;
50 fetch_this_line(&bi, &line);
51
52 // If current line does not match \{\s*$ but matches ^\s*\} then
53 // cursor is likely at end of the block you want to select
54 if (!line_has_opening_brace(line) && line_has_closing_brace(line)) {
55 block_iter_prev_line(&bi);
56 }
57
58 BlockIter sbi;
59 int level = 0;
60 while (1) {
61 fetch_this_line(&bi, &line);
62 if (line_has_opening_brace(line)) {
63 if (level++ == 0) {
64 sbi = bi;
65 block_iter_next_line(&bi);
66 break;
67 }
68 }
69 if (line_has_closing_brace(line)) {
70 level--;
71 }
72
73 if (!block_iter_prev_line(&bi)) {
74 return;
75 }
76 }
77
78 BlockIter ebi;
79 while (1) {
80 fetch_this_line(&bi, &line);
81 if (line_has_closing_brace(line)) {
82 if (--level == 0) {
83 ebi = bi;
84 break;
85 }
86 }
87 if (line_has_opening_brace(line)) {
88 level++;
89 }
90
91 if (!block_iter_next_line(&bi)) {
92 return;
93 }
94 }
95
96 view->cursor = sbi;
97 view->sel_so = block_iter_get_offset(&ebi);
98 view->sel_eo = SEL_EO_RECALC;
99 view->selection = SELECT_LINES;
100 mark_all_lines_changed(view->buffer);
101 }
102
103 49 void unselect(View *view)
104 {
105 49 view->select_mode = SELECT_NONE;
106
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 43 times.
49 if (view->selection) {
107 6 view->selection = SELECT_NONE;
108 6 mark_all_lines_changed(view->buffer);
109 }
110 49 }
111
112 3 void delete_ch(View *view)
113 {
114 3 size_t size = 0;
115
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (view->selection) {
116 size = prepare_selection(view);
117 unselect(view);
118 } else {
119 3 const LocalOptions *options = &view->buffer->options;
120 3 begin_change(CHANGE_MERGE_DELETE);
121
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 if (options->emulate_tab) {
122 size = get_indent_level_bytes_right(options, &view->cursor);
123 }
124 if (size == 0) {
125 3 BlockIter bi = view->cursor;
126 3 size = block_iter_next_column(&bi);
127 }
128 }
129 3 buffer_delete_bytes(view, size);
130 3 }
131
132 4 void erase(View *view)
133 {
134 4 size_t size = 0;
135
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (view->selection) {
136 size = prepare_selection(view);
137 unselect(view);
138 } else {
139 4 const LocalOptions *options = &view->buffer->options;
140 4 begin_change(CHANGE_MERGE_ERASE);
141
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (options->emulate_tab) {
142 size = get_indent_level_bytes_left(options, &view->cursor);
143 block_iter_back_bytes(&view->cursor, size);
144 }
145 if (size == 0) {
146 4 CodePoint u;
147 4 size = block_iter_prev_char(&view->cursor, &u);
148 }
149 }
150 4 buffer_erase_bytes(view, size);
151 4 }
152
153 4 static void join_selection(View *view, const char *delim, size_t delim_len)
154 {
155 4 size_t count = prepare_selection(view);
156 4 BlockIter bi = view->cursor;
157 4 size_t ws_len = 0;
158 4 size_t join = 0;
159 4 CodePoint ch = 0;
160 4 unselect(view);
161 4 begin_change_chain();
162
163
2/2
✓ Branch 0 taken 20 times.
✓ Branch 1 taken 4 times.
24 while (count > 0) {
164
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 6 times.
20 if (!ws_len) {
165 14 view->cursor = bi;
166 }
167
168 20 size_t n = block_iter_next_char(&bi, &ch);
169 20 count -= MIN(n, count);
170
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 10 times.
20 if (ch == '\n' || ch == '\t' || ch == ' ') {
171 10 join += (ch == '\n');
172 10 ws_len++;
173 10 continue;
174 }
175
176
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
10 if (join) {
177 6 buffer_replace_bytes(view, ws_len, delim, delim_len);
178 // Skip the delimiter we inserted and the char we read last
179 6 block_iter_skip_bytes(&view->cursor, delim_len);
180 6 block_iter_next_char(&view->cursor, &ch);
181 6 bi = view->cursor;
182 }
183
184 ws_len = 0;
185 join = 0;
186 }
187
188
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
4 if (join && ch == '\n') {
189 // Don't replace last newline at end of selection
190 4 join--;
191 4 ws_len--;
192 }
193
194
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (join) {
195 size_t ins_len = (ch == '\n') ? 0 : delim_len; // Don't add delim, if at eol
196 buffer_replace_bytes(view, ws_len, delim, ins_len);
197 }
198
199 4 end_change_chain(view);
200 4 }
201
202 16 void join_lines(View *view, const char *delim, size_t delim_len)
203 {
204
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 12 times.
16 if (view->selection) {
205 4 join_selection(view, delim, delim_len);
206 14 return;
207 }
208
209 // Create an iterator and position it at the beginning of the next line
210 // (or return early, if there is no next line)
211 12 BlockIter next = view->cursor;
212
3/4
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 6 times.
✗ Branch 3 not taken.
12 if (!block_iter_next_line(&next) || block_iter_is_eof(&next)) {
213 return;
214 }
215
216 // Create a second iterator and position it at the end of the current line
217 6 BlockIter eol = next;
218 6 CodePoint u;
219 6 size_t nbytes = block_iter_prev_char(&eol, &u);
220 6 BUG_ON(nbytes != 1);
221 6 BUG_ON(u != '\n');
222
223 // Skip over trailing whitespace at the end of the current line
224 6 size_t del_count = 1 + block_iter_skip_blanks_bwd(&eol);
225
226 // Skip over leading whitespace at the start of the next line
227 6 del_count += block_iter_skip_blanks_fwd(&next);
228
229 // Move the cursor to the join position
230 6 view->cursor = eol;
231
232
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
6 if (block_iter_is_bol(&next)) {
233 // If the next line is empty (or whitespace only) just discard
234 // it, by deleting the newline and any whitespace
235 3 buffer_delete_bytes(view, del_count);
236 } else {
237 // Otherwise, join the current and next lines together, by
238 // replacing the newline/whitespace with the delimiter string
239 3 buffer_replace_bytes(view, del_count, delim, delim_len);
240 }
241 }
242
243 2 void clear_lines(View *view, bool auto_indent)
244 {
245 2 char *indent = NULL;
246
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (auto_indent) {
247 2 BlockIter bi = view->cursor;
248
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
2 if (block_iter_prev_line(&bi) && block_iter_find_non_empty_line_bwd(&bi)) {
249 StringView line = block_iter_get_line(&bi);
250 indent = get_indent_for_next_line(&view->buffer->options, &line);
251 }
252 }
253
254 2 size_t del_count = 0;
255
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (view->selection) {
256 view->selection = SELECT_LINES;
257 del_count = prepare_selection(view);
258 unselect(view);
259 // Don't delete last newline
260 if (del_count) {
261 del_count--;
262 }
263 } else {
264 2 block_iter_eol(&view->cursor);
265 2 del_count = block_iter_bol(&view->cursor);
266 }
267
268
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if (!indent && !del_count) {
269 return;
270 }
271
272
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 size_t ins_count = indent ? strlen(indent) : 0;
273 2 buffer_replace_bytes(view, del_count, indent, ins_count);
274 2 free(indent);
275 2 block_iter_skip_bytes(&view->cursor, ins_count);
276 }
277
278 2 void change_case(View *view, char mode)
279 {
280 2 bool was_selecting = false;
281 2 bool move = true;
282 2 size_t text_len;
283
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (view->selection) {
284 SelectionInfo info = init_selection(view);
285 view->cursor = info.si;
286 text_len = info.eo - info.so;
287 unselect(view);
288 was_selecting = true;
289 move = !info.swapped;
290 } else {
291 2 CodePoint u;
292
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!block_iter_get_char(&view->cursor, &u)) {
293 return;
294 }
295 2 text_len = u_char_size(u);
296 }
297
298 2 String dst = string_new(text_len);
299 2 char *src = block_iter_get_bytes(&view->cursor, text_len);
300 2 size_t i = 0;
301
1/4
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 switch (mode) {
302 case 'l':
303 while (i < text_len) {
304 CodePoint u = u_to_lower(u_get_char(src, text_len, &i));
305 string_append_codepoint(&dst, u);
306 }
307 break;
308 case 'u':
309 while (i < text_len) {
310 CodePoint u = u_to_upper(u_get_char(src, text_len, &i));
311 string_append_codepoint(&dst, u);
312 }
313 break;
314 case 't':
315
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
4 while (i < text_len) {
316 2 CodePoint u = u_get_char(src, text_len, &i);
317
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 u = u_is_upper(u) ? u_to_lower(u) : u_to_upper(u);
318 2 string_append_codepoint(&dst, u);
319 }
320 break;
321 default:
322 BUG("unhandled case mode");
323 }
324
325 2 buffer_replace_bytes(view, text_len, dst.buffer, dst.len);
326 2 free(src);
327
328
2/4
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
✗ Branch 3 not taken.
2 if (move && dst.len > 0) {
329 2 size_t skip = dst.len;
330
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (was_selecting) {
331 // Move cursor back to where it was
332 u_prev_char(dst.buffer, &skip);
333 }
334 2 block_iter_skip_bytes(&view->cursor, skip);
335 }
336
337 2 string_free(&dst);
338 }
339