| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "move.h" | ||
| 2 | #include "buffer.h" | ||
| 3 | #include "indent.h" | ||
| 4 | #include "util/ascii.h" | ||
| 5 | #include "util/debug.h" | ||
| 6 | #include "util/utf8.h" | ||
| 7 | |||
| 8 | typedef enum { | ||
| 9 | CT_SPACE, | ||
| 10 | CT_NEWLINE, | ||
| 11 | CT_WORD, | ||
| 12 | CT_OTHER, | ||
| 13 | } CharTypeEnum; | ||
| 14 | |||
| 15 | 59 | void move_to_preferred_x(View *view, long preferred_x) | |
| 16 | { | ||
| 17 | 59 | const LocalOptions *options = &view->buffer->options; | |
| 18 | 59 | view->preferred_x = preferred_x; | |
| 19 | 59 | block_iter_bol(&view->cursor); | |
| 20 | 59 | StringView line = block_iter_get_line(&view->cursor); | |
| 21 | |||
| 22 |
4/4✓ Branch 0 (4→5) taken 24 times.
✓ Branch 1 (4→12) taken 35 times.
✓ Branch 2 (5→6) taken 9 times.
✓ Branch 3 (5→12) taken 15 times.
|
59 | if (options->emulate_tab && preferred_x < line.length) { |
| 23 | 9 | const size_t iw = options->indent_width; | |
| 24 | 9 | const size_t ilevel = indent_level(preferred_x, iw); | |
| 25 |
3/4✓ Branch 0 (10→11) taken 77 times.
✗ Branch 1 (10→12) not taken.
✓ Branch 2 (11→7) taken 68 times.
✓ Branch 3 (11→12) taken 9 times.
|
77 | for (size_t i = 0; i < line.length && line.data[i] == ' '; i++) { |
| 26 |
1/2✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→10) taken 68 times.
|
68 | if (i + 1 == (ilevel + 1) * iw) { |
| 27 | // Force cursor to beginning of the indentation level | ||
| 28 | ✗ | view->cursor.offset += ilevel * iw; | |
| 29 | ✗ | return; | |
| 30 | } | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | 59 | const unsigned int tw = options->tab_width; | |
| 35 | 59 | unsigned long x = 0; | |
| 36 | 59 | size_t i = 0; | |
| 37 | |||
| 38 |
4/4✓ Branch 0 (24→25) taken 283 times.
✓ Branch 1 (24→26) taken 40 times.
✓ Branch 2 (25→13) taken 264 times.
✓ Branch 3 (25→26) taken 19 times.
|
323 | while (x < preferred_x && i < line.length) { |
| 39 | 264 | unsigned char ch = line.data[i]; | |
| 40 |
1/2✓ Branch 0 (13→14) taken 264 times.
✗ Branch 1 (13→20) not taken.
|
264 | if (likely(ch < 0x80)) { |
| 41 | 264 | i++; | |
| 42 |
2/2✓ Branch 0 (14→15) taken 262 times.
✓ Branch 1 (14→16) taken 2 times.
|
264 | if (likely(!ascii_iscntrl(ch))) { |
| 43 | 262 | x++; | |
| 44 |
1/2✓ Branch 0 (16→17) taken 2 times.
✗ Branch 1 (16→18) not taken.
|
2 | } else if (ch == '\t') { |
| 45 | 2 | x = next_indent_width(x, tw); | |
| 46 | ✗ | } else if (ch == '\n') { | |
| 47 | break; | ||
| 48 | } else { | ||
| 49 | ✗ | x += 2; | |
| 50 | } | ||
| 51 | } else { | ||
| 52 | ✗ | size_t next = i + 1; | |
| 53 | ✗ | CodePoint u = u_get_nonascii(line.data, line.length, &i); | |
| 54 | ✗ | x += u_char_width(u); | |
| 55 | ✗ | if (x > preferred_x) { | |
| 56 | ✗ | i = next; | |
| 57 | ✗ | break; | |
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | 59 | view->cursor.offset += i - (x > preferred_x); | |
| 63 | |||
| 64 | // If cursor stopped on a zero-width char, move to the next spacing char | ||
| 65 | 59 | CodePoint u; | |
| 66 |
3/4✓ Branch 0 (27→28) taken 55 times.
✓ Branch 1 (27→31) taken 4 times.
✗ Branch 2 (29→30) not taken.
✓ Branch 3 (29→31) taken 55 times.
|
59 | if (block_iter_get_char(&view->cursor, &u) && u_is_zero_width(u)) { |
| 67 | ✗ | block_iter_next_column(&view->cursor); | |
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | 16 | void move_cursor_left(View *view) | |
| 72 | { | ||
| 73 | 16 | const LocalOptions *options = &view->buffer->options; | |
| 74 |
2/2✓ Branch 0 (2→3) taken 2 times.
✓ Branch 1 (2→8) taken 14 times.
|
16 | if (options->emulate_tab) { |
| 75 | 2 | size_t size = get_indent_level_bytes_left(options, &view->cursor); | |
| 76 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→8) taken 2 times.
|
2 | if (size) { |
| 77 | ✗ | block_iter_back_bytes(&view->cursor, size); | |
| 78 | ✗ | view_reset_preferred_x(view); | |
| 79 | ✗ | return; | |
| 80 | } | ||
| 81 | } | ||
| 82 | 16 | block_iter_prev_column(&view->cursor); | |
| 83 | 16 | view_reset_preferred_x(view); | |
| 84 | } | ||
| 85 | |||
| 86 | 31 | void move_cursor_right(View *view) | |
| 87 | { | ||
| 88 | 31 | const LocalOptions *options = &view->buffer->options; | |
| 89 |
2/2✓ Branch 0 (2→3) taken 15 times.
✓ Branch 1 (2→8) taken 16 times.
|
31 | if (options->emulate_tab) { |
| 90 | 15 | size_t size = get_indent_level_bytes_right(options, &view->cursor); | |
| 91 |
1/2✗ Branch 0 (4→5) not taken.
✓ Branch 1 (4→8) taken 15 times.
|
15 | if (size) { |
| 92 | ✗ | block_iter_skip_bytes(&view->cursor, size); | |
| 93 | ✗ | view_reset_preferred_x(view); | |
| 94 | ✗ | return; | |
| 95 | } | ||
| 96 | } | ||
| 97 | 31 | block_iter_next_column(&view->cursor); | |
| 98 | 31 | view_reset_preferred_x(view); | |
| 99 | } | ||
| 100 | |||
| 101 | 7 | void move_bol(View *view, SmartBolType type) | |
| 102 | { | ||
| 103 | 7 | BlockIter bol = view->cursor; | |
| 104 | 7 | size_t cursor_offset = block_iter_bol(&bol); | |
| 105 |
1/2✓ Branch 0 (3→4) taken 7 times.
✗ Branch 1 (3→5) not taken.
|
7 | if (type == BOL_SIMPLE) { |
| 106 | 7 | view->cursor = bol; | |
| 107 | 7 | goto out; | |
| 108 | } | ||
| 109 | |||
| 110 | ✗ | bool at_bol = (cursor_offset == 0); | |
| 111 | ✗ | if (at_bol && type == BOL_INDENT) { | |
| 112 | // At BOL and not using toggle; nothing to do | ||
| 113 | ✗ | goto out; | |
| 114 | } | ||
| 115 | |||
| 116 | ✗ | StringView line = block_iter_get_line(&bol); | |
| 117 | ✗ | size_t indent = ascii_blank_prefix_length(line.data, line.length); | |
| 118 | ✗ | if (at_bol) { | |
| 119 | // At BOL and using toggle; move right to first non-blank char | ||
| 120 | ✗ | block_iter_skip_bytes(&view->cursor, indent); | |
| 121 | ✗ | goto out; | |
| 122 | } | ||
| 123 | |||
| 124 | // Not at BOL; move left (either to BOL or leftmost non-blank) depending on | ||
| 125 | // `type` and whether the cursor is before or after the leftmost non-blank | ||
| 126 | ✗ | size_t co = cursor_offset; | |
| 127 | ✗ | size_t move = (co > indent && type != BOL_TOGGLE_LR) ? co - indent : co; | |
| 128 | ✗ | block_iter_back_bytes(&view->cursor, move); | |
| 129 | |||
| 130 | 7 | out: | |
| 131 | 7 | view_reset_preferred_x(view); | |
| 132 | 7 | } | |
| 133 | |||
| 134 | 6 | void move_eol(View *view) | |
| 135 | { | ||
| 136 | 6 | block_iter_eol(&view->cursor); | |
| 137 | 6 | view_reset_preferred_x(view); | |
| 138 | 6 | } | |
| 139 | |||
| 140 | 19 | void move_up(View *view, long count) | |
| 141 | { | ||
| 142 | 19 | const long x = view_get_preferred_x(view); | |
| 143 |
2/2✓ Branch 0 (7→4) taken 20 times.
✓ Branch 1 (7→8) taken 16 times.
|
36 | while (count > 0) { |
| 144 |
2/2✓ Branch 0 (5→6) taken 17 times.
✓ Branch 1 (5→8) taken 3 times.
|
20 | if (!block_iter_prev_line(&view->cursor)) { |
| 145 | break; | ||
| 146 | } | ||
| 147 | 17 | count--; | |
| 148 | } | ||
| 149 | 19 | move_to_preferred_x(view, x); | |
| 150 | 19 | } | |
| 151 | |||
| 152 | 15 | void move_down(View *view, long count) | |
| 153 | { | ||
| 154 | 15 | const long x = view_get_preferred_x(view); | |
| 155 |
2/2✓ Branch 0 (7→4) taken 12 times.
✓ Branch 1 (7→8) taken 15 times.
|
27 | while (count > 0) { |
| 156 |
1/2✓ Branch 0 (5→6) taken 12 times.
✗ Branch 1 (5→8) not taken.
|
12 | if (!block_iter_eat_line(&view->cursor)) { |
| 157 | break; | ||
| 158 | } | ||
| 159 | 12 | count--; | |
| 160 | } | ||
| 161 | 15 | move_to_preferred_x(view, x); | |
| 162 | 15 | } | |
| 163 | |||
| 164 | 7 | void move_bof(View *view) | |
| 165 | { | ||
| 166 | 7 | block_iter_bof(&view->cursor); | |
| 167 | 7 | view_reset_preferred_x(view); | |
| 168 | 7 | } | |
| 169 | |||
| 170 | 6 | void move_eof(View *view) | |
| 171 | { | ||
| 172 | 6 | block_iter_eof(&view->cursor); | |
| 173 | 6 | view_reset_preferred_x(view); | |
| 174 | 6 | } | |
| 175 | |||
| 176 | 3 | void move_to_line(View *view, size_t line) | |
| 177 | { | ||
| 178 | 3 | BUG_ON(line == 0); | |
| 179 | 3 | view->center_on_scroll = true; | |
| 180 | 3 | block_iter_goto_line(&view->cursor, line - 1); | |
| 181 | 3 | } | |
| 182 | |||
| 183 | ✗ | void move_to_column(View *view, size_t column) | |
| 184 | { | ||
| 185 | ✗ | BUG_ON(column == 0); | |
| 186 | ✗ | block_iter_bol(&view->cursor); | |
| 187 | ✗ | while (column-- > 1) { | |
| 188 | ✗ | CodePoint u; | |
| 189 | ✗ | if (!block_iter_next_char(&view->cursor, &u)) { | |
| 190 | break; | ||
| 191 | } | ||
| 192 | ✗ | if (u == '\n') { | |
| 193 | ✗ | block_iter_prev_char(&view->cursor, &u); | |
| 194 | ✗ | break; | |
| 195 | } | ||
| 196 | } | ||
| 197 | ✗ | view_reset_preferred_x(view); | |
| 198 | ✗ | } | |
| 199 | |||
| 200 | 2 | void move_to_filepos(View *view, size_t line, size_t column) | |
| 201 | { | ||
| 202 | 2 | move_to_line(view, line); | |
| 203 | 2 | BUG_ON(!block_iter_is_bol(&view->cursor)); | |
| 204 |
1/2✗ Branch 0 (5→6) not taken.
✓ Branch 1 (5→7) taken 2 times.
|
2 | if (column != 1) { |
| 205 | ✗ | move_to_column(view, column); | |
| 206 | } | ||
| 207 | 2 | view_reset_preferred_x(view); | |
| 208 | 2 | } | |
| 209 | |||
| 210 | 325 | static CharTypeEnum get_char_type(CodePoint u) | |
| 211 | { | ||
| 212 | 325 | return (u == '\n') ? CT_NEWLINE | |
| 213 |
4/4✓ Branch 0 (2→3) taken 293 times.
✓ Branch 1 (2→6) taken 32 times.
✓ Branch 2 (3→4) taken 215 times.
✓ Branch 3 (3→6) taken 78 times.
|
325 | : u_is_breakable_whitespace(u) ? CT_SPACE |
| 214 |
2/2✓ Branch 0 (4→5) taken 48 times.
✓ Branch 1 (4→6) taken 167 times.
|
215 | : u_is_word_char(u) ? CT_WORD |
| 215 | : CT_OTHER; | ||
| 216 | } | ||
| 217 | |||
| 218 | 24 | UNITTEST { | |
| 219 | // NOLINTBEGIN(bugprone-assert-side-effect) | ||
| 220 | 24 | BUG_ON(get_char_type('\n') != CT_NEWLINE); | |
| 221 | 24 | BUG_ON(get_char_type('\t') != CT_SPACE); | |
| 222 | 24 | BUG_ON(get_char_type(' ') != CT_SPACE); | |
| 223 | 24 | BUG_ON(get_char_type(0x3000) != CT_SPACE); // Ideographic space | |
| 224 | 24 | BUG_ON(get_char_type('A') != CT_WORD); | |
| 225 | 24 | BUG_ON(get_char_type('z') != CT_WORD); | |
| 226 | 24 | BUG_ON(get_char_type('9') != CT_WORD); | |
| 227 | 24 | BUG_ON(get_char_type('_') != CT_WORD); | |
| 228 | 24 | BUG_ON(get_char_type(0xE1) != CT_WORD); // รก | |
| 229 | 24 | BUG_ON(get_char_type(',') != CT_OTHER); | |
| 230 | 24 | BUG_ON(get_char_type('~') != CT_OTHER); | |
| 231 | // NOLINTEND(bugprone-assert-side-effect) | ||
| 232 | 24 | } | |
| 233 | |||
| 234 | 15 | static size_t skip_fwd_char_type(BlockIter *bi, CharTypeEnum type) | |
| 235 | { | ||
| 236 | 15 | size_t count = 0; | |
| 237 | 15 | CodePoint u; | |
| 238 |
1/2✓ Branch 0 (8→3) taken 45 times.
✗ Branch 1 (8→9) not taken.
|
45 | while (block_iter_next_char(bi, &u)) { |
| 239 |
2/2✓ Branch 0 (3→4) taken 15 times.
✓ Branch 1 (3→6) taken 30 times.
|
45 | if (get_char_type(u) != type) { |
| 240 | 15 | block_iter_prev_char(bi, &u); | |
| 241 | 15 | break; | |
| 242 | } | ||
| 243 | 30 | count += u_char_size(u); | |
| 244 | } | ||
| 245 | 15 | return count; | |
| 246 | } | ||
| 247 | |||
| 248 | 5 | static size_t skip_bwd_char_type(BlockIter *bi, CharTypeEnum type) | |
| 249 | { | ||
| 250 | 5 | size_t count = 0; | |
| 251 | 5 | CodePoint u; | |
| 252 |
2/2✓ Branch 0 (8→3) taken 4 times.
✓ Branch 1 (8→9) taken 2 times.
|
6 | while (block_iter_prev_char(bi, &u)) { |
| 253 |
2/2✓ Branch 0 (3→4) taken 3 times.
✓ Branch 1 (3→6) taken 1 times.
|
4 | if (get_char_type(u) != type) { |
| 254 | 3 | block_iter_next_char(bi, &u); | |
| 255 | 3 | break; | |
| 256 | } | ||
| 257 | 1 | count += u_char_size(u); | |
| 258 | } | ||
| 259 | 5 | return count; | |
| 260 | } | ||
| 261 | |||
| 262 | 5 | size_t word_fwd(BlockIter *bi, bool skip_non_word) | |
| 263 | { | ||
| 264 | 5 | for (size_t count = 0; true; ) { | |
| 265 | 10 | count += skip_fwd_char_type(bi, CT_SPACE); | |
| 266 | |||
| 267 | 10 | CodePoint u; | |
| 268 |
1/2✓ Branch 0 (5→6) taken 10 times.
✗ Branch 1 (5→11) not taken.
|
10 | if (!block_iter_get_char(bi, &u)) { |
| 269 | 5 | return count; | |
| 270 | } | ||
| 271 | |||
| 272 | 10 | CharTypeEnum type = get_char_type(u); | |
| 273 |
2/2✓ Branch 0 (6→7) taken 5 times.
✓ Branch 1 (6→9) taken 5 times.
|
10 | if ( |
| 274 | count | ||
| 275 |
3/4✓ Branch 0 (7→8) taken 3 times.
✓ Branch 1 (7→11) taken 2 times.
✗ Branch 2 (8→9) not taken.
✓ Branch 3 (8→11) taken 3 times.
|
5 | && (!skip_non_word || (type == CT_WORD || type == CT_NEWLINE)) |
| 276 | ) { | ||
| 277 | return count; | ||
| 278 | } | ||
| 279 | |||
| 280 | 5 | count += skip_fwd_char_type(bi, type); | |
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | 3 | size_t word_bwd(BlockIter *bi, bool skip_non_word) | |
| 285 | { | ||
| 286 | 3 | size_t count = 0; | |
| 287 | 3 | CharTypeEnum type; | |
| 288 | 3 | CodePoint u; | |
| 289 | |||
| 290 | 3 | do { | |
| 291 | 3 | count += skip_bwd_char_type(bi, CT_SPACE); | |
| 292 |
2/2✓ Branch 0 (5→6) taken 2 times.
✓ Branch 1 (5→9) taken 1 times.
|
3 | if (!block_iter_prev_char(bi, &u)) { |
| 293 | return count; | ||
| 294 | } | ||
| 295 | |||
| 296 | 2 | type = get_char_type(u); | |
| 297 | 2 | count += u_char_size(u); | |
| 298 | 2 | count += skip_bwd_char_type(bi, type); | |
| 299 |
1/4✗ Branch 0 (7→8) not taken.
✓ Branch 1 (7→9) taken 2 times.
✗ Branch 2 (8→3) not taken.
✗ Branch 3 (8→9) not taken.
|
2 | } while (skip_non_word && type != CT_WORD && type != CT_NEWLINE); |
| 300 | return count; | ||
| 301 | } | ||
| 302 |