1 | // This is an open source non-commercial project. Dear PVS-Studio, please check |
2 | // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com |
3 | |
4 | #include <stdbool.h> |
5 | #include <inttypes.h> |
6 | |
7 | #include "nvim/assert.h" |
8 | #include "nvim/change.h" |
9 | #include "nvim/cursor.h" |
10 | #include "nvim/charset.h" |
11 | #include "nvim/fold.h" |
12 | #include "nvim/memline.h" |
13 | #include "nvim/memory.h" |
14 | #include "nvim/misc1.h" |
15 | #include "nvim/move.h" |
16 | #include "nvim/screen.h" |
17 | #include "nvim/state.h" |
18 | #include "nvim/vim.h" |
19 | #include "nvim/ascii.h" |
20 | #include "nvim/mark.h" |
21 | |
22 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
23 | # include "cursor.c.generated.h" |
24 | #endif |
25 | |
26 | /* |
27 | * Get the screen position of the cursor. |
28 | */ |
29 | int getviscol(void) |
30 | { |
31 | colnr_T x; |
32 | |
33 | getvvcol(curwin, &curwin->w_cursor, &x, NULL, NULL); |
34 | return (int)x; |
35 | } |
36 | |
37 | /* |
38 | * Get the screen position of character col with a coladd in the cursor line. |
39 | */ |
40 | int getviscol2(colnr_T col, colnr_T coladd) |
41 | { |
42 | colnr_T x; |
43 | pos_T pos; |
44 | |
45 | pos.lnum = curwin->w_cursor.lnum; |
46 | pos.col = col; |
47 | pos.coladd = coladd; |
48 | getvvcol(curwin, &pos, &x, NULL, NULL); |
49 | return (int)x; |
50 | } |
51 | |
52 | /* |
53 | * Go to column "wcol", and add/insert white space as necessary to get the |
54 | * cursor in that column. |
55 | * The caller must have saved the cursor line for undo! |
56 | */ |
57 | int coladvance_force(colnr_T wcol) |
58 | { |
59 | int rc = coladvance2(&curwin->w_cursor, true, false, wcol); |
60 | |
61 | if (wcol == MAXCOL) { |
62 | curwin->w_valid &= ~VALID_VIRTCOL; |
63 | } else { |
64 | /* Virtcol is valid */ |
65 | curwin->w_valid |= VALID_VIRTCOL; |
66 | curwin->w_virtcol = wcol; |
67 | } |
68 | return rc; |
69 | } |
70 | |
71 | /* |
72 | * Try to advance the Cursor to the specified screen column. |
73 | * If virtual editing: fine tune the cursor position. |
74 | * Note that all virtual positions off the end of a line should share |
75 | * a curwin->w_cursor.col value (n.b. this is equal to STRLEN(line)), |
76 | * beginning at coladd 0. |
77 | * |
78 | * return OK if desired column is reached, FAIL if not |
79 | */ |
80 | int coladvance(colnr_T wcol) |
81 | { |
82 | int rc = getvpos(&curwin->w_cursor, wcol); |
83 | |
84 | if (wcol == MAXCOL || rc == FAIL) |
85 | curwin->w_valid &= ~VALID_VIRTCOL; |
86 | else if (*get_cursor_pos_ptr() != TAB) { |
87 | /* Virtcol is valid when not on a TAB */ |
88 | curwin->w_valid |= VALID_VIRTCOL; |
89 | curwin->w_virtcol = wcol; |
90 | } |
91 | return rc; |
92 | } |
93 | |
94 | static int coladvance2( |
95 | pos_T *pos, |
96 | bool addspaces, /* change the text to achieve our goal? */ |
97 | bool finetune, /* change char offset for the exact column */ |
98 | colnr_T wcol /* column to move to */ |
99 | ) |
100 | { |
101 | int idx; |
102 | char_u *ptr; |
103 | char_u *line; |
104 | colnr_T col = 0; |
105 | int csize = 0; |
106 | int one_more; |
107 | int head = 0; |
108 | |
109 | one_more = (State & INSERT) |
110 | || restart_edit != NUL |
111 | || (VIsual_active && *p_sel != 'o') |
112 | || ((ve_flags & VE_ONEMORE) && wcol < MAXCOL); |
113 | line = ml_get_buf(curbuf, pos->lnum, false); |
114 | |
115 | if (wcol >= MAXCOL) { |
116 | idx = (int)STRLEN(line) - 1 + one_more; |
117 | col = wcol; |
118 | |
119 | if ((addspaces || finetune) && !VIsual_active) { |
120 | curwin->w_curswant = linetabsize(line) + one_more; |
121 | if (curwin->w_curswant > 0) |
122 | --curwin->w_curswant; |
123 | } |
124 | } else { |
125 | int width = curwin->w_width_inner - win_col_off(curwin); |
126 | |
127 | if (finetune |
128 | && curwin->w_p_wrap |
129 | && curwin->w_width_inner != 0 |
130 | && wcol >= (colnr_T)width) { |
131 | csize = linetabsize(line); |
132 | if (csize > 0) |
133 | csize--; |
134 | |
135 | if (wcol / width > (colnr_T)csize / width |
136 | && ((State & INSERT) == 0 || (int)wcol > csize + 1)) { |
137 | /* In case of line wrapping don't move the cursor beyond the |
138 | * right screen edge. In Insert mode allow going just beyond |
139 | * the last character (like what happens when typing and |
140 | * reaching the right window edge). */ |
141 | wcol = (csize / width + 1) * width - 1; |
142 | } |
143 | } |
144 | |
145 | ptr = line; |
146 | while (col <= wcol && *ptr != NUL) { |
147 | /* Count a tab for what it's worth (if list mode not on) */ |
148 | csize = win_lbr_chartabsize(curwin, line, ptr, col, &head); |
149 | MB_PTR_ADV(ptr); |
150 | col += csize; |
151 | } |
152 | idx = (int)(ptr - line); |
153 | /* |
154 | * Handle all the special cases. The virtual_active() check |
155 | * is needed to ensure that a virtual position off the end of |
156 | * a line has the correct indexing. The one_more comparison |
157 | * replaces an explicit add of one_more later on. |
158 | */ |
159 | if (col > wcol || (!virtual_active() && one_more == 0)) { |
160 | idx -= 1; |
161 | /* Don't count the chars from 'showbreak'. */ |
162 | csize -= head; |
163 | col -= csize; |
164 | } |
165 | |
166 | if (virtual_active() |
167 | && addspaces |
168 | && ((col != wcol && col != wcol + 1) || csize > 1)) { |
169 | /* 'virtualedit' is set: The difference between wcol and col is |
170 | * filled with spaces. */ |
171 | |
172 | if (line[idx] == NUL) { |
173 | /* Append spaces */ |
174 | int correct = wcol - col; |
175 | size_t newline_size; |
176 | STRICT_ADD(idx, correct, &newline_size, size_t); |
177 | char_u *newline = xmallocz(newline_size); |
178 | memcpy(newline, line, (size_t)idx); |
179 | memset(newline + idx, ' ', (size_t)correct); |
180 | |
181 | ml_replace(pos->lnum, newline, false); |
182 | changed_bytes(pos->lnum, (colnr_T)idx); |
183 | idx += correct; |
184 | col = wcol; |
185 | } else { |
186 | /* Break a tab */ |
187 | int linelen = (int)STRLEN(line); |
188 | int correct = wcol - col - csize + 1; /* negative!! */ |
189 | char_u *newline; |
190 | |
191 | if (-correct > csize) |
192 | return FAIL; |
193 | |
194 | size_t n; |
195 | STRICT_ADD(linelen - 1, csize, &n, size_t); |
196 | newline = xmallocz(n); |
197 | // Copy first idx chars |
198 | memcpy(newline, line, (size_t)idx); |
199 | // Replace idx'th char with csize spaces |
200 | memset(newline + idx, ' ', (size_t)csize); |
201 | // Copy the rest of the line |
202 | STRICT_SUB(linelen, idx, &n, size_t); |
203 | STRICT_SUB(n, 1, &n, size_t); |
204 | memcpy(newline + idx + csize, line + idx + 1, n); |
205 | |
206 | ml_replace(pos->lnum, newline, false); |
207 | changed_bytes(pos->lnum, idx); |
208 | idx += (csize - 1 + correct); |
209 | col += correct; |
210 | } |
211 | } |
212 | } |
213 | |
214 | if (idx < 0) |
215 | pos->col = 0; |
216 | else |
217 | pos->col = idx; |
218 | |
219 | pos->coladd = 0; |
220 | |
221 | if (finetune) { |
222 | if (wcol == MAXCOL) { |
223 | /* The width of the last character is used to set coladd. */ |
224 | if (!one_more) { |
225 | colnr_T scol, ecol; |
226 | |
227 | getvcol(curwin, pos, &scol, NULL, &ecol); |
228 | pos->coladd = ecol - scol; |
229 | } |
230 | } else { |
231 | int b = (int)wcol - (int)col; |
232 | |
233 | // The difference between wcol and col is used to set coladd. |
234 | if (b > 0 && b < (MAXCOL - 2 * curwin->w_width_inner)) { |
235 | pos->coladd = b; |
236 | } |
237 | |
238 | col += b; |
239 | } |
240 | } |
241 | |
242 | // Prevent from moving onto a trail byte. |
243 | if (has_mbyte) { |
244 | mark_mb_adjustpos(curbuf, pos); |
245 | } |
246 | |
247 | if (col < wcol) |
248 | return FAIL; |
249 | return OK; |
250 | } |
251 | |
252 | /* |
253 | * Return in "pos" the position of the cursor advanced to screen column "wcol". |
254 | * return OK if desired column is reached, FAIL if not |
255 | */ |
256 | int getvpos(pos_T *pos, colnr_T wcol) |
257 | { |
258 | return coladvance2(pos, false, virtual_active(), wcol); |
259 | } |
260 | |
261 | /* |
262 | * Increment the cursor position. See inc() for return values. |
263 | */ |
264 | int inc_cursor(void) |
265 | { |
266 | return inc(&curwin->w_cursor); |
267 | } |
268 | |
269 | /* |
270 | * dec(p) |
271 | * |
272 | * Decrement the line pointer 'p' crossing line boundaries as necessary. |
273 | * Return 1 when crossing a line, -1 when at start of file, 0 otherwise. |
274 | */ |
275 | int dec_cursor(void) |
276 | { |
277 | return dec(&curwin->w_cursor); |
278 | } |
279 | |
280 | /// Get the line number relative to the current cursor position, i.e. the |
281 | /// difference between line number and cursor position. Only look for lines that |
282 | /// can be visible, folded lines don't count. |
283 | /// |
284 | /// @param lnum line number to get the result for |
285 | linenr_T get_cursor_rel_lnum(win_T *wp, linenr_T lnum) |
286 | { |
287 | linenr_T cursor = wp->w_cursor.lnum; |
288 | if (lnum == cursor || !hasAnyFolding(wp)) { |
289 | return lnum - cursor; |
290 | } |
291 | |
292 | linenr_T from_line = lnum < cursor ? lnum : cursor; |
293 | linenr_T to_line = lnum > cursor ? lnum : cursor; |
294 | linenr_T retval = 0; |
295 | |
296 | // Loop until we reach to_line, skipping folds. |
297 | for (; from_line < to_line; from_line++, retval++) { |
298 | // If from_line is in a fold, set it to the last line of that fold. |
299 | (void)hasFoldingWin(wp, from_line, NULL, &from_line, true, NULL); |
300 | } |
301 | |
302 | // If to_line is in a closed fold, the line count is off by +1. Correct it. |
303 | if (from_line > to_line) { |
304 | retval--; |
305 | } |
306 | |
307 | return (lnum < cursor) ? -retval : retval; |
308 | } |
309 | |
310 | // Make sure "pos.lnum" and "pos.col" are valid in "buf". |
311 | // This allows for the col to be on the NUL byte. |
312 | void check_pos(buf_T *buf, pos_T *pos) |
313 | { |
314 | char_u *line; |
315 | colnr_T len; |
316 | |
317 | if (pos->lnum > buf->b_ml.ml_line_count) { |
318 | pos->lnum = buf->b_ml.ml_line_count; |
319 | } |
320 | |
321 | if (pos->col > 0) { |
322 | line = ml_get_buf(buf, pos->lnum, false); |
323 | len = (colnr_T)STRLEN(line); |
324 | if (pos->col > len) { |
325 | pos->col = len; |
326 | } |
327 | } |
328 | } |
329 | |
330 | /* |
331 | * Make sure curwin->w_cursor.lnum is valid. |
332 | */ |
333 | void check_cursor_lnum(void) |
334 | { |
335 | if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { |
336 | /* If there is a closed fold at the end of the file, put the cursor in |
337 | * its first line. Otherwise in the last line. */ |
338 | if (!hasFolding(curbuf->b_ml.ml_line_count, |
339 | &curwin->w_cursor.lnum, NULL)) |
340 | curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; |
341 | } |
342 | if (curwin->w_cursor.lnum <= 0) |
343 | curwin->w_cursor.lnum = 1; |
344 | } |
345 | |
346 | /* |
347 | * Make sure curwin->w_cursor.col is valid. |
348 | */ |
349 | void check_cursor_col(void) |
350 | { |
351 | check_cursor_col_win(curwin); |
352 | } |
353 | |
354 | /// Make sure win->w_cursor.col is valid. Special handling of insert-mode. |
355 | /// @see mb_check_adjust_col |
356 | void check_cursor_col_win(win_T *win) |
357 | { |
358 | colnr_T len; |
359 | colnr_T oldcol = win->w_cursor.col; |
360 | colnr_T oldcoladd = win->w_cursor.col + win->w_cursor.coladd; |
361 | |
362 | len = (colnr_T)STRLEN(ml_get_buf(win->w_buffer, win->w_cursor.lnum, false)); |
363 | if (len == 0) { |
364 | win->w_cursor.col = 0; |
365 | } else if (win->w_cursor.col >= len) { |
366 | /* Allow cursor past end-of-line when: |
367 | * - in Insert mode or restarting Insert mode |
368 | * - in Visual mode and 'selection' isn't "old" |
369 | * - 'virtualedit' is set */ |
370 | if ((State & INSERT) || restart_edit |
371 | || (VIsual_active && *p_sel != 'o') |
372 | || (ve_flags & VE_ONEMORE) |
373 | || virtual_active()) { |
374 | win->w_cursor.col = len; |
375 | } else { |
376 | win->w_cursor.col = len - 1; |
377 | // Move the cursor to the head byte. |
378 | if (has_mbyte) { |
379 | mark_mb_adjustpos(win->w_buffer, &win->w_cursor); |
380 | } |
381 | } |
382 | } else if (win->w_cursor.col < 0) { |
383 | win->w_cursor.col = 0; |
384 | } |
385 | |
386 | // If virtual editing is on, we can leave the cursor on the old position, |
387 | // only we must set it to virtual. But don't do it when at the end of the |
388 | // line. |
389 | if (oldcol == MAXCOL) { |
390 | win->w_cursor.coladd = 0; |
391 | } else if (ve_flags == VE_ALL) { |
392 | if (oldcoladd > win->w_cursor.col) { |
393 | win->w_cursor.coladd = oldcoladd - win->w_cursor.col; |
394 | |
395 | // Make sure that coladd is not more than the char width. |
396 | // Not for the last character, coladd is then used when the cursor |
397 | // is actually after the last character. |
398 | if (win->w_cursor.col + 1 < len) { |
399 | assert(win->w_cursor.coladd > 0); |
400 | int cs, ce; |
401 | |
402 | getvcol(win, &win->w_cursor, &cs, NULL, &ce); |
403 | if (win->w_cursor.coladd > ce - cs) { |
404 | win->w_cursor.coladd = ce - cs; |
405 | } |
406 | } |
407 | } else { |
408 | // avoid weird number when there is a miscalculation or overflow |
409 | win->w_cursor.coladd = 0; |
410 | } |
411 | } |
412 | } |
413 | |
414 | /* |
415 | * make sure curwin->w_cursor in on a valid character |
416 | */ |
417 | void check_cursor(void) |
418 | { |
419 | check_cursor_lnum(); |
420 | check_cursor_col(); |
421 | } |
422 | |
423 | /* |
424 | * Make sure curwin->w_cursor is not on the NUL at the end of the line. |
425 | * Allow it when in Visual mode and 'selection' is not "old". |
426 | */ |
427 | void adjust_cursor_col(void) |
428 | { |
429 | if (curwin->w_cursor.col > 0 |
430 | && (!VIsual_active || *p_sel == 'o') |
431 | && gchar_cursor() == NUL) |
432 | --curwin->w_cursor.col; |
433 | } |
434 | |
435 | /* |
436 | * When curwin->w_leftcol has changed, adjust the cursor position. |
437 | * Return true if the cursor was moved. |
438 | */ |
439 | bool leftcol_changed(void) |
440 | { |
441 | // TODO(hinidu): I think it should be colnr_T or int, but p_siso is long. |
442 | // Perhaps we can change p_siso to int. |
443 | int64_t lastcol; |
444 | colnr_T s, e; |
445 | bool retval = false; |
446 | |
447 | changed_cline_bef_curs(); |
448 | lastcol = curwin->w_leftcol + curwin->w_width_inner - curwin_col_off() - 1; |
449 | validate_virtcol(); |
450 | |
451 | /* |
452 | * If the cursor is right or left of the screen, move it to last or first |
453 | * character. |
454 | */ |
455 | if (curwin->w_virtcol > (colnr_T)(lastcol - p_siso)) { |
456 | retval = true; |
457 | coladvance((colnr_T)(lastcol - p_siso)); |
458 | } else if (curwin->w_virtcol < curwin->w_leftcol + p_siso) { |
459 | retval = true; |
460 | coladvance((colnr_T)(curwin->w_leftcol + p_siso)); |
461 | } |
462 | |
463 | /* |
464 | * If the start of the character under the cursor is not on the screen, |
465 | * advance the cursor one more char. If this fails (last char of the |
466 | * line) adjust the scrolling. |
467 | */ |
468 | getvvcol(curwin, &curwin->w_cursor, &s, NULL, &e); |
469 | if (e > (colnr_T)lastcol) { |
470 | retval = true; |
471 | coladvance(s - 1); |
472 | } else if (s < curwin->w_leftcol) { |
473 | retval = true; |
474 | if (coladvance(e + 1) == FAIL) { /* there isn't another character */ |
475 | curwin->w_leftcol = s; /* adjust w_leftcol instead */ |
476 | changed_cline_bef_curs(); |
477 | } |
478 | } |
479 | |
480 | if (retval) |
481 | curwin->w_set_curswant = true; |
482 | redraw_later(NOT_VALID); |
483 | return retval; |
484 | } |
485 | |
486 | int gchar_cursor(void) |
487 | { |
488 | return utf_ptr2char(get_cursor_pos_ptr()); |
489 | } |
490 | |
491 | /* |
492 | * Write a character at the current cursor position. |
493 | * It is directly written into the block. |
494 | */ |
495 | void pchar_cursor(char_u c) |
496 | { |
497 | *(ml_get_buf(curbuf, curwin->w_cursor.lnum, true) |
498 | + curwin->w_cursor.col) = c; |
499 | } |
500 | |
501 | /* |
502 | * Return pointer to cursor line. |
503 | */ |
504 | char_u *get_cursor_line_ptr(void) |
505 | { |
506 | return ml_get_buf(curbuf, curwin->w_cursor.lnum, false); |
507 | } |
508 | |
509 | /* |
510 | * Return pointer to cursor position. |
511 | */ |
512 | char_u *get_cursor_pos_ptr(void) |
513 | { |
514 | return ml_get_buf(curbuf, curwin->w_cursor.lnum, false) + |
515 | curwin->w_cursor.col; |
516 | } |
517 | |