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 | |
6 | #include "nvim/mouse.h" |
7 | #include "nvim/vim.h" |
8 | #include "nvim/ascii.h" |
9 | #include "nvim/window.h" |
10 | #include "nvim/state.h" |
11 | #include "nvim/strings.h" |
12 | #include "nvim/screen.h" |
13 | #include "nvim/syntax.h" |
14 | #include "nvim/ui.h" |
15 | #include "nvim/ui_compositor.h" |
16 | #include "nvim/os_unix.h" |
17 | #include "nvim/fold.h" |
18 | #include "nvim/diff.h" |
19 | #include "nvim/move.h" |
20 | #include "nvim/misc1.h" |
21 | #include "nvim/cursor.h" |
22 | #include "nvim/buffer_defs.h" |
23 | #include "nvim/memline.h" |
24 | #include "nvim/charset.h" |
25 | |
26 | #ifdef INCLUDE_GENERATED_DECLARATIONS |
27 | # include "mouse.c.generated.h" |
28 | #endif |
29 | |
30 | static linenr_T orig_topline = 0; |
31 | static int orig_topfill = 0; |
32 | |
33 | // Move the cursor to the specified row and column on the screen. |
34 | // Change current window if necessary. Returns an integer with the |
35 | // CURSOR_MOVED bit set if the cursor has moved or unset otherwise. |
36 | // |
37 | // The MOUSE_FOLD_CLOSE bit is set when clicked on the '-' in a fold column. |
38 | // The MOUSE_FOLD_OPEN bit is set when clicked on the '+' in a fold column. |
39 | // |
40 | // If flags has MOUSE_FOCUS, then the current window will not be changed, and |
41 | // if the mouse is outside the window then the text will scroll, or if the |
42 | // mouse was previously on a status line, then the status line may be dragged. |
43 | // |
44 | // If flags has MOUSE_MAY_VIS, then VIsual mode will be started before the |
45 | // cursor is moved unless the cursor was on a status line. |
46 | // This function returns one of IN_UNKNOWN, IN_BUFFER, IN_STATUS_LINE or |
47 | // IN_SEP_LINE depending on where the cursor was clicked. |
48 | // |
49 | // If flags has MOUSE_MAY_STOP_VIS, then Visual mode will be stopped, unless |
50 | // the mouse is on the status line of the same window. |
51 | // |
52 | // If flags has MOUSE_DID_MOVE, nothing is done if the mouse didn't move since |
53 | // the last call. |
54 | // |
55 | // If flags has MOUSE_SETPOS, nothing is done, only the current position is |
56 | // remembered. |
57 | int jump_to_mouse(int flags, |
58 | bool *inclusive, // used for inclusive operator, can be NULL |
59 | int which_button) // MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE |
60 | { |
61 | static int on_status_line = 0; // #lines below bottom of window |
62 | static int on_sep_line = 0; // on separator right of window |
63 | static int prev_row = -1; |
64 | static int prev_col = -1; |
65 | static win_T *dragwin = NULL; // window being dragged |
66 | static int did_drag = false; // drag was noticed |
67 | |
68 | win_T *wp, *old_curwin; |
69 | pos_T old_cursor; |
70 | int count; |
71 | bool first; |
72 | int row = mouse_row; |
73 | int col = mouse_col; |
74 | int grid = mouse_grid; |
75 | int mouse_char; |
76 | |
77 | mouse_past_bottom = false; |
78 | mouse_past_eol = false; |
79 | |
80 | if (flags & MOUSE_RELEASED) { |
81 | // On button release we may change window focus if positioned on a |
82 | // status line and no dragging happened. |
83 | if (dragwin != NULL && !did_drag) |
84 | flags &= ~(MOUSE_FOCUS | MOUSE_DID_MOVE); |
85 | dragwin = NULL; |
86 | did_drag = false; |
87 | } |
88 | |
89 | if ((flags & MOUSE_DID_MOVE) |
90 | && prev_row == mouse_row |
91 | && prev_col == mouse_col) { |
92 | retnomove: |
93 | // before moving the cursor for a left click which is NOT in a status |
94 | // line, stop Visual mode |
95 | if (on_status_line) |
96 | return IN_STATUS_LINE; |
97 | if (on_sep_line) |
98 | return IN_SEP_LINE; |
99 | if (flags & MOUSE_MAY_STOP_VIS) { |
100 | end_visual_mode(); |
101 | redraw_curbuf_later(INVERTED); // delete the inversion |
102 | } |
103 | return IN_BUFFER; |
104 | } |
105 | |
106 | prev_row = mouse_row; |
107 | prev_col = mouse_col; |
108 | |
109 | if (flags & MOUSE_SETPOS) |
110 | goto retnomove; // ugly goto... |
111 | |
112 | // Remember the character under the mouse, it might be a '-' or '+' in the |
113 | // fold column. NB: only works for ASCII chars! |
114 | if (row >= 0 && row < Rows && col >= 0 && col <= Columns |
115 | && default_grid.chars != NULL) { |
116 | mouse_char = default_grid.chars[default_grid.line_offset[row] |
117 | + (unsigned)col][0]; |
118 | } else { |
119 | mouse_char = ' '; |
120 | } |
121 | |
122 | old_curwin = curwin; |
123 | old_cursor = curwin->w_cursor; |
124 | |
125 | if (!(flags & MOUSE_FOCUS)) { |
126 | if (row < 0 || col < 0) // check if it makes sense |
127 | return IN_UNKNOWN; |
128 | |
129 | // find the window where the row is in |
130 | wp = mouse_find_win(&grid, &row, &col); |
131 | if (wp == NULL) { |
132 | return IN_UNKNOWN; |
133 | } |
134 | dragwin = NULL; |
135 | // winpos and height may change in win_enter()! |
136 | if (grid == DEFAULT_GRID_HANDLE && row >= wp->w_height) { |
137 | // In (or below) status line |
138 | on_status_line = row - wp->w_height + 1; |
139 | dragwin = wp; |
140 | } else { |
141 | on_status_line = 0; |
142 | } |
143 | |
144 | if (grid == DEFAULT_GRID_HANDLE && col >= wp->w_width) { |
145 | // In separator line |
146 | on_sep_line = col - wp->w_width + 1; |
147 | dragwin = wp; |
148 | } else { |
149 | on_sep_line = 0; |
150 | } |
151 | |
152 | // The rightmost character of the status line might be a vertical |
153 | // separator character if there is no connecting window to the right. |
154 | if (on_status_line && on_sep_line) { |
155 | if (stl_connected(wp)) |
156 | on_sep_line = 0; |
157 | else |
158 | on_status_line = 0; |
159 | } |
160 | |
161 | // Before jumping to another buffer, or moving the cursor for a left |
162 | // click, stop Visual mode. |
163 | if (VIsual_active |
164 | && (wp->w_buffer != curwin->w_buffer |
165 | || (!on_status_line |
166 | && !on_sep_line |
167 | && (wp->w_p_rl |
168 | ? col < wp->w_width_inner - wp->w_p_fdc |
169 | : col >= wp->w_p_fdc + (cmdwin_type == 0 && wp == curwin |
170 | ? 0 : 1)) |
171 | && (flags & MOUSE_MAY_STOP_VIS)))) { |
172 | end_visual_mode(); |
173 | redraw_curbuf_later(INVERTED); // delete the inversion |
174 | } |
175 | if (cmdwin_type != 0 && wp != curwin) { |
176 | // A click outside the command-line window: Use modeless |
177 | // selection if possible. Allow dragging the status lines. |
178 | on_sep_line = 0; |
179 | row = 0; |
180 | col += wp->w_wincol; |
181 | wp = curwin; |
182 | } |
183 | // Only change window focus when not clicking on or dragging the |
184 | // status line. Do change focus when releasing the mouse button |
185 | // (MOUSE_FOCUS was set above if we dragged first). |
186 | if (dragwin == NULL || (flags & MOUSE_RELEASED)) |
187 | win_enter(wp, true); // can make wp invalid! |
188 | // set topline, to be able to check for double click ourselves |
189 | if (curwin != old_curwin) |
190 | set_mouse_topline(curwin); |
191 | if (on_status_line) { // In (or below) status line |
192 | // Don't use start_arrow() if we're in the same window |
193 | if (curwin == old_curwin) |
194 | return IN_STATUS_LINE; |
195 | else |
196 | return IN_STATUS_LINE | CURSOR_MOVED; |
197 | } |
198 | if (on_sep_line) { // In (or below) status line |
199 | // Don't use start_arrow() if we're in the same window |
200 | if (curwin == old_curwin) |
201 | return IN_SEP_LINE; |
202 | else |
203 | return IN_SEP_LINE | CURSOR_MOVED; |
204 | } |
205 | |
206 | curwin->w_cursor.lnum = curwin->w_topline; |
207 | } else if (on_status_line && which_button == MOUSE_LEFT) { |
208 | if (dragwin != NULL) { |
209 | // Drag the status line |
210 | count = row - dragwin->w_winrow - dragwin->w_height + 1 |
211 | - on_status_line; |
212 | win_drag_status_line(dragwin, count); |
213 | did_drag |= count; |
214 | } |
215 | return IN_STATUS_LINE; // Cursor didn't move |
216 | } else if (on_sep_line && which_button == MOUSE_LEFT) { |
217 | if (dragwin != NULL) { |
218 | // Drag the separator column |
219 | count = col - dragwin->w_wincol - dragwin->w_width + 1 |
220 | - on_sep_line; |
221 | win_drag_vsep_line(dragwin, count); |
222 | did_drag |= count; |
223 | } |
224 | return IN_SEP_LINE; // Cursor didn't move |
225 | } else { |
226 | // keep_window_focus must be true |
227 | // before moving the cursor for a left click, stop Visual mode |
228 | if (flags & MOUSE_MAY_STOP_VIS) { |
229 | end_visual_mode(); |
230 | redraw_curbuf_later(INVERTED); // delete the inversion |
231 | } |
232 | |
233 | |
234 | row -= curwin->w_winrow; |
235 | col -= curwin->w_wincol; |
236 | |
237 | // When clicking beyond the end of the window, scroll the screen. |
238 | // Scroll by however many rows outside the window we are. |
239 | if (row < 0) { |
240 | count = 0; |
241 | for (first = true; curwin->w_topline > 1; ) { |
242 | if (curwin->w_topfill < diff_check(curwin, curwin->w_topline)) |
243 | ++count; |
244 | else |
245 | count += plines(curwin->w_topline - 1); |
246 | if (!first && count > -row) |
247 | break; |
248 | first = false; |
249 | (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); |
250 | if (curwin->w_topfill < diff_check(curwin, curwin->w_topline)) { |
251 | ++curwin->w_topfill; |
252 | } else { |
253 | --curwin->w_topline; |
254 | curwin->w_topfill = 0; |
255 | } |
256 | } |
257 | check_topfill(curwin, false); |
258 | curwin->w_valid &= |
259 | ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); |
260 | redraw_later(VALID); |
261 | row = 0; |
262 | } else if (row >= curwin->w_height_inner) { |
263 | count = 0; |
264 | for (first = true; curwin->w_topline < curbuf->b_ml.ml_line_count; ) { |
265 | if (curwin->w_topfill > 0) { |
266 | ++count; |
267 | } else { |
268 | count += plines(curwin->w_topline); |
269 | } |
270 | |
271 | if (!first && count > row - curwin->w_height_inner + 1) { |
272 | break; |
273 | } |
274 | first = false; |
275 | |
276 | if (hasFolding(curwin->w_topline, NULL, &curwin->w_topline) |
277 | && curwin->w_topline == curbuf->b_ml.ml_line_count) { |
278 | break; |
279 | } |
280 | |
281 | if (curwin->w_topfill > 0) { |
282 | --curwin->w_topfill; |
283 | } else { |
284 | ++curwin->w_topline; |
285 | curwin->w_topfill = |
286 | diff_check_fill(curwin, curwin->w_topline); |
287 | } |
288 | } |
289 | check_topfill(curwin, false); |
290 | redraw_later(VALID); |
291 | curwin->w_valid &= |
292 | ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); |
293 | row = curwin->w_height_inner - 1; |
294 | } else if (row == 0) { |
295 | // When dragging the mouse, while the text has been scrolled up as |
296 | // far as it goes, moving the mouse in the top line should scroll |
297 | // the text down (done later when recomputing w_topline). |
298 | if (mouse_dragging > 0 |
299 | && curwin->w_cursor.lnum |
300 | == curwin->w_buffer->b_ml.ml_line_count |
301 | && curwin->w_cursor.lnum == curwin->w_topline) { |
302 | curwin->w_valid &= ~(VALID_TOPLINE); |
303 | } |
304 | } |
305 | } |
306 | |
307 | // Check for position outside of the fold column. |
308 | if (curwin->w_p_rl ? col < curwin->w_width_inner - curwin->w_p_fdc : |
309 | col >= curwin->w_p_fdc + (cmdwin_type == 0 ? 0 : 1)) { |
310 | mouse_char = ' '; |
311 | } |
312 | |
313 | // compute the position in the buffer line from the posn on the screen |
314 | if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) { |
315 | mouse_past_bottom = true; |
316 | } |
317 | |
318 | if (!(flags & MOUSE_RELEASED) && which_button == MOUSE_LEFT) { |
319 | col = mouse_adjust_click(curwin, row, col); |
320 | } |
321 | |
322 | // Start Visual mode before coladvance(), for when 'sel' != "old" |
323 | if ((flags & MOUSE_MAY_VIS) && !VIsual_active) { |
324 | VIsual = old_cursor; |
325 | VIsual_active = true; |
326 | VIsual_reselect = true; |
327 | // if 'selectmode' contains "mouse", start Select mode |
328 | may_start_select('o'); |
329 | setmouse(); |
330 | |
331 | if (p_smd && msg_silent == 0) { |
332 | redraw_cmdline = true; // show visual mode later |
333 | } |
334 | } |
335 | |
336 | curwin->w_curswant = col; |
337 | curwin->w_set_curswant = false; // May still have been true |
338 | if (coladvance(col) == FAIL) { // Mouse click beyond end of line |
339 | if (inclusive != NULL) { |
340 | *inclusive = true; |
341 | } |
342 | mouse_past_eol = true; |
343 | } else if (inclusive != NULL) { |
344 | *inclusive = false; |
345 | } |
346 | |
347 | count = IN_BUFFER; |
348 | if (curwin != old_curwin || curwin->w_cursor.lnum != old_cursor.lnum |
349 | || curwin->w_cursor.col != old_cursor.col) { |
350 | count |= CURSOR_MOVED; // Cursor has moved |
351 | } |
352 | |
353 | if (mouse_char == '+') { |
354 | count |= MOUSE_FOLD_OPEN; |
355 | } else if (mouse_char != ' ') { |
356 | count |= MOUSE_FOLD_CLOSE; |
357 | } |
358 | |
359 | return count; |
360 | } |
361 | |
362 | // Compute the position in the buffer line from the posn on the screen in |
363 | // window "win". |
364 | // Returns true if the position is below the last line. |
365 | bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) |
366 | { |
367 | int col = *colp; |
368 | int row = *rowp; |
369 | linenr_T lnum; |
370 | bool retval = false; |
371 | int off; |
372 | int count; |
373 | |
374 | if (win->w_p_rl) { |
375 | col = win->w_width_inner - 1 - col; |
376 | } |
377 | |
378 | lnum = win->w_topline; |
379 | |
380 | while (row > 0) { |
381 | // Don't include filler lines in "count" |
382 | if (win->w_p_diff |
383 | && !hasFoldingWin(win, lnum, NULL, NULL, true, NULL)) { |
384 | if (lnum == win->w_topline) { |
385 | row -= win->w_topfill; |
386 | } else { |
387 | row -= diff_check_fill(win, lnum); |
388 | } |
389 | count = plines_win_nofill(win, lnum, true); |
390 | } else { |
391 | count = plines_win(win, lnum, true); |
392 | } |
393 | |
394 | if (count > row) { |
395 | break; // Position is in this buffer line. |
396 | } |
397 | |
398 | (void)hasFoldingWin(win, lnum, NULL, &lnum, true, NULL); |
399 | |
400 | if (lnum == win->w_buffer->b_ml.ml_line_count) { |
401 | retval = true; |
402 | break; // past end of file |
403 | } |
404 | row -= count; |
405 | ++lnum; |
406 | } |
407 | |
408 | if (!retval) { |
409 | // Compute the column without wrapping. |
410 | off = win_col_off(win) - win_col_off2(win); |
411 | if (col < off) |
412 | col = off; |
413 | col += row * (win->w_width_inner - off); |
414 | // add skip column (for long wrapping line) |
415 | col += win->w_skipcol; |
416 | } |
417 | |
418 | if (!win->w_p_wrap) { |
419 | col += win->w_leftcol; |
420 | } |
421 | |
422 | // skip line number and fold column in front of the line |
423 | col -= win_col_off(win); |
424 | if (col < 0) { |
425 | col = 0; |
426 | } |
427 | |
428 | *colp = col; |
429 | *rowp = row; |
430 | *lnump = lnum; |
431 | return retval; |
432 | } |
433 | |
434 | /// Find the window at "grid" position "*rowp" and "*colp". The positions are |
435 | /// updated to become relative to the top-left of the window. |
436 | /// |
437 | /// @return NULL when something is wrong. |
438 | win_T *mouse_find_win(int *gridp, int *rowp, int *colp) |
439 | { |
440 | win_T *wp_grid = mouse_find_grid_win(gridp, rowp, colp); |
441 | if (wp_grid) { |
442 | return wp_grid; |
443 | } else if (*gridp > 1) { |
444 | return NULL; |
445 | } |
446 | |
447 | |
448 | frame_T *fp; |
449 | |
450 | fp = topframe; |
451 | *rowp -= firstwin->w_winrow; |
452 | for (;; ) { |
453 | if (fp->fr_layout == FR_LEAF) |
454 | break; |
455 | if (fp->fr_layout == FR_ROW) { |
456 | for (fp = fp->fr_child; fp->fr_next != NULL; fp = fp->fr_next) { |
457 | if (*colp < fp->fr_width) |
458 | break; |
459 | *colp -= fp->fr_width; |
460 | } |
461 | } else { // fr_layout == FR_COL |
462 | for (fp = fp->fr_child; fp->fr_next != NULL; fp = fp->fr_next) { |
463 | if (*rowp < fp->fr_height) |
464 | break; |
465 | *rowp -= fp->fr_height; |
466 | } |
467 | } |
468 | } |
469 | // When using a timer that closes a window the window might not actually |
470 | // exist. |
471 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
472 | if (wp == fp->fr_win) { |
473 | return wp; |
474 | } |
475 | } |
476 | return NULL; |
477 | } |
478 | |
479 | static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) |
480 | { |
481 | if (*gridp == msg_grid.handle) { |
482 | rowp += msg_grid_pos; |
483 | *gridp = DEFAULT_GRID_HANDLE; |
484 | } else if (*gridp > 1) { |
485 | win_T *wp = get_win_by_grid_handle(*gridp); |
486 | if (wp && wp->w_grid.chars |
487 | && !(wp->w_floating && !wp->w_float_config.focusable)) { |
488 | *rowp = MIN(*rowp, wp->w_grid.Rows-1); |
489 | *colp = MIN(*colp, wp->w_grid.Columns-1); |
490 | return wp; |
491 | } |
492 | } else if (*gridp == 0) { |
493 | ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp); |
494 | FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { |
495 | if (&wp->w_grid != grid) { |
496 | continue; |
497 | } |
498 | *gridp = grid->handle; |
499 | *rowp -= grid->comp_row; |
500 | *colp -= grid->comp_col; |
501 | return wp; |
502 | } |
503 | |
504 | // no float found, click on the default grid |
505 | // TODO(bfredl): grid can be &pum_grid, allow select pum items by mouse? |
506 | *gridp = DEFAULT_GRID_HANDLE; |
507 | } |
508 | return NULL; |
509 | } |
510 | |
511 | /* |
512 | * setmouse() - switch mouse on/off depending on current mode and 'mouse' |
513 | */ |
514 | void setmouse(void) |
515 | { |
516 | int checkfor; |
517 | |
518 | ui_cursor_shape(); |
519 | |
520 | /* be quick when mouse is off */ |
521 | if (*p_mouse == NUL) |
522 | return; |
523 | |
524 | if (VIsual_active) |
525 | checkfor = MOUSE_VISUAL; |
526 | else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE) |
527 | checkfor = MOUSE_RETURN; |
528 | else if (State & INSERT) |
529 | checkfor = MOUSE_INSERT; |
530 | else if (State & CMDLINE) |
531 | checkfor = MOUSE_COMMAND; |
532 | else if (State == CONFIRM || State == EXTERNCMD) |
533 | checkfor = ' '; /* don't use mouse for ":confirm" or ":!cmd" */ |
534 | else |
535 | checkfor = MOUSE_NORMAL; /* assume normal mode */ |
536 | |
537 | if (mouse_has(checkfor)) { |
538 | ui_call_mouse_on(); |
539 | } else { |
540 | ui_call_mouse_off(); |
541 | } |
542 | } |
543 | |
544 | /* |
545 | * Return true if |
546 | * - "c" is in 'mouse', or |
547 | * - 'a' is in 'mouse' and "c" is in MOUSE_A, or |
548 | * - the current buffer is a help file and 'h' is in 'mouse' and we are in a |
549 | * normal editing mode (not at hit-return message). |
550 | */ |
551 | int mouse_has(int c) |
552 | { |
553 | for (char_u *p = p_mouse; *p; ++p) |
554 | switch (*p) { |
555 | case 'a': if (vim_strchr((char_u *)MOUSE_A, c) != NULL) |
556 | return true; |
557 | break; |
558 | case MOUSE_HELP: if (c != MOUSE_RETURN && curbuf->b_help) |
559 | return true; |
560 | break; |
561 | default: if (c == *p) return true; break; |
562 | } |
563 | return false; |
564 | } |
565 | |
566 | // Set orig_topline. Used when jumping to another window, so that a double |
567 | // click still works. |
568 | void set_mouse_topline(win_T *wp) |
569 | { |
570 | orig_topline = wp->w_topline; |
571 | orig_topfill = wp->w_topfill; |
572 | } |
573 | |
574 | /// |
575 | /// Return length of line "lnum" for horizontal scrolling. |
576 | /// |
577 | static colnr_T scroll_line_len(linenr_T lnum) |
578 | { |
579 | colnr_T col = 0; |
580 | char_u *line = ml_get(lnum); |
581 | if (*line != NUL) { |
582 | for (;;) { |
583 | int numchar = chartabsize(line, col); |
584 | MB_PTR_ADV(line); |
585 | if (*line == NUL) { // don't count the last character |
586 | break; |
587 | } |
588 | col += numchar; |
589 | } |
590 | } |
591 | return col; |
592 | } |
593 | |
594 | /// |
595 | /// Find longest visible line number. |
596 | /// |
597 | static linenr_T find_longest_lnum(void) |
598 | { |
599 | linenr_T ret = 0; |
600 | |
601 | // Calculate maximum for horizontal scrollbar. Check for reasonable |
602 | // line numbers, topline and botline can be invalid when displaying is |
603 | // postponed. |
604 | if (curwin->w_topline <= curwin->w_cursor.lnum |
605 | && curwin->w_botline > curwin->w_cursor.lnum |
606 | && curwin->w_botline <= curbuf->b_ml.ml_line_count + 1) { |
607 | long max = 0; |
608 | |
609 | // Use maximum of all visible lines. Remember the lnum of the |
610 | // longest line, closest to the cursor line. Used when scrolling |
611 | // below. |
612 | for (linenr_T lnum = curwin->w_topline; lnum < curwin->w_botline; lnum++) { |
613 | colnr_T len = scroll_line_len(lnum); |
614 | if (len > (colnr_T)max) { |
615 | max = len; |
616 | ret = lnum; |
617 | } else if (len == (colnr_T)max |
618 | && abs((int)(lnum - curwin->w_cursor.lnum)) |
619 | < abs((int)(ret - curwin->w_cursor.lnum))) { |
620 | ret = lnum; |
621 | } |
622 | } |
623 | } else { |
624 | // Use cursor line only. |
625 | ret = curwin->w_cursor.lnum; |
626 | } |
627 | |
628 | return ret; |
629 | } |
630 | |
631 | /// |
632 | /// Do a horizontal scroll. Return TRUE if the cursor moved, FALSE otherwise. |
633 | /// |
634 | bool mouse_scroll_horiz(int dir) |
635 | { |
636 | if (curwin->w_p_wrap) { |
637 | return false; |
638 | } |
639 | |
640 | int step = 6; |
641 | if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { |
642 | step = curwin->w_width_inner; |
643 | } |
644 | |
645 | int leftcol = curwin->w_leftcol + (dir == MSCR_RIGHT ? -step : +step); |
646 | if (leftcol < 0) { |
647 | leftcol = 0; |
648 | } |
649 | |
650 | if (curwin->w_leftcol == leftcol) { |
651 | return false; |
652 | } |
653 | |
654 | curwin->w_leftcol = (colnr_T)leftcol; |
655 | |
656 | // When the line of the cursor is too short, move the cursor to the |
657 | // longest visible line. |
658 | if (!virtual_active() |
659 | && (colnr_T)leftcol > scroll_line_len(curwin->w_cursor.lnum)) { |
660 | curwin->w_cursor.lnum = find_longest_lnum(); |
661 | curwin->w_cursor.col = 0; |
662 | } |
663 | |
664 | return leftcol_changed(); |
665 | } |
666 | |
667 | /// Adjusts the clicked column position when 'conceallevel' > 0 |
668 | static int mouse_adjust_click(win_T *wp, int row, int col) |
669 | { |
670 | if (!(wp->w_p_cole > 0 && curbuf->b_p_smc > 0 |
671 | && wp->w_leftcol < curbuf->b_p_smc && conceal_cursor_line(wp))) { |
672 | return col; |
673 | } |
674 | |
675 | // `col` is the position within the current line that is highlighted by the |
676 | // cursor without consideration for concealed characters. The current line is |
677 | // scanned *up to* `col`, nudging it left or right when concealed characters |
678 | // are encountered. |
679 | // |
680 | // chartabsize() is used to keep track of the virtual column position relative |
681 | // to the line's bytes. For example: if col == 9 and the line starts with a |
682 | // tab that's 8 columns wide, we would want the cursor to be highlighting the |
683 | // second byte, not the ninth. |
684 | |
685 | linenr_T lnum = wp->w_cursor.lnum; |
686 | char_u *line = ml_get(lnum); |
687 | char_u *ptr = line; |
688 | char_u *ptr_end; |
689 | char_u *ptr_row_offset = line; // Where we begin adjusting `ptr_end` |
690 | |
691 | // Find the offset where scanning should begin. |
692 | int offset = wp->w_leftcol; |
693 | if (row > 0) { |
694 | offset += row * (wp->w_width_inner - win_col_off(wp) - win_col_off2(wp) - |
695 | wp->w_leftcol + wp->w_skipcol); |
696 | } |
697 | |
698 | int vcol; |
699 | |
700 | if (offset) { |
701 | // Skip everything up to an offset since nvim takes care of displaying the |
702 | // correct portion of the line when horizontally scrolling. |
703 | // When 'wrap' is enabled, only the row (of the wrapped line) needs to be |
704 | // checked for concealed characters. |
705 | vcol = 0; |
706 | while (vcol < offset && *ptr != NUL) { |
707 | vcol += chartabsize(ptr, vcol); |
708 | ptr += utfc_ptr2len(ptr); |
709 | } |
710 | |
711 | ptr_row_offset = ptr; |
712 | } |
713 | |
714 | // Align `ptr_end` with `col` |
715 | vcol = offset; |
716 | ptr_end = ptr_row_offset; |
717 | while (vcol < col && *ptr_end != NUL) { |
718 | vcol += chartabsize(ptr_end, vcol); |
719 | ptr_end += utfc_ptr2len(ptr_end); |
720 | } |
721 | |
722 | int matchid; |
723 | int prev_matchid; |
724 | int nudge = 0; |
725 | int cwidth = 0; |
726 | |
727 | vcol = offset; |
728 | |
729 | #define incr() nudge++; ptr_end += utfc_ptr2len(ptr_end) |
730 | #define decr() nudge--; ptr_end -= utfc_ptr2len(ptr_end) |
731 | |
732 | while (ptr < ptr_end && *ptr != NUL) { |
733 | cwidth = chartabsize(ptr, vcol); |
734 | vcol += cwidth; |
735 | if (cwidth > 1 && *ptr == '\t' && nudge > 0) { |
736 | // A tab will "absorb" any previous adjustments. |
737 | cwidth = MIN(cwidth, nudge); |
738 | while (cwidth > 0) { |
739 | decr(); |
740 | cwidth--; |
741 | } |
742 | } |
743 | |
744 | matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line)); |
745 | if (matchid != 0) { |
746 | if (wp->w_p_cole == 3) { |
747 | incr(); |
748 | } else { |
749 | if (!(row > 0 && ptr == ptr_row_offset) |
750 | && (wp->w_p_cole == 1 || (wp->w_p_cole == 2 |
751 | && (wp->w_p_lcs_chars.conceal != NUL |
752 | || syn_get_sub_char() != NUL)))) { |
753 | // At least one placeholder character will be displayed. |
754 | decr(); |
755 | } |
756 | |
757 | prev_matchid = matchid; |
758 | |
759 | while (prev_matchid == matchid && *ptr != NUL) { |
760 | incr(); |
761 | ptr += utfc_ptr2len(ptr); |
762 | matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line)); |
763 | } |
764 | |
765 | continue; |
766 | } |
767 | } |
768 | |
769 | ptr += utfc_ptr2len(ptr); |
770 | } |
771 | |
772 | return col + nudge; |
773 | } |
774 | |