src/view.c
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include "view.h" | ||
| 2 | #include "block.h" | ||
| 3 | #include "buffer.h" | ||
| 4 | #include "editor.h" | ||
| 5 | #include "indent.h" | ||
| 6 | #include "util/ascii.h" | ||
| 7 | #include "util/debug.h" | ||
| 8 | #include "util/numtostr.h" | ||
| 9 | #include "util/str-util.h" | ||
| 10 | #include "util/time-util.h" | ||
| 11 | #include "util/utf8.h" | ||
| 12 | #include "window.h" | ||
| 13 | |||
| 14 | 484 | void view_update_cursor_y(View *view) | |
| 15 | { | ||
| 16 | 484 | const Buffer *buffer = view->buffer; | |
| 17 | 484 | const Block *blk; | |
| 18 | 484 | size_t nl = 0; | |
| 19 |
1/2✓ Branch 7 → 3 taken 484 times.
✗ Branch 7 → 8 not taken.
|
484 | block_for_each(blk, &buffer->blocks) { |
| 20 |
1/2✓ Branch 3 → 4 taken 484 times.
✗ Branch 3 → 6 not taken.
|
484 | if (blk == view->cursor.blk) { |
| 21 | 484 | nl += count_nl(blk->data, view->cursor.offset); | |
| 22 | 484 | view->cy = nl; | |
| 23 | 484 | return; | |
| 24 | } | ||
| 25 | ✗ | nl += blk->nl; | |
| 26 | } | ||
| 27 | − | BUG("unreachable"); | |
| 28 | } | ||
| 29 | |||
| 30 | 32 | static void view_update_cursor_x(View *view) | |
| 31 | { | ||
| 32 | 32 | const unsigned int tw = view->buffer->options.tab_width; | |
| 33 | 32 | const CurrentLineRef lr = get_current_line_and_offset(view->cursor); | |
| 34 | 32 | const size_t cx = lr.cursor_offset; | |
| 35 | 32 | long cx_char = 0; | |
| 36 | 32 | long w = 0; | |
| 37 | |||
| 38 |
2/2✓ Branch 14 → 4 taken 83 times.
✓ Branch 14 → 15 taken 32 times.
|
115 | for (size_t idx = 0; idx < cx; cx_char++) { |
| 39 | 83 | unsigned char ch = lr.line.data[idx]; | |
| 40 |
1/2✓ Branch 4 → 5 taken 83 times.
✗ Branch 4 → 11 not taken.
|
83 | if (likely(ch < 0x80)) { |
| 41 | 83 | idx++; | |
| 42 |
2/2✓ Branch 5 → 6 taken 79 times.
✓ Branch 5 → 7 taken 4 times.
|
83 | if (likely(!ascii_iscntrl(ch))) { |
| 43 | 79 | w++; | |
| 44 |
1/2✓ Branch 7 → 8 taken 4 times.
✗ Branch 7 → 10 not taken.
|
4 | } else if (ch == '\t') { |
| 45 | 4 | w = next_indent_width(w, tw); | |
| 46 | } else { | ||
| 47 | ✗ | w += 2; | |
| 48 | } | ||
| 49 | } else { | ||
| 50 | ✗ | CodePoint u = u_get_nonascii(lr.line.data, lr.line.length, &idx); | |
| 51 | ✗ | w += u_char_width(u); | |
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | 32 | view->cx = cx; | |
| 56 | 32 | view->cx_char = cx_char; | |
| 57 | 32 | view->cx_display = w; | |
| 58 | 32 | } | |
| 59 | |||
| 60 | 1 | static bool view_is_cursor_visible(const View *v) | |
| 61 | { | ||
| 62 |
2/4✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 5 not taken.
✗ Branch 3 → 4 not taken.
✓ Branch 3 → 5 taken 1 time.
|
1 | return v->cy < v->vy || v->cy > v->vy + v->window->edit_h - 1; |
| 63 | } | ||
| 64 | |||
| 65 | 1 | static void view_center_to_cursor(View *view) | |
| 66 | { | ||
| 67 | 1 | unsigned long height = view->window->edit_h; | |
| 68 | 1 | unsigned long half_height = height / 2; | |
| 69 | 1 | unsigned long lines = view->buffer->nl; | |
| 70 | 1 | unsigned long cursor_y = view->cy; | |
| 71 | |||
| 72 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
|
1 | if (height >= lines || half_height >= cursor_y) { |
| 73 | ✗ | view->vy = 0; | |
| 74 | ✗ | return; | |
| 75 | } | ||
| 76 | |||
| 77 | // +1 here makes one "~" line visible, so that the EOF position can | ||
| 78 | // still be seen even when center_at_eof is false | ||
| 79 | 1 | bool center_at_eof = view->window->editor->options.center_at_eof; | |
| 80 | 1 | unsigned long new_y = cursor_y - half_height; | |
| 81 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
|
1 | view->vy = center_at_eof ? new_y : MIN(new_y, (lines - height) + 1); |
| 82 | } | ||
| 83 | |||
| 84 | 1 | static void view_update_vx(View *v) | |
| 85 | { | ||
| 86 | 1 | Window *window = v->window; | |
| 87 | 1 | unsigned int c = 8; | |
| 88 | |||
| 89 |
1/2✗ Branch 2 → 3 not taken.
✓ Branch 2 → 4 taken 1 time.
|
1 | if (v->cx_display - v->vx >= window->edit_w) { |
| 90 | ✗ | v->vx = (v->cx_display - window->edit_w + c) / c * c; | |
| 91 | } | ||
| 92 |
1/2✗ Branch 4 → 5 not taken.
✓ Branch 4 → 6 taken 1 time.
|
1 | if (v->cx_display < v->vx) { |
| 93 | ✗ | v->vx = v->cx_display / c * c; | |
| 94 | } | ||
| 95 | 1 | } | |
| 96 | |||
| 97 | ✗ | static void view_update_vy(View *v) | |
| 98 | { | ||
| 99 | ✗ | Window *window = v->window; | |
| 100 | ✗ | long margin = window_get_scroll_margin(window); | |
| 101 | ✗ | long max_y = v->vy + window->edit_h - 1 - margin; | |
| 102 | |||
| 103 | ✗ | if (v->cy < v->vy + margin) { | |
| 104 | ✗ | v->vy = MAX(v->cy - margin, 0); | |
| 105 | ✗ | } else if (v->cy > max_y) { | |
| 106 | ✗ | v->vy += v->cy - max_y; | |
| 107 | ✗ | max_y = v->buffer->nl - window->edit_h + 1; | |
| 108 | ✗ | if (v->vy > max_y && max_y >= 0) { | |
| 109 | ✗ | v->vy = max_y; | |
| 110 | } | ||
| 111 | } | ||
| 112 | ✗ | } | |
| 113 | |||
| 114 | 1 | void view_update(View *v) | |
| 115 | { | ||
| 116 | 1 | view_update_cursor_x(v); | |
| 117 | 1 | view_update_cursor_y(v); | |
| 118 | 1 | view_update_vx(v); | |
| 119 |
3/6✓ Branch 5 → 6 taken 1 time.
✗ Branch 5 → 8 not taken.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 9 not taken.
✓ Branch 7 → 8 taken 1 time.
✗ Branch 7 → 9 not taken.
|
1 | if (v->force_center || (v->center_on_scroll && view_is_cursor_visible(v))) { |
| 120 | 1 | view_center_to_cursor(v); | |
| 121 | } else { | ||
| 122 | ✗ | view_update_vy(v); | |
| 123 | } | ||
| 124 | 1 | v->force_center = false; | |
| 125 | 1 | v->center_on_scroll = false; | |
| 126 | 1 | } | |
| 127 | |||
| 128 | 63 | long view_get_preferred_x(View *v) | |
| 129 | { | ||
| 130 |
2/2✓ Branch 2 → 3 taken 31 times.
✓ Branch 2 → 5 taken 32 times.
|
63 | if (v->preferred_x < 0) { |
| 131 | 31 | view_update_cursor_x(v); | |
| 132 | 31 | v->preferred_x = v->cx_display; | |
| 133 | } | ||
| 134 | 63 | return v->preferred_x; | |
| 135 | } | ||
| 136 | |||
| 137 | 25 | bool view_can_close(const View *view) | |
| 138 | { | ||
| 139 | 25 | const Buffer *buffer = view->buffer; | |
| 140 |
4/4✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 5 taken 20 times.
✓ Branch 3 → 4 taken 4 times.
✓ Branch 3 → 5 taken 1 time.
|
25 | return !buffer_modified(buffer) || buffer->views.count > 1; |
| 141 | } | ||
| 142 | |||
| 143 | // Like window_remove_view(), but searching for the index of `view` in | ||
| 144 | // `view->window->views` by pointer | ||
| 145 | 28 | size_t view_remove(View *view) | |
| 146 | { | ||
| 147 | 28 | Window *window = view->window; | |
| 148 | 28 | size_t view_idx = ptr_array_xindex(&window->views, view); | |
| 149 | 28 | window_remove_view_at_index(window, view_idx); | |
| 150 | 28 | return view_idx; | |
| 151 | } | ||
| 152 | |||
| 153 | 4 | WordBounds get_bounds_for_word_under_cursor(CurrentLineRef lr) | |
| 154 | { | ||
| 155 | 4 | const size_t len = lr.line.length; | |
| 156 | 4 | size_t si = lr.cursor_offset; | |
| 157 | 4 | BUG_ON(si > len); | |
| 158 | |||
| 159 | // Move right, until over a word char (if not already) | ||
| 160 |
1/2✓ Branch 8 → 4 taken 4 times.
✗ Branch 8 → 9 not taken.
|
4 | while (si < len) { |
| 161 | 4 | size_t i = si; | |
| 162 |
1/2✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 4 times.
|
4 | if (u_is_word_char(u_get_char(lr.line.data, len, &i))) { |
| 163 | break; | ||
| 164 | } | ||
| 165 | ✗ | si = i; | |
| 166 | } | ||
| 167 | |||
| 168 |
1/2✗ Branch 9 → 10 not taken.
✓ Branch 9 → 15 taken 4 times.
|
4 | if (si == len) { |
| 169 | // No word char between cursor and EOL; no word | ||
| 170 | ✗ | return (WordBounds){0}; | |
| 171 | } | ||
| 172 | |||
| 173 | // Move left, to start of word (if cursor is already within one) | ||
| 174 | 8 | size_t ei = si; | |
| 175 |
2/2✓ Branch 15 → 11 taken 4 times.
✓ Branch 15 → 20 taken 4 times.
|
8 | while (si) { |
| 176 | 4 | size_t i = si; | |
| 177 |
1/2✓ Branch 12 → 13 taken 4 times.
✗ Branch 12 → 14 not taken.
|
4 | if (!u_is_word_char(u_prev_char(lr.line.data, &i))) { |
| 178 | break; | ||
| 179 | } | ||
| 180 | 4 | si = i; | |
| 181 | } | ||
| 182 | |||
| 183 | // Move right, to end of word | ||
| 184 |
2/2✓ Branch 21 → 16 taken 4114 times.
✓ Branch 21 → 22 taken 4 times.
|
4118 | while (ei < len) { |
| 185 | 4114 | size_t i = ei; | |
| 186 |
1/2✓ Branch 17 → 18 taken 4114 times.
✗ Branch 17 → 19 not taken.
|
4114 | if (!u_is_word_char(u_get_char(lr.line.data, len, &i))) { |
| 187 | break; | ||
| 188 | } | ||
| 189 | 4114 | ei = i; | |
| 190 | } | ||
| 191 | |||
| 192 | 4 | bool empty = (si == ei); | |
| 193 | 4 | BUG_ON(si > ei); | |
| 194 | |||
| 195 | 4 | return (WordBounds) { | |
| 196 |
1/2✓ Branch 24 → 25 taken 4 times.
✗ Branch 24 → 26 not taken.
|
4 | .start = empty ? 0 : si, |
| 197 |
1/2✓ Branch 26 → 27 taken 4 times.
✗ Branch 26 → 28 not taken.
|
4 | .end = empty ? 0 : ei, |
| 198 | }; | ||
| 199 | } | ||
| 200 | |||
| 201 | 4 | StringView view_get_word_under_cursor(const View *view) | |
| 202 | { | ||
| 203 | 4 | CurrentLineRef lr = get_current_line_and_offset(view->cursor); | |
| 204 | 4 | WordBounds wb = get_bounds_for_word_under_cursor(lr); | |
| 205 |
1/2✓ Branch 4 → 5 taken 4 times.
✗ Branch 4 → 6 not taken.
|
4 | return string_view(lr.line.data + wb.start, wb.end ? wb.end - wb.start : 0); |
| 206 | } | ||
| 207 | |||
| 208 | 1 | String dump_buffer(const View *view) | |
| 209 | { | ||
| 210 | 1 | const Buffer *buffer = view->buffer; | |
| 211 | 1 | uintmax_t counts[2]; | |
| 212 | 1 | char sizestr[FILESIZE_STR_MAX]; | |
| 213 | 1 | buffer_count_blocks_and_bytes(buffer, counts); | |
| 214 | 1 | BUG_ON(counts[0] < 1); | |
| 215 | 1 | BUG_ON(!buffer->setup); | |
| 216 | 1 | String buf = string_new(1024 + (DEBUG ? 24 * counts[0] : 0)); | |
| 217 | |||
| 218 | 2 | string_sprintf ( | |
| 219 | &buf, | ||
| 220 | "%s %s\n%s %lu\n%s %s\n%s %s\n%s %ju\n%s %zu\n%s %s\n", | ||
| 221 | " Name:", buffer_filename(buffer), | ||
| 222 | 1 | " ID:", buffer->id, | |
| 223 | 1 | " Encoding:", buffer->encoding, | |
| 224 | 1 | " Filetype:", buffer->options.filetype, | |
| 225 | " Blocks:", counts[0], | ||
| 226 | 1 | " Lines:", buffer->nl, | |
| 227 | " Size:", filesize_to_str(counts[1], sizestr) | ||
| 228 | ); | ||
| 229 | |||
| 230 | 1 | if ( | |
| 231 |
3/6✓ Branch 11 → 12 taken 1 time.
✗ Branch 11 → 17 not taken.
✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 17 not taken.
✓ Branch 13 → 14 taken 1 time.
✗ Branch 13 → 17 not taken.
|
1 | buffer->stdout_buffer || buffer->temporary || buffer->readonly |
| 232 |
3/6✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 17 not taken.
✓ Branch 15 → 16 taken 1 time.
✗ Branch 15 → 17 not taken.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 30 taken 1 time.
|
1 | || buffer->locked || buffer->crlf_newlines || buffer->bom |
| 233 | ) { | ||
| 234 | ✗ | string_sprintf ( | |
| 235 | &buf, | ||
| 236 | " Flags:%s%s%s%s%s%s\n", | ||
| 237 | buffer->stdout_buffer ? " STDOUT" : "", | ||
| 238 | ✗ | buffer->temporary ? " TMP" : "", | |
| 239 | ✗ | buffer->readonly ? " RO" : "", | |
| 240 | ✗ | buffer->locked ? " LOCKED" : "", | |
| 241 | ✗ | buffer->crlf_newlines ? " CRLF" : "", | |
| 242 | ✗ | buffer->bom ? " BOM" : "" | |
| 243 | ); | ||
| 244 | } | ||
| 245 | |||
| 246 |
1/2✗ Branch 30 → 31 not taken.
✓ Branch 30 → 32 taken 1 time.
|
1 | if (buffer->views.count > 1) { |
| 247 | ✗ | string_sprintf(&buf, " Views: %zu\n", buffer->views.count); | |
| 248 | } | ||
| 249 | |||
| 250 |
1/2✗ Branch 32 → 33 not taken.
✓ Branch 32 → 40 taken 1 time.
|
1 | if (buffer->abs_filename) { |
| 251 | ✗ | const FileInfo *file = &buffer->file; | |
| 252 | ✗ | unsigned int perms = file->mode & 07777; | |
| 253 | ✗ | char tstr[TIME_STR_BUFSIZE]; | |
| 254 | ✗ | char modestr[FILE_PERMISSIONS_BUFSIZE]; | |
| 255 | ✗ | string_sprintf ( | |
| 256 | &buf, | ||
| 257 | "\nLast stat:\n----------\n\n" | ||
| 258 | "%s %s\n%s %s\n%s -%s (%04o)\n%s %jd\n%s %jd\n%s %s\n%s %jd\n%s %ju\n", | ||
| 259 | ✗ | " Path:", buffer->abs_filename, | |
| 260 | ✗ | " Modified:", timespec_to_str(&file->mtime, tstr) ? tstr : "-", | |
| 261 | ✗ | " Mode:", file_permissions_to_str(file->mode, modestr), perms, | |
| 262 | ✗ | " User:", (intmax_t)file->uid, | |
| 263 | ✗ | " Group:", (intmax_t)file->gid, | |
| 264 | ✗ | " Size:", filesize_to_str(file->size, sizestr), | |
| 265 | ✗ | " Device:", (intmax_t)file->dev, | |
| 266 | ✗ | " Inode:", (uintmax_t)file->ino | |
| 267 | ); | ||
| 268 | } | ||
| 269 | |||
| 270 | 1 | if (DEBUG >= 1) { | |
| 271 | 1 | const BlockIter *cursor = &view->cursor; | |
| 272 | 1 | string_append_literal(&buf, "\nBlocks:\n-------\n\n"); | |
| 273 | 1 | size_t i = 1; | |
| 274 | 1 | const Block *b; | |
| 275 |
2/2✓ Branch 47 → 42 taken 1 time.
✓ Branch 47 → 48 taken 1 time.
|
2 | block_for_each(b, &buffer->blocks) { |
| 276 | 1 | string_sprintf(&buf, "%4zu: %zu/%zu nl=%zu", i++, b->size, b->alloc, b->nl); | |
| 277 |
1/2✓ Branch 43 → 44 taken 1 time.
✗ Branch 43 → 45 not taken.
|
1 | if (b == cursor->blk) { |
| 278 | 1 | string_sprintf(&buf, " (cursor; offset=%zu)", cursor->offset); | |
| 279 | } | ||
| 280 | 1 | string_append_byte(&buf, '\n'); | |
| 281 | } | ||
| 282 | } | ||
| 283 | |||
| 284 | 1 | return buf; | |
| 285 | } | ||
| 286 |