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 |