blob: c2bde6d16b0e5861bae5af5f194d05dbe53d717f [file] [log] [blame]
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001/* vi:set ts=8 sts=4 sw=4 noet:
2 *
3 * VIM - Vi IMproved by Bram Moolenaar
4 *
5 * Do ":help uganda" in Vim to read copying and usage conditions.
6 * Do ":help credits" in Vim to see a list of people who contributed.
7 * See README.txt for an overview of the Vim source code.
8 */
9
10/*
11 * mouse.c: mouse handling functions
12 */
13
14#include "vim.h"
15
Bram Moolenaar85c35022019-11-22 22:21:59 +010016#ifdef CHECK_DOUBLE_CLICK
17/*
18 * Return the duration from t1 to t2 in milliseconds.
19 */
20 static long
21time_diff_ms(struct timeval *t1, struct timeval *t2)
22{
23 // This handles wrapping of tv_usec correctly without any special case.
24 // Example of 2 pairs (tv_sec, tv_usec) with a duration of 5 ms:
25 // t1 = (1, 998000) t2 = (2, 3000) gives:
26 // (2 - 1) * 1000 + (3000 - 998000) / 1000 -> 5 ms.
27 return (t2->tv_sec - t1->tv_sec) * 1000
28 + (t2->tv_usec - t1->tv_usec) / 1000;
29}
30#endif
31
Bram Moolenaarb20b9e12019-09-21 20:48:04 +020032/*
33 * Get class of a character for selection: same class means same word.
34 * 0: blank
35 * 1: punctuation groups
36 * 2: normal word character
37 * >2: multi-byte word character.
38 */
39 static int
40get_mouse_class(char_u *p)
41{
42 int c;
43
44 if (has_mbyte && MB_BYTE2LEN(p[0]) > 1)
45 return mb_get_class(p);
46
47 c = *p;
48 if (c == ' ' || c == '\t')
49 return 0;
50
51 if (vim_iswordc(c))
52 return 2;
53
54 // There are a few special cases where we want certain combinations of
55 // characters to be considered as a single word. These are things like
56 // "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each
57 // character is in its own class.
58 if (c != NUL && vim_strchr((char_u *)"-+*/%<>&|^!=", c) != NULL)
59 return 1;
60 return c;
61}
62
63/*
64 * Move "pos" back to the start of the word it's in.
65 */
66 static void
67find_start_of_word(pos_T *pos)
68{
69 char_u *line;
70 int cclass;
71 int col;
72
73 line = ml_get(pos->lnum);
74 cclass = get_mouse_class(line + pos->col);
75
76 while (pos->col > 0)
77 {
78 col = pos->col - 1;
79 col -= (*mb_head_off)(line, line + col);
80 if (get_mouse_class(line + col) != cclass)
81 break;
82 pos->col = col;
83 }
84}
85
86/*
87 * Move "pos" forward to the end of the word it's in.
88 * When 'selection' is "exclusive", the position is just after the word.
89 */
90 static void
91find_end_of_word(pos_T *pos)
92{
93 char_u *line;
94 int cclass;
95 int col;
96
97 line = ml_get(pos->lnum);
98 if (*p_sel == 'e' && pos->col > 0)
99 {
100 --pos->col;
101 pos->col -= (*mb_head_off)(line, line + pos->col);
102 }
103 cclass = get_mouse_class(line + pos->col);
104 while (line[pos->col] != NUL)
105 {
106 col = pos->col + (*mb_ptr2len)(line + pos->col);
107 if (get_mouse_class(line + col) != cclass)
108 {
109 if (*p_sel == 'e')
110 pos->col = col;
111 break;
112 }
113 pos->col = col;
114 }
115}
116
Bram Moolenaar910c3782019-09-22 14:11:50 +0200117#if defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_GTK) \
118 || defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MSWIN) \
Bram Moolenaar097148e2020-08-11 21:58:20 +0200119 || defined(FEAT_GUI_PHOTON) \
Bram Moolenaar910c3782019-09-22 14:11:50 +0200120 || defined(FEAT_TERM_POPUP_MENU)
121# define USE_POPUP_SETPOS
122# define NEED_VCOL2COL
123
124/*
125 * Translate window coordinates to buffer position without any side effects
126 */
127 static int
128get_fpos_of_mouse(pos_T *mpos)
129{
130 win_T *wp;
131 int row = mouse_row;
132 int col = mouse_col;
133
134 if (row < 0 || col < 0) // check if it makes sense
135 return IN_UNKNOWN;
136
137 // find the window where the row is in
138 wp = mouse_find_win(&row, &col, FAIL_POPUP);
139 if (wp == NULL)
140 return IN_UNKNOWN;
141 // winpos and height may change in win_enter()!
142 if (row >= wp->w_height) // In (or below) status line
143 return IN_STATUS_LINE;
144 if (col >= wp->w_width) // In vertical separator line
145 return IN_SEP_LINE;
146
147 if (wp != curwin)
148 return IN_UNKNOWN;
149
150 // compute the position in the buffer line from the posn on the screen
151 if (mouse_comp_pos(curwin, &row, &col, &mpos->lnum, NULL))
152 return IN_STATUS_LINE; // past bottom
153
154 mpos->col = vcol2col(wp, mpos->lnum, col);
155
156 if (mpos->col > 0)
157 --mpos->col;
158 mpos->coladd = 0;
159 return IN_BUFFER;
160}
161#endif
162
Bram Moolenaarb20b9e12019-09-21 20:48:04 +0200163/*
164 * Do the appropriate action for the current mouse click in the current mode.
165 * Not used for Command-line mode.
166 *
167 * Normal and Visual Mode:
168 * event modi- position visual change action
169 * fier cursor window
170 * left press - yes end yes
171 * left press C yes end yes "^]" (2)
172 * left press S yes end (popup: extend) yes "*" (2)
173 * left drag - yes start if moved no
174 * left relse - yes start if moved no
175 * middle press - yes if not active no put register
176 * middle press - yes if active no yank and put
177 * right press - yes start or extend yes
178 * right press S yes no change yes "#" (2)
179 * right drag - yes extend no
180 * right relse - yes extend no
181 *
182 * Insert or Replace Mode:
183 * event modi- position visual change action
184 * fier cursor window
185 * left press - yes (cannot be active) yes
186 * left press C yes (cannot be active) yes "CTRL-O^]" (2)
187 * left press S yes (cannot be active) yes "CTRL-O*" (2)
188 * left drag - yes start or extend (1) no CTRL-O (1)
189 * left relse - yes start or extend (1) no CTRL-O (1)
190 * middle press - no (cannot be active) no put register
191 * right press - yes start or extend yes CTRL-O
192 * right press S yes (cannot be active) yes "CTRL-O#" (2)
193 *
194 * (1) only if mouse pointer moved since press
195 * (2) only if click is in same buffer
196 *
197 * Return TRUE if start_arrow() should be called for edit mode.
198 */
199 int
200do_mouse(
201 oparg_T *oap, // operator argument, can be NULL
202 int c, // K_LEFTMOUSE, etc
203 int dir, // Direction to 'put' if necessary
204 long count,
205 int fixindent) // PUT_FIXINDENT if fixing indent necessary
206{
207 static int do_always = FALSE; // ignore 'mouse' setting next time
208 static int got_click = FALSE; // got a click some time back
209
210 int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT
211 int is_click = FALSE; // If FALSE it's a drag or release event
212 int is_drag = FALSE; // If TRUE it's a drag event
213 int jump_flags = 0; // flags for jump_to_mouse()
214 pos_T start_visual;
215 int moved; // Has cursor moved?
216 int in_status_line; // mouse in status line
217 static int in_tab_line = FALSE; // mouse clicked in tab line
218 int in_sep_line; // mouse in vertical separator line
219 int c1, c2;
220#if defined(FEAT_FOLDING)
221 pos_T save_cursor;
222#endif
223 win_T *old_curwin = curwin;
224 static pos_T orig_cursor;
225 colnr_T leftcol, rightcol;
226 pos_T end_visual;
227 int diff;
228 int old_active = VIsual_active;
229 int old_mode = VIsual_mode;
230 int regname;
231
232#if defined(FEAT_FOLDING)
233 save_cursor = curwin->w_cursor;
234#endif
235
236 // When GUI is active, always recognize mouse events, otherwise:
237 // - Ignore mouse event in normal mode if 'mouse' doesn't include 'n'.
238 // - Ignore mouse event in visual mode if 'mouse' doesn't include 'v'.
239 // - For command line and insert mode 'mouse' is checked before calling
240 // do_mouse().
241 if (do_always)
242 do_always = FALSE;
243 else
244#ifdef FEAT_GUI
245 if (!gui.in_use)
246#endif
247 {
248 if (VIsual_active)
249 {
250 if (!mouse_has(MOUSE_VISUAL))
251 return FALSE;
252 }
253 else if (State == NORMAL && !mouse_has(MOUSE_NORMAL))
254 return FALSE;
255 }
256
257 for (;;)
258 {
259 which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag);
260 if (is_drag)
261 {
262 // If the next character is the same mouse event then use that
263 // one. Speeds up dragging the status line.
264 if (vpeekc() != NUL)
265 {
266 int nc;
267 int save_mouse_row = mouse_row;
268 int save_mouse_col = mouse_col;
269
270 // Need to get the character, peeking doesn't get the actual
271 // one.
272 nc = safe_vgetc();
273 if (c == nc)
274 continue;
275 vungetc(nc);
276 mouse_row = save_mouse_row;
277 mouse_col = save_mouse_col;
278 }
279 }
280 break;
281 }
282
283 if (c == K_MOUSEMOVE)
284 {
285 // Mouse moved without a button pressed.
286#ifdef FEAT_BEVAL_TERM
287 ui_may_remove_balloon();
288 if (p_bevalterm)
289 {
290 profile_setlimit(p_bdlay, &bevalexpr_due);
291 bevalexpr_due_set = TRUE;
292 }
293#endif
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +0100294#ifdef FEAT_PROP_POPUP
Bram Moolenaarb20b9e12019-09-21 20:48:04 +0200295 popup_handle_mouse_moved();
296#endif
297 return FALSE;
298 }
299
300#ifdef FEAT_MOUSESHAPE
301 // May have stopped dragging the status or separator line. The pointer is
302 // most likely still on the status or separator line.
303 if (!is_drag && drag_status_line)
304 {
305 drag_status_line = FALSE;
306 update_mouseshape(SHAPE_IDX_STATUS);
307 }
308 if (!is_drag && drag_sep_line)
309 {
310 drag_sep_line = FALSE;
311 update_mouseshape(SHAPE_IDX_VSEP);
312 }
313#endif
314
315 // Ignore drag and release events if we didn't get a click.
316 if (is_click)
317 got_click = TRUE;
318 else
319 {
320 if (!got_click) // didn't get click, ignore
321 return FALSE;
322 if (!is_drag) // release, reset got_click
323 {
324 got_click = FALSE;
325 if (in_tab_line)
326 {
327 in_tab_line = FALSE;
328 return FALSE;
329 }
330 }
331 }
332
333 // CTRL right mouse button does CTRL-T
334 if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT)
335 {
336 if (State & INSERT)
337 stuffcharReadbuff(Ctrl_O);
338 if (count > 1)
339 stuffnumReadbuff(count);
340 stuffcharReadbuff(Ctrl_T);
341 got_click = FALSE; // ignore drag&release now
342 return FALSE;
343 }
344
345 // CTRL only works with left mouse button
346 if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT)
347 return FALSE;
348
349 // When a modifier is down, ignore drag and release events, as well as
350 // multiple clicks and the middle mouse button.
351 // Accept shift-leftmouse drags when 'mousemodel' is "popup.*".
352 if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT
353 | MOD_MASK_META))
354 && (!is_click
355 || (mod_mask & MOD_MASK_MULTI_CLICK)
356 || which_button == MOUSE_MIDDLE)
357 && !((mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))
358 && mouse_model_popup()
359 && which_button == MOUSE_LEFT)
360 && !((mod_mask & MOD_MASK_ALT)
361 && !mouse_model_popup()
362 && which_button == MOUSE_RIGHT)
363 )
364 return FALSE;
365
366 // If the button press was used as the movement command for an operator
367 // (eg "d<MOUSE>"), or it is the middle button that is held down, ignore
368 // drag/release events.
369 if (!is_click && which_button == MOUSE_MIDDLE)
370 return FALSE;
371
372 if (oap != NULL)
373 regname = oap->regname;
374 else
375 regname = 0;
376
377 // Middle mouse button does a 'put' of the selected text
378 if (which_button == MOUSE_MIDDLE)
379 {
380 if (State == NORMAL)
381 {
382 // If an operator was pending, we don't know what the user wanted
383 // to do. Go back to normal mode: Clear the operator and beep().
384 if (oap != NULL && oap->op_type != OP_NOP)
385 {
386 clearopbeep(oap);
387 return FALSE;
388 }
389
390 // If visual was active, yank the highlighted text and put it
391 // before the mouse pointer position.
392 // In Select mode replace the highlighted text with the clipboard.
393 if (VIsual_active)
394 {
395 if (VIsual_select)
396 {
397 stuffcharReadbuff(Ctrl_G);
398 stuffReadbuff((char_u *)"\"+p");
399 }
400 else
401 {
402 stuffcharReadbuff('y');
403 stuffcharReadbuff(K_MIDDLEMOUSE);
404 }
405 do_always = TRUE; // ignore 'mouse' setting next time
406 return FALSE;
407 }
408 // The rest is below jump_to_mouse()
409 }
410
411 else if ((State & INSERT) == 0)
412 return FALSE;
413
414 // Middle click in insert mode doesn't move the mouse, just insert the
415 // contents of a register. '.' register is special, can't insert that
416 // with do_put().
417 // Also paste at the cursor if the current mode isn't in 'mouse' (only
418 // happens for the GUI).
419 if ((State & INSERT) || !mouse_has(MOUSE_NORMAL))
420 {
421 if (regname == '.')
422 insert_reg(regname, TRUE);
423 else
424 {
425#ifdef FEAT_CLIPBOARD
426 if (clip_star.available && regname == 0)
427 regname = '*';
428#endif
429 if ((State & REPLACE_FLAG) && !yank_register_mline(regname))
430 insert_reg(regname, TRUE);
431 else
432 {
Bram Moolenaarc3516f72020-09-08 22:45:35 +0200433 do_put(regname, NULL, BACKWARD, 1L,
434 fixindent | PUT_CURSEND);
Bram Moolenaarb20b9e12019-09-21 20:48:04 +0200435
436 // Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r
437 AppendCharToRedobuff(Ctrl_R);
438 AppendCharToRedobuff(fixindent ? Ctrl_P : Ctrl_O);
439 AppendCharToRedobuff(regname == 0 ? '"' : regname);
440 }
441 }
442 return FALSE;
443 }
444 }
445
446 // When dragging or button-up stay in the same window.
447 if (!is_click)
448 jump_flags |= MOUSE_FOCUS | MOUSE_DID_MOVE;
449
450 start_visual.lnum = 0;
451
452 // Check for clicking in the tab page line.
453 if (mouse_row == 0 && firstwin->w_winrow > 0)
454 {
455 if (is_drag)
456 {
457 if (in_tab_line)
458 {
459 c1 = TabPageIdxs[mouse_col];
460 tabpage_move(c1 <= 0 ? 9999 : c1 < tabpage_index(curtab)
461 ? c1 - 1 : c1);
462 }
463 return FALSE;
464 }
465
466 // click in a tab selects that tab page
467 if (is_click
468# ifdef FEAT_CMDWIN
469 && cmdwin_type == 0
470# endif
471 && mouse_col < Columns)
472 {
473 in_tab_line = TRUE;
474 c1 = TabPageIdxs[mouse_col];
475 if (c1 >= 0)
476 {
477 if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)
478 {
479 // double click opens new page
Bram Moolenaar4f3c57f2021-06-03 22:11:08 +0200480 end_visual_mode_keep_button();
Bram Moolenaarb20b9e12019-09-21 20:48:04 +0200481 tabpage_new();
482 tabpage_move(c1 == 0 ? 9999 : c1 - 1);
483 }
484 else
485 {
486 // Go to specified tab page, or next one if not clicking
487 // on a label.
488 goto_tabpage(c1);
489
490 // It's like clicking on the status line of a window.
491 if (curwin != old_curwin)
Bram Moolenaar4f3c57f2021-06-03 22:11:08 +0200492 end_visual_mode_keep_button();
Bram Moolenaarb20b9e12019-09-21 20:48:04 +0200493 }
494 }
495 else
496 {
497 tabpage_T *tp;
498
499 // Close the current or specified tab page.
500 if (c1 == -999)
501 tp = curtab;
502 else
503 tp = find_tabpage(-c1);
504 if (tp == curtab)
505 {
506 if (first_tabpage->tp_next != NULL)
507 tabpage_close(FALSE);
508 }
509 else if (tp != NULL)
510 tabpage_close_other(tp, FALSE);
511 }
512 }
513 return TRUE;
514 }
515 else if (is_drag && in_tab_line)
516 {
517 c1 = TabPageIdxs[mouse_col];
518 tabpage_move(c1 <= 0 ? 9999 : c1 - 1);
519 return FALSE;
520 }
521
522 // When 'mousemodel' is "popup" or "popup_setpos", translate mouse events:
523 // right button up -> pop-up menu
524 // shift-left button -> right button
525 // alt-left button -> alt-right button
526 if (mouse_model_popup())
527 {
528 if (which_button == MOUSE_RIGHT
529 && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)))
530 {
Bram Moolenaar910c3782019-09-22 14:11:50 +0200531#ifdef USE_POPUP_SETPOS
Bram Moolenaarb20b9e12019-09-21 20:48:04 +0200532# ifdef FEAT_GUI
533 if (gui.in_use)
534 {
535# if defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_GTK) \
Bram Moolenaar097148e2020-08-11 21:58:20 +0200536 || defined(FEAT_GUI_PHOTON)
Bram Moolenaarb20b9e12019-09-21 20:48:04 +0200537 if (!is_click)
538 // Ignore right button release events, only shows the popup
539 // menu on the button down event.
540 return FALSE;
541# endif
Bram Moolenaarb3f74062020-02-26 16:16:53 +0100542# if defined(FEAT_GUI_ATHENA) || defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_HAIKU)
Bram Moolenaarb20b9e12019-09-21 20:48:04 +0200543 if (is_click || is_drag)
544 // Ignore right button down and drag mouse events. Windows
545 // only shows the popup menu on the button up event.
546 return FALSE;
547# endif
548 }
549# endif
550# if defined(FEAT_GUI) && defined(FEAT_TERM_POPUP_MENU)
551 else
552# endif
553# if defined(FEAT_TERM_POPUP_MENU)
554 if (!is_click)
555 // Ignore right button release events, only shows the popup
556 // menu on the button down event.
557 return FALSE;
558#endif
559
560 jump_flags = 0;
561 if (STRCMP(p_mousem, "popup_setpos") == 0)
562 {
563 // First set the cursor position before showing the popup
564 // menu.
565 if (VIsual_active)
566 {
567 pos_T m_pos;
568
569 // set MOUSE_MAY_STOP_VIS if we are outside the
570 // selection or the current window (might have false
571 // negative here)
572 if (mouse_row < curwin->w_winrow
573 || mouse_row
574 > (curwin->w_winrow + curwin->w_height))
575 jump_flags = MOUSE_MAY_STOP_VIS;
576 else if (get_fpos_of_mouse(&m_pos) != IN_BUFFER)
577 jump_flags = MOUSE_MAY_STOP_VIS;
578 else
579 {
580 if ((LT_POS(curwin->w_cursor, VIsual)
581 && (LT_POS(m_pos, curwin->w_cursor)
582 || LT_POS(VIsual, m_pos)))
583 || (LT_POS(VIsual, curwin->w_cursor)
584 && (LT_POS(m_pos, VIsual)
585 || LT_POS(curwin->w_cursor, m_pos))))
586 {
587 jump_flags = MOUSE_MAY_STOP_VIS;
588 }
589 else if (VIsual_mode == Ctrl_V)
590 {
591 getvcols(curwin, &curwin->w_cursor, &VIsual,
592 &leftcol, &rightcol);
593 getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL);
594 if (m_pos.col < leftcol || m_pos.col > rightcol)
595 jump_flags = MOUSE_MAY_STOP_VIS;
596 }
597 }
598 }
599 else
600 jump_flags = MOUSE_MAY_STOP_VIS;
601 }
602 if (jump_flags)
603 {
604 jump_flags = jump_to_mouse(jump_flags, NULL, which_button);
605 update_curbuf(VIsual_active ? INVERTED : VALID);
606 setcursor();
607 out_flush(); // Update before showing popup menu
608 }
609# ifdef FEAT_MENU
610 show_popupmenu();
611 got_click = FALSE; // ignore release events
612# endif
613 return (jump_flags & CURSOR_MOVED) != 0;
614#else
615 return FALSE;
616#endif
617 }
618 if (which_button == MOUSE_LEFT
619 && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)))
620 {
621 which_button = MOUSE_RIGHT;
622 mod_mask &= ~MOD_MASK_SHIFT;
623 }
624 }
625
626 if ((State & (NORMAL | INSERT))
627 && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)))
628 {
629 if (which_button == MOUSE_LEFT)
630 {
631 if (is_click)
632 {
633 // stop Visual mode for a left click in a window, but not when
634 // on a status line
635 if (VIsual_active)
636 jump_flags |= MOUSE_MAY_STOP_VIS;
637 }
638 else if (mouse_has(MOUSE_VISUAL))
639 jump_flags |= MOUSE_MAY_VIS;
640 }
641 else if (which_button == MOUSE_RIGHT)
642 {
643 if (is_click && VIsual_active)
644 {
645 // Remember the start and end of visual before moving the
646 // cursor.
647 if (LT_POS(curwin->w_cursor, VIsual))
648 {
649 start_visual = curwin->w_cursor;
650 end_visual = VIsual;
651 }
652 else
653 {
654 start_visual = VIsual;
655 end_visual = curwin->w_cursor;
656 }
657 }
658 jump_flags |= MOUSE_FOCUS;
659 if (mouse_has(MOUSE_VISUAL))
660 jump_flags |= MOUSE_MAY_VIS;
661 }
662 }
663
664 // If an operator is pending, ignore all drags and releases until the
665 // next mouse click.
666 if (!is_drag && oap != NULL && oap->op_type != OP_NOP)
667 {
668 got_click = FALSE;
669 oap->motion_type = MCHAR;
670 }
671
672 // When releasing the button let jump_to_mouse() know.
673 if (!is_click && !is_drag)
674 jump_flags |= MOUSE_RELEASED;
675
676 // JUMP!
677 jump_flags = jump_to_mouse(jump_flags,
678 oap == NULL ? NULL : &(oap->inclusive), which_button);
679
680#ifdef FEAT_MENU
681 // A click in the window toolbar has no side effects.
682 if (jump_flags & MOUSE_WINBAR)
683 return FALSE;
684#endif
685 moved = (jump_flags & CURSOR_MOVED);
686 in_status_line = (jump_flags & IN_STATUS_LINE);
687 in_sep_line = (jump_flags & IN_SEP_LINE);
688
689#ifdef FEAT_NETBEANS_INTG
690 if (isNetbeansBuffer(curbuf)
691 && !(jump_flags & (IN_STATUS_LINE | IN_SEP_LINE)))
692 {
693 int key = KEY2TERMCAP1(c);
694
695 if (key == (int)KE_LEFTRELEASE || key == (int)KE_MIDDLERELEASE
696 || key == (int)KE_RIGHTRELEASE)
697 netbeans_button_release(which_button);
698 }
699#endif
700
701 // When jumping to another window, clear a pending operator. That's a bit
702 // friendlier than beeping and not jumping to that window.
703 if (curwin != old_curwin && oap != NULL && oap->op_type != OP_NOP)
704 clearop(oap);
705
706#ifdef FEAT_FOLDING
707 if (mod_mask == 0
708 && !is_drag
709 && (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN))
710 && which_button == MOUSE_LEFT)
711 {
712 // open or close a fold at this line
713 if (jump_flags & MOUSE_FOLD_OPEN)
714 openFold(curwin->w_cursor.lnum, 1L);
715 else
716 closeFold(curwin->w_cursor.lnum, 1L);
717 // don't move the cursor if still in the same window
718 if (curwin == old_curwin)
719 curwin->w_cursor = save_cursor;
720 }
721#endif
722
723#if defined(FEAT_CLIPBOARD) && defined(FEAT_CMDWIN)
724 if ((jump_flags & IN_OTHER_WIN) && !VIsual_active && clip_star.available)
725 {
726 clip_modeless(which_button, is_click, is_drag);
727 return FALSE;
728 }
729#endif
730
731 // Set global flag that we are extending the Visual area with mouse
732 // dragging; temporarily minimize 'scrolloff'.
733 if (VIsual_active && is_drag && get_scrolloff_value())
734 {
735 // In the very first line, allow scrolling one line
736 if (mouse_row == 0)
737 mouse_dragging = 2;
738 else
739 mouse_dragging = 1;
740 }
741
742 // When dragging the mouse above the window, scroll down.
743 if (is_drag && mouse_row < 0 && !in_status_line)
744 {
745 scroll_redraw(FALSE, 1L);
746 mouse_row = 0;
747 }
748
749 if (start_visual.lnum) // right click in visual mode
750 {
751 // When ALT is pressed make Visual mode blockwise.
752 if (mod_mask & MOD_MASK_ALT)
753 VIsual_mode = Ctrl_V;
754
755 // In Visual-block mode, divide the area in four, pick up the corner
756 // that is in the quarter that the cursor is in.
757 if (VIsual_mode == Ctrl_V)
758 {
759 getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol);
760 if (curwin->w_curswant > (leftcol + rightcol) / 2)
761 end_visual.col = leftcol;
762 else
763 end_visual.col = rightcol;
764 if (curwin->w_cursor.lnum >=
765 (start_visual.lnum + end_visual.lnum) / 2)
766 end_visual.lnum = start_visual.lnum;
767
768 // move VIsual to the right column
769 start_visual = curwin->w_cursor; // save the cursor pos
770 curwin->w_cursor = end_visual;
771 coladvance(end_visual.col);
772 VIsual = curwin->w_cursor;
773 curwin->w_cursor = start_visual; // restore the cursor
774 }
775 else
776 {
777 // If the click is before the start of visual, change the start.
778 // If the click is after the end of visual, change the end. If
779 // the click is inside the visual, change the closest side.
780 if (LT_POS(curwin->w_cursor, start_visual))
781 VIsual = end_visual;
782 else if (LT_POS(end_visual, curwin->w_cursor))
783 VIsual = start_visual;
784 else
785 {
786 // In the same line, compare column number
787 if (end_visual.lnum == start_visual.lnum)
788 {
789 if (curwin->w_cursor.col - start_visual.col >
790 end_visual.col - curwin->w_cursor.col)
791 VIsual = start_visual;
792 else
793 VIsual = end_visual;
794 }
795
796 // In different lines, compare line number
797 else
798 {
799 diff = (curwin->w_cursor.lnum - start_visual.lnum) -
800 (end_visual.lnum - curwin->w_cursor.lnum);
801
802 if (diff > 0) // closest to end
803 VIsual = start_visual;
804 else if (diff < 0) // closest to start
805 VIsual = end_visual;
806 else // in the middle line
807 {
808 if (curwin->w_cursor.col <
809 (start_visual.col + end_visual.col) / 2)
810 VIsual = end_visual;
811 else
812 VIsual = start_visual;
813 }
814 }
815 }
816 }
817 }
818 // If Visual mode started in insert mode, execute "CTRL-O"
819 else if ((State & INSERT) && VIsual_active)
820 stuffcharReadbuff(Ctrl_O);
821
822 // Middle mouse click: Put text before cursor.
823 if (which_button == MOUSE_MIDDLE)
824 {
825#ifdef FEAT_CLIPBOARD
826 if (clip_star.available && regname == 0)
827 regname = '*';
828#endif
829 if (yank_register_mline(regname))
830 {
831 if (mouse_past_bottom)
832 dir = FORWARD;
833 }
834 else if (mouse_past_eol)
835 dir = FORWARD;
836
837 if (fixindent)
838 {
839 c1 = (dir == BACKWARD) ? '[' : ']';
840 c2 = 'p';
841 }
842 else
843 {
844 c1 = (dir == FORWARD) ? 'p' : 'P';
845 c2 = NUL;
846 }
847 prep_redo(regname, count, NUL, c1, NUL, c2, NUL);
848
849 // Remember where the paste started, so in edit() Insstart can be set
850 // to this position
851 if (restart_edit != 0)
852 where_paste_started = curwin->w_cursor;
Bram Moolenaarc3516f72020-09-08 22:45:35 +0200853 do_put(regname, NULL, dir, count, fixindent | PUT_CURSEND);
Bram Moolenaarb20b9e12019-09-21 20:48:04 +0200854 }
855
856#if defined(FEAT_QUICKFIX)
857 // Ctrl-Mouse click or double click in a quickfix window jumps to the
858 // error under the mouse pointer.
859 else if (((mod_mask & MOD_MASK_CTRL)
860 || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)
861 && bt_quickfix(curbuf))
862 {
863 if (curwin->w_llist_ref == NULL) // quickfix window
864 do_cmdline_cmd((char_u *)".cc");
865 else // location list window
866 do_cmdline_cmd((char_u *)".ll");
867 got_click = FALSE; // ignore drag&release now
868 }
869#endif
870
871 // Ctrl-Mouse click (or double click in a help window) jumps to the tag
872 // under the mouse pointer.
873 else if ((mod_mask & MOD_MASK_CTRL) || (curbuf->b_help
874 && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK))
875 {
876 if (State & INSERT)
877 stuffcharReadbuff(Ctrl_O);
878 stuffcharReadbuff(Ctrl_RSB);
879 got_click = FALSE; // ignore drag&release now
880 }
881
882 // Shift-Mouse click searches for the next occurrence of the word under
883 // the mouse pointer
884 else if ((mod_mask & MOD_MASK_SHIFT))
885 {
886 if ((State & INSERT) || (VIsual_active && VIsual_select))
887 stuffcharReadbuff(Ctrl_O);
888 if (which_button == MOUSE_LEFT)
889 stuffcharReadbuff('*');
890 else // MOUSE_RIGHT
891 stuffcharReadbuff('#');
892 }
893
894 // Handle double clicks, unless on status line
895 else if (in_status_line)
896 {
897#ifdef FEAT_MOUSESHAPE
898 if ((is_drag || is_click) && !drag_status_line)
899 {
900 drag_status_line = TRUE;
901 update_mouseshape(-1);
902 }
903#endif
904 }
905 else if (in_sep_line)
906 {
907#ifdef FEAT_MOUSESHAPE
908 if ((is_drag || is_click) && !drag_sep_line)
909 {
910 drag_sep_line = TRUE;
911 update_mouseshape(-1);
912 }
913#endif
914 }
915 else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (NORMAL | INSERT))
916 && mouse_has(MOUSE_VISUAL))
917 {
918 if (is_click || !VIsual_active)
919 {
920 if (VIsual_active)
921 orig_cursor = VIsual;
922 else
923 {
924 check_visual_highlight();
925 VIsual = curwin->w_cursor;
926 orig_cursor = VIsual;
927 VIsual_active = TRUE;
928 VIsual_reselect = TRUE;
929 // start Select mode if 'selectmode' contains "mouse"
930 may_start_select('o');
931 setmouse();
932 }
933 if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)
934 {
935 // Double click with ALT pressed makes it blockwise.
936 if (mod_mask & MOD_MASK_ALT)
937 VIsual_mode = Ctrl_V;
938 else
939 VIsual_mode = 'v';
940 }
941 else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK)
942 VIsual_mode = 'V';
943 else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK)
944 VIsual_mode = Ctrl_V;
945#ifdef FEAT_CLIPBOARD
946 // Make sure the clipboard gets updated. Needed because start and
947 // end may still be the same, and the selection needs to be owned
948 clip_star.vmode = NUL;
949#endif
950 }
951 // A double click selects a word or a block.
952 if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)
953 {
954 pos_T *pos = NULL;
955 int gc;
956
957 if (is_click)
958 {
959 // If the character under the cursor (skipping white space) is
960 // not a word character, try finding a match and select a (),
961 // {}, [], #if/#endif, etc. block.
962 end_visual = curwin->w_cursor;
963 while (gc = gchar_pos(&end_visual), VIM_ISWHITE(gc))
964 inc(&end_visual);
965 if (oap != NULL)
966 oap->motion_type = MCHAR;
967 if (oap != NULL
968 && VIsual_mode == 'v'
969 && !vim_iswordc(gchar_pos(&end_visual))
970 && EQUAL_POS(curwin->w_cursor, VIsual)
971 && (pos = findmatch(oap, NUL)) != NULL)
972 {
973 curwin->w_cursor = *pos;
974 if (oap->motion_type == MLINE)
975 VIsual_mode = 'V';
976 else if (*p_sel == 'e')
977 {
978 if (LT_POS(curwin->w_cursor, VIsual))
979 ++VIsual.col;
980 else
981 ++curwin->w_cursor.col;
982 }
983 }
984 }
985
986 if (pos == NULL && (is_click || is_drag))
987 {
988 // When not found a match or when dragging: extend to include
989 // a word.
990 if (LT_POS(curwin->w_cursor, orig_cursor))
991 {
992 find_start_of_word(&curwin->w_cursor);
993 find_end_of_word(&VIsual);
994 }
995 else
996 {
997 find_start_of_word(&VIsual);
998 if (*p_sel == 'e' && *ml_get_cursor() != NUL)
999 curwin->w_cursor.col +=
1000 (*mb_ptr2len)(ml_get_cursor());
1001 find_end_of_word(&curwin->w_cursor);
1002 }
1003 }
1004 curwin->w_set_curswant = TRUE;
1005 }
1006 if (is_click)
1007 redraw_curbuf_later(INVERTED); // update the inversion
1008 }
1009 else if (VIsual_active && !old_active)
1010 {
1011 if (mod_mask & MOD_MASK_ALT)
1012 VIsual_mode = Ctrl_V;
1013 else
1014 VIsual_mode = 'v';
1015 }
1016
1017 // If Visual mode changed show it later.
1018 if ((!VIsual_active && old_active && mode_displayed)
1019 || (VIsual_active && p_smd && msg_silent == 0
1020 && (!old_active || VIsual_mode != old_mode)))
1021 redraw_cmdline = TRUE;
1022
1023 return moved;
1024}
1025
1026 void
1027ins_mouse(int c)
1028{
1029 pos_T tpos;
1030 win_T *old_curwin = curwin;
1031
1032# ifdef FEAT_GUI
1033 // When GUI is active, also move/paste when 'mouse' is empty
1034 if (!gui.in_use)
1035# endif
1036 if (!mouse_has(MOUSE_INSERT))
1037 return;
1038
1039 undisplay_dollar();
1040 tpos = curwin->w_cursor;
1041 if (do_mouse(NULL, c, BACKWARD, 1L, 0))
1042 {
1043 win_T *new_curwin = curwin;
1044
1045 if (curwin != old_curwin && win_valid(old_curwin))
1046 {
1047 // Mouse took us to another window. We need to go back to the
1048 // previous one to stop insert there properly.
1049 curwin = old_curwin;
1050 curbuf = curwin->w_buffer;
1051#ifdef FEAT_JOB_CHANNEL
1052 if (bt_prompt(curbuf))
1053 // Restart Insert mode when re-entering the prompt buffer.
1054 curbuf->b_prompt_insert = 'A';
1055#endif
1056 }
1057 start_arrow(curwin == old_curwin ? &tpos : NULL);
1058 if (curwin != new_curwin && win_valid(new_curwin))
1059 {
1060 curwin = new_curwin;
1061 curbuf = curwin->w_buffer;
1062 }
1063# ifdef FEAT_CINDENT
1064 set_can_cindent(TRUE);
1065# endif
1066 }
1067
1068 // redraw status lines (in case another window became active)
1069 redraw_statuslines();
1070}
1071
1072 void
1073ins_mousescroll(int dir)
1074{
1075 pos_T tpos;
1076 win_T *old_curwin = curwin, *wp;
1077 int did_scroll = FALSE;
1078
1079 tpos = curwin->w_cursor;
1080
1081 if (mouse_row >= 0 && mouse_col >= 0)
1082 {
1083 int row, col;
1084
1085 row = mouse_row;
1086 col = mouse_col;
1087
1088 // find the window at the pointer coordinates
1089 wp = mouse_find_win(&row, &col, FIND_POPUP);
1090 if (wp == NULL)
1091 return;
1092 curwin = wp;
1093 curbuf = curwin->w_buffer;
1094 }
1095 if (curwin == old_curwin)
1096 undisplay_dollar();
1097
1098 // Don't scroll the window in which completion is being done.
1099 if (!pum_visible() || curwin != old_curwin)
1100 {
1101 if (dir == MSCR_DOWN || dir == MSCR_UP)
1102 {
1103 if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
1104 scroll_redraw(dir,
1105 (long)(curwin->w_botline - curwin->w_topline));
1106 else
1107 scroll_redraw(dir, 3L);
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01001108# ifdef FEAT_PROP_POPUP
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001109 if (WIN_IS_POPUP(curwin))
1110 popup_set_firstline(curwin);
1111# endif
1112 }
1113#ifdef FEAT_GUI
1114 else
1115 {
1116 int val, step = 6;
1117
1118 if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
1119 step = curwin->w_width;
1120 val = curwin->w_leftcol + (dir == MSCR_RIGHT ? -step : step);
1121 if (val < 0)
1122 val = 0;
1123 gui_do_horiz_scroll(val, TRUE);
1124 }
1125#endif
1126 did_scroll = TRUE;
1127 }
1128
1129 curwin->w_redr_status = TRUE;
1130
1131 curwin = old_curwin;
1132 curbuf = curwin->w_buffer;
1133
1134 // The popup menu may overlay the window, need to redraw it.
1135 // TODO: Would be more efficient to only redraw the windows that are
1136 // overlapped by the popup menu.
1137 if (pum_visible() && did_scroll)
1138 {
1139 redraw_all_later(NOT_VALID);
1140 ins_compl_show_pum();
1141 }
1142
1143 if (!EQUAL_POS(curwin->w_cursor, tpos))
1144 {
1145 start_arrow(&tpos);
1146# ifdef FEAT_CINDENT
1147 set_can_cindent(TRUE);
1148# endif
1149 }
1150}
1151
1152/*
1153 * Return TRUE if "c" is a mouse key.
1154 */
1155 int
1156is_mouse_key(int c)
1157{
1158 return c == K_LEFTMOUSE
1159 || c == K_LEFTMOUSE_NM
1160 || c == K_LEFTDRAG
1161 || c == K_LEFTRELEASE
1162 || c == K_LEFTRELEASE_NM
1163 || c == K_MOUSEMOVE
1164 || c == K_MIDDLEMOUSE
1165 || c == K_MIDDLEDRAG
1166 || c == K_MIDDLERELEASE
1167 || c == K_RIGHTMOUSE
1168 || c == K_RIGHTDRAG
1169 || c == K_RIGHTRELEASE
1170 || c == K_MOUSEDOWN
1171 || c == K_MOUSEUP
1172 || c == K_MOUSELEFT
1173 || c == K_MOUSERIGHT
1174 || c == K_X1MOUSE
1175 || c == K_X1DRAG
1176 || c == K_X1RELEASE
1177 || c == K_X2MOUSE
1178 || c == K_X2DRAG
1179 || c == K_X2RELEASE;
1180}
1181
1182static struct mousetable
1183{
1184 int pseudo_code; // Code for pseudo mouse event
1185 int button; // Which mouse button is it?
1186 int is_click; // Is it a mouse button click event?
1187 int is_drag; // Is it a mouse drag event?
1188} mouse_table[] =
1189{
1190 {(int)KE_LEFTMOUSE, MOUSE_LEFT, TRUE, FALSE},
1191#ifdef FEAT_GUI
1192 {(int)KE_LEFTMOUSE_NM, MOUSE_LEFT, TRUE, FALSE},
1193#endif
1194 {(int)KE_LEFTDRAG, MOUSE_LEFT, FALSE, TRUE},
1195 {(int)KE_LEFTRELEASE, MOUSE_LEFT, FALSE, FALSE},
1196#ifdef FEAT_GUI
1197 {(int)KE_LEFTRELEASE_NM, MOUSE_LEFT, FALSE, FALSE},
1198#endif
1199 {(int)KE_MIDDLEMOUSE, MOUSE_MIDDLE, TRUE, FALSE},
1200 {(int)KE_MIDDLEDRAG, MOUSE_MIDDLE, FALSE, TRUE},
1201 {(int)KE_MIDDLERELEASE, MOUSE_MIDDLE, FALSE, FALSE},
1202 {(int)KE_RIGHTMOUSE, MOUSE_RIGHT, TRUE, FALSE},
1203 {(int)KE_RIGHTDRAG, MOUSE_RIGHT, FALSE, TRUE},
1204 {(int)KE_RIGHTRELEASE, MOUSE_RIGHT, FALSE, FALSE},
1205 {(int)KE_X1MOUSE, MOUSE_X1, TRUE, FALSE},
1206 {(int)KE_X1DRAG, MOUSE_X1, FALSE, TRUE},
1207 {(int)KE_X1RELEASE, MOUSE_X1, FALSE, FALSE},
1208 {(int)KE_X2MOUSE, MOUSE_X2, TRUE, FALSE},
1209 {(int)KE_X2DRAG, MOUSE_X2, FALSE, TRUE},
1210 {(int)KE_X2RELEASE, MOUSE_X2, FALSE, FALSE},
1211 // DRAG without CLICK
1212 {(int)KE_MOUSEMOVE, MOUSE_RELEASE, FALSE, TRUE},
1213 // RELEASE without CLICK
1214 {(int)KE_IGNORE, MOUSE_RELEASE, FALSE, FALSE},
1215 {0, 0, 0, 0},
1216};
1217
1218/*
1219 * Look up the given mouse code to return the relevant information in the other
1220 * arguments. Return which button is down or was released.
1221 */
1222 int
1223get_mouse_button(int code, int *is_click, int *is_drag)
1224{
1225 int i;
1226
1227 for (i = 0; mouse_table[i].pseudo_code; i++)
1228 if (code == mouse_table[i].pseudo_code)
1229 {
1230 *is_click = mouse_table[i].is_click;
1231 *is_drag = mouse_table[i].is_drag;
1232 return mouse_table[i].button;
1233 }
1234 return 0; // Shouldn't get here
1235}
1236
1237/*
1238 * Return the appropriate pseudo mouse event token (KE_LEFTMOUSE etc) based on
1239 * the given information about which mouse button is down, and whether the
1240 * mouse was clicked, dragged or released.
1241 */
1242 int
1243get_pseudo_mouse_code(
1244 int button, // eg MOUSE_LEFT
1245 int is_click,
1246 int is_drag)
1247{
1248 int i;
1249
1250 for (i = 0; mouse_table[i].pseudo_code; i++)
1251 if (button == mouse_table[i].button
1252 && is_click == mouse_table[i].is_click
1253 && is_drag == mouse_table[i].is_drag)
1254 {
1255#ifdef FEAT_GUI
1256 // Trick: a non mappable left click and release has mouse_col -1
1257 // or added MOUSE_COLOFF. Used for 'mousefocus' in
1258 // gui_mouse_moved()
1259 if (mouse_col < 0 || mouse_col > MOUSE_COLOFF)
1260 {
1261 if (mouse_col < 0)
1262 mouse_col = 0;
1263 else
1264 mouse_col -= MOUSE_COLOFF;
1265 if (mouse_table[i].pseudo_code == (int)KE_LEFTMOUSE)
1266 return (int)KE_LEFTMOUSE_NM;
1267 if (mouse_table[i].pseudo_code == (int)KE_LEFTRELEASE)
1268 return (int)KE_LEFTRELEASE_NM;
1269 }
1270#endif
1271 return mouse_table[i].pseudo_code;
1272 }
1273 return (int)KE_IGNORE; // not recognized, ignore it
1274}
1275
Bram Moolenaara1cb1d12019-10-17 23:00:07 +02001276# define HMT_NORMAL 1
1277# define HMT_NETTERM 2
1278# define HMT_DEC 4
1279# define HMT_JSBTERM 8
1280# define HMT_PTERM 16
1281# define HMT_URXVT 32
1282# define HMT_GPM 64
1283# define HMT_SGR 128
1284# define HMT_SGR_REL 256
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001285static int has_mouse_termcode = 0;
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001286
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001287 void
1288set_mouse_termcode(
1289 int n, // KS_MOUSE, KS_NETTERM_MOUSE or KS_DEC_MOUSE
1290 char_u *s)
1291{
1292 char_u name[2];
1293
1294 name[0] = n;
1295 name[1] = KE_FILLER;
1296 add_termcode(name, s, FALSE);
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001297# ifdef FEAT_MOUSE_JSB
1298 if (n == KS_JSBTERM_MOUSE)
1299 has_mouse_termcode |= HMT_JSBTERM;
1300 else
1301# endif
1302# ifdef FEAT_MOUSE_NET
1303 if (n == KS_NETTERM_MOUSE)
1304 has_mouse_termcode |= HMT_NETTERM;
1305 else
1306# endif
1307# ifdef FEAT_MOUSE_DEC
1308 if (n == KS_DEC_MOUSE)
1309 has_mouse_termcode |= HMT_DEC;
1310 else
1311# endif
1312# ifdef FEAT_MOUSE_PTERM
1313 if (n == KS_PTERM_MOUSE)
1314 has_mouse_termcode |= HMT_PTERM;
1315 else
1316# endif
1317# ifdef FEAT_MOUSE_URXVT
1318 if (n == KS_URXVT_MOUSE)
1319 has_mouse_termcode |= HMT_URXVT;
1320 else
1321# endif
1322# ifdef FEAT_MOUSE_GPM
1323 if (n == KS_GPM_MOUSE)
1324 has_mouse_termcode |= HMT_GPM;
1325 else
1326# endif
1327 if (n == KS_SGR_MOUSE)
1328 has_mouse_termcode |= HMT_SGR;
1329 else if (n == KS_SGR_MOUSE_RELEASE)
1330 has_mouse_termcode |= HMT_SGR_REL;
1331 else
1332 has_mouse_termcode |= HMT_NORMAL;
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001333}
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001334
Bram Moolenaara1cb1d12019-10-17 23:00:07 +02001335# if defined(UNIX) || defined(VMS) || defined(PROTO)
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001336 void
1337del_mouse_termcode(
1338 int n) // KS_MOUSE, KS_NETTERM_MOUSE or KS_DEC_MOUSE
1339{
1340 char_u name[2];
1341
1342 name[0] = n;
1343 name[1] = KE_FILLER;
1344 del_termcode(name);
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001345# ifdef FEAT_MOUSE_JSB
1346 if (n == KS_JSBTERM_MOUSE)
1347 has_mouse_termcode &= ~HMT_JSBTERM;
1348 else
1349# endif
1350# ifdef FEAT_MOUSE_NET
1351 if (n == KS_NETTERM_MOUSE)
1352 has_mouse_termcode &= ~HMT_NETTERM;
1353 else
1354# endif
1355# ifdef FEAT_MOUSE_DEC
1356 if (n == KS_DEC_MOUSE)
1357 has_mouse_termcode &= ~HMT_DEC;
1358 else
1359# endif
1360# ifdef FEAT_MOUSE_PTERM
1361 if (n == KS_PTERM_MOUSE)
1362 has_mouse_termcode &= ~HMT_PTERM;
1363 else
1364# endif
1365# ifdef FEAT_MOUSE_URXVT
1366 if (n == KS_URXVT_MOUSE)
1367 has_mouse_termcode &= ~HMT_URXVT;
1368 else
1369# endif
1370# ifdef FEAT_MOUSE_GPM
1371 if (n == KS_GPM_MOUSE)
1372 has_mouse_termcode &= ~HMT_GPM;
1373 else
1374# endif
1375 if (n == KS_SGR_MOUSE)
1376 has_mouse_termcode &= ~HMT_SGR;
1377 else if (n == KS_SGR_MOUSE_RELEASE)
1378 has_mouse_termcode &= ~HMT_SGR_REL;
1379 else
1380 has_mouse_termcode &= ~HMT_NORMAL;
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001381}
1382# endif
1383
1384/*
1385 * setmouse() - switch mouse on/off depending on current mode and 'mouse'
1386 */
1387 void
1388setmouse(void)
1389{
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001390 int checkfor;
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001391
1392# ifdef FEAT_MOUSESHAPE
1393 update_mouseshape(-1);
1394# endif
1395
Bram Moolenaara1cb1d12019-10-17 23:00:07 +02001396 // Should be outside proc, but may break MOUSESHAPE
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001397# ifdef FEAT_GUI
1398 // In the GUI the mouse is always enabled.
1399 if (gui.in_use)
1400 return;
1401# endif
1402 // be quick when mouse is off
1403 if (*p_mouse == NUL || has_mouse_termcode == 0)
1404 return;
1405
1406 // don't switch mouse on when not in raw mode (Ex mode)
1407 if (cur_tmode != TMODE_RAW)
1408 {
1409 mch_setmouse(FALSE);
1410 return;
1411 }
1412
1413 if (VIsual_active)
1414 checkfor = MOUSE_VISUAL;
1415 else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE)
1416 checkfor = MOUSE_RETURN;
1417 else if (State & INSERT)
1418 checkfor = MOUSE_INSERT;
1419 else if (State & CMDLINE)
1420 checkfor = MOUSE_COMMAND;
1421 else if (State == CONFIRM || State == EXTERNCMD)
1422 checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd"
1423 else
1424 checkfor = MOUSE_NORMAL; // assume normal mode
1425
1426 if (mouse_has(checkfor))
1427 mch_setmouse(TRUE);
1428 else
1429 mch_setmouse(FALSE);
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001430}
1431
1432/*
1433 * Return TRUE if
1434 * - "c" is in 'mouse', or
1435 * - 'a' is in 'mouse' and "c" is in MOUSE_A, or
1436 * - the current buffer is a help file and 'h' is in 'mouse' and we are in a
1437 * normal editing mode (not at hit-return message).
1438 */
1439 int
1440mouse_has(int c)
1441{
1442 char_u *p;
1443
1444 for (p = p_mouse; *p; ++p)
1445 switch (*p)
1446 {
1447 case 'a': if (vim_strchr((char_u *)MOUSE_A, c) != NULL)
1448 return TRUE;
1449 break;
1450 case MOUSE_HELP: if (c != MOUSE_RETURN && curbuf->b_help)
1451 return TRUE;
1452 break;
1453 default: if (c == *p) return TRUE; break;
1454 }
1455 return FALSE;
1456}
1457
1458/*
1459 * Return TRUE when 'mousemodel' is set to "popup" or "popup_setpos".
1460 */
1461 int
1462mouse_model_popup(void)
1463{
1464 return (p_mousem[0] == 'p');
1465}
1466
1467/*
1468 * Move the cursor to the specified row and column on the screen.
1469 * Change current window if necessary. Returns an integer with the
1470 * CURSOR_MOVED bit set if the cursor has moved or unset otherwise.
1471 *
1472 * The MOUSE_FOLD_CLOSE bit is set when clicked on the '-' in a fold column.
1473 * The MOUSE_FOLD_OPEN bit is set when clicked on the '+' in a fold column.
1474 *
1475 * If flags has MOUSE_FOCUS, then the current window will not be changed, and
1476 * if the mouse is outside the window then the text will scroll, or if the
1477 * mouse was previously on a status line, then the status line may be dragged.
1478 *
1479 * If flags has MOUSE_MAY_VIS, then VIsual mode will be started before the
1480 * cursor is moved unless the cursor was on a status line.
1481 * This function returns one of IN_UNKNOWN, IN_BUFFER, IN_STATUS_LINE or
1482 * IN_SEP_LINE depending on where the cursor was clicked.
1483 *
1484 * If flags has MOUSE_MAY_STOP_VIS, then Visual mode will be stopped, unless
1485 * the mouse is on the status line of the same window.
1486 *
1487 * If flags has MOUSE_DID_MOVE, nothing is done if the mouse didn't move since
1488 * the last call.
1489 *
1490 * If flags has MOUSE_SETPOS, nothing is done, only the current position is
1491 * remembered.
1492 */
1493 int
1494jump_to_mouse(
1495 int flags,
1496 int *inclusive, // used for inclusive operator, can be NULL
1497 int which_button) // MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE
1498{
1499 static int on_status_line = 0; // #lines below bottom of window
1500 static int on_sep_line = 0; // on separator right of window
1501#ifdef FEAT_MENU
1502 static int in_winbar = FALSE;
1503#endif
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01001504#ifdef FEAT_PROP_POPUP
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001505 static int in_popup_win = FALSE;
1506 static win_T *click_in_popup_win = NULL;
1507#endif
1508 static int prev_row = -1;
1509 static int prev_col = -1;
1510 static win_T *dragwin = NULL; // window being dragged
1511 static int did_drag = FALSE; // drag was noticed
1512
1513 win_T *wp, *old_curwin;
1514 pos_T old_cursor;
1515 int count;
1516 int first;
1517 int row = mouse_row;
1518 int col = mouse_col;
1519#ifdef FEAT_FOLDING
1520 int mouse_char;
1521#endif
1522
1523 mouse_past_bottom = FALSE;
1524 mouse_past_eol = FALSE;
1525
1526 if (flags & MOUSE_RELEASED)
1527 {
1528 // On button release we may change window focus if positioned on a
1529 // status line and no dragging happened.
1530 if (dragwin != NULL && !did_drag)
1531 flags &= ~(MOUSE_FOCUS | MOUSE_DID_MOVE);
1532 dragwin = NULL;
1533 did_drag = FALSE;
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01001534#ifdef FEAT_PROP_POPUP
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001535 if (click_in_popup_win != NULL && popup_dragwin == NULL)
1536 popup_close_for_mouse_click(click_in_popup_win);
1537
1538 popup_dragwin = NULL;
1539 click_in_popup_win = NULL;
1540#endif
1541 }
1542
1543 if ((flags & MOUSE_DID_MOVE)
1544 && prev_row == mouse_row
1545 && prev_col == mouse_col)
1546 {
1547retnomove:
1548 // before moving the cursor for a left click which is NOT in a status
1549 // line, stop Visual mode
1550 if (on_status_line)
1551 return IN_STATUS_LINE;
1552 if (on_sep_line)
1553 return IN_SEP_LINE;
1554#ifdef FEAT_MENU
1555 if (in_winbar)
1556 {
1557 // A quick second click may arrive as a double-click, but we use it
1558 // as a second click in the WinBar.
1559 if ((mod_mask & MOD_MASK_MULTI_CLICK) && !(flags & MOUSE_RELEASED))
1560 {
1561 wp = mouse_find_win(&row, &col, FAIL_POPUP);
1562 if (wp == NULL)
1563 return IN_UNKNOWN;
1564 winbar_click(wp, col);
1565 }
1566 return IN_OTHER_WIN | MOUSE_WINBAR;
1567 }
1568#endif
1569 if (flags & MOUSE_MAY_STOP_VIS)
1570 {
Bram Moolenaar4f3c57f2021-06-03 22:11:08 +02001571 end_visual_mode_keep_button();
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001572 redraw_curbuf_later(INVERTED); // delete the inversion
1573 }
1574#if defined(FEAT_CMDWIN) && defined(FEAT_CLIPBOARD)
1575 // Continue a modeless selection in another window.
1576 if (cmdwin_type != 0 && row < curwin->w_winrow)
1577 return IN_OTHER_WIN;
1578#endif
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01001579#ifdef FEAT_PROP_POPUP
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001580 // Continue a modeless selection in a popup window or dragging it.
1581 if (in_popup_win)
1582 {
1583 click_in_popup_win = NULL; // don't close it on release
1584 if (popup_dragwin != NULL)
1585 {
1586 // dragging a popup window
1587 popup_drag(popup_dragwin);
1588 return IN_UNKNOWN;
1589 }
1590 return IN_OTHER_WIN;
1591 }
1592#endif
1593 return IN_BUFFER;
1594 }
1595
1596 prev_row = mouse_row;
1597 prev_col = mouse_col;
1598
1599 if (flags & MOUSE_SETPOS)
1600 goto retnomove; // ugly goto...
1601
1602#ifdef FEAT_FOLDING
1603 // Remember the character under the mouse, it might be a '-' or '+' in the
1604 // fold column.
1605 if (row >= 0 && row < Rows && col >= 0 && col <= Columns
1606 && ScreenLines != NULL)
1607 mouse_char = ScreenLines[LineOffset[row] + col];
1608 else
1609 mouse_char = ' ';
1610#endif
1611
1612 old_curwin = curwin;
1613 old_cursor = curwin->w_cursor;
1614
1615 if (!(flags & MOUSE_FOCUS))
1616 {
1617 if (row < 0 || col < 0) // check if it makes sense
1618 return IN_UNKNOWN;
1619
1620 // find the window where the row is in and adjust "row" and "col" to be
1621 // relative to top-left of the window
1622 wp = mouse_find_win(&row, &col, FIND_POPUP);
1623 if (wp == NULL)
1624 return IN_UNKNOWN;
1625 dragwin = NULL;
1626
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01001627#ifdef FEAT_PROP_POPUP
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001628 // Click in a popup window may start dragging or modeless selection,
1629 // but not much else.
1630 if (WIN_IS_POPUP(wp))
1631 {
1632 on_sep_line = 0;
Bram Moolenaarbfc57862021-11-26 15:57:40 +00001633 on_status_line = 0;
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001634 in_popup_win = TRUE;
1635 if (which_button == MOUSE_LEFT && popup_close_if_on_X(wp, row, col))
1636 {
1637 return IN_UNKNOWN;
1638 }
Bram Moolenaar0b74d002021-11-29 17:38:02 +00001639 else if (((wp->w_popup_flags & (POPF_DRAG | POPF_RESIZE))
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001640 && popup_on_border(wp, row, col))
Bram Moolenaar0b74d002021-11-29 17:38:02 +00001641 || (wp->w_popup_flags & POPF_DRAGALL))
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001642 {
1643 popup_dragwin = wp;
1644 popup_start_drag(wp, row, col);
1645 return IN_UNKNOWN;
1646 }
1647 // Only close on release, otherwise it's not possible to drag or do
1648 // modeless selection.
1649 else if (wp->w_popup_close == POPCLOSE_CLICK
1650 && which_button == MOUSE_LEFT)
1651 {
1652 click_in_popup_win = wp;
1653 }
1654 else if (which_button == MOUSE_LEFT)
1655 // If the click is in the scrollbar, may scroll up/down.
1656 popup_handle_scrollbar_click(wp, row, col);
1657# ifdef FEAT_CLIPBOARD
1658 return IN_OTHER_WIN;
1659# else
1660 return IN_UNKNOWN;
1661# endif
1662 }
1663 in_popup_win = FALSE;
1664 popup_dragwin = NULL;
1665#endif
1666#ifdef FEAT_MENU
1667 if (row == -1)
1668 {
1669 // A click in the window toolbar does not enter another window or
1670 // change Visual highlighting.
1671 winbar_click(wp, col);
1672 in_winbar = TRUE;
1673 return IN_OTHER_WIN | MOUSE_WINBAR;
1674 }
1675 in_winbar = FALSE;
1676#endif
1677
1678 // winpos and height may change in win_enter()!
1679 if (row >= wp->w_height) // In (or below) status line
1680 {
1681 on_status_line = row - wp->w_height + 1;
1682 dragwin = wp;
1683 }
1684 else
1685 on_status_line = 0;
1686 if (col >= wp->w_width) // In separator line
1687 {
1688 on_sep_line = col - wp->w_width + 1;
1689 dragwin = wp;
1690 }
1691 else
1692 on_sep_line = 0;
1693
1694 // The rightmost character of the status line might be a vertical
1695 // separator character if there is no connecting window to the right.
1696 if (on_status_line && on_sep_line)
1697 {
1698 if (stl_connected(wp))
1699 on_sep_line = 0;
1700 else
1701 on_status_line = 0;
1702 }
1703
1704 // Before jumping to another buffer, or moving the cursor for a left
1705 // click, stop Visual mode.
1706 if (VIsual_active
1707 && (wp->w_buffer != curwin->w_buffer
1708 || (!on_status_line && !on_sep_line
1709#ifdef FEAT_FOLDING
1710 && (
1711# ifdef FEAT_RIGHTLEFT
1712 wp->w_p_rl ? col < wp->w_width - wp->w_p_fdc :
1713# endif
1714 col >= wp->w_p_fdc
1715# ifdef FEAT_CMDWIN
1716 + (cmdwin_type == 0 && wp == curwin ? 0 : 1)
1717# endif
1718 )
1719#endif
1720 && (flags & MOUSE_MAY_STOP_VIS))))
1721 {
Bram Moolenaar4f3c57f2021-06-03 22:11:08 +02001722 end_visual_mode_keep_button();
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001723 redraw_curbuf_later(INVERTED); // delete the inversion
1724 }
1725#ifdef FEAT_CMDWIN
1726 if (cmdwin_type != 0 && wp != curwin)
1727 {
1728 // A click outside the command-line window: Use modeless
1729 // selection if possible. Allow dragging the status lines.
1730 on_sep_line = 0;
1731# ifdef FEAT_CLIPBOARD
1732 if (on_status_line)
1733 return IN_STATUS_LINE;
1734 return IN_OTHER_WIN;
1735# else
1736 row = 0;
1737 col += wp->w_wincol;
1738 wp = curwin;
1739# endif
1740 }
1741#endif
Bram Moolenaar219c7d02020-02-01 21:57:29 +01001742#if defined(FEAT_PROP_POPUP) && defined(FEAT_TERMINAL)
1743 if (popup_is_popup(curwin) && curbuf->b_term != NULL)
1744 // terminal in popup window: don't jump to another window
1745 return IN_OTHER_WIN;
1746#endif
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001747 // Only change window focus when not clicking on or dragging the
1748 // status line. Do change focus when releasing the mouse button
1749 // (MOUSE_FOCUS was set above if we dragged first).
1750 if (dragwin == NULL || (flags & MOUSE_RELEASED))
1751 win_enter(wp, TRUE); // can make wp invalid!
1752
1753 if (curwin != old_curwin)
1754 {
1755#ifdef CHECK_DOUBLE_CLICK
1756 // set topline, to be able to check for double click ourselves
1757 set_mouse_topline(curwin);
1758#endif
1759#ifdef FEAT_TERMINAL
1760 // when entering a terminal window may change state
1761 term_win_entered();
1762#endif
1763 }
1764 if (on_status_line) // In (or below) status line
1765 {
1766 // Don't use start_arrow() if we're in the same window
1767 if (curwin == old_curwin)
1768 return IN_STATUS_LINE;
1769 else
1770 return IN_STATUS_LINE | CURSOR_MOVED;
1771 }
1772 if (on_sep_line) // In (or below) status line
1773 {
1774 // Don't use start_arrow() if we're in the same window
1775 if (curwin == old_curwin)
1776 return IN_SEP_LINE;
1777 else
1778 return IN_SEP_LINE | CURSOR_MOVED;
1779 }
1780
1781 curwin->w_cursor.lnum = curwin->w_topline;
1782#ifdef FEAT_GUI
1783 // remember topline, needed for double click
1784 gui_prev_topline = curwin->w_topline;
1785# ifdef FEAT_DIFF
1786 gui_prev_topfill = curwin->w_topfill;
1787# endif
1788#endif
1789 }
1790 else if (on_status_line && which_button == MOUSE_LEFT)
1791 {
1792 if (dragwin != NULL)
1793 {
1794 // Drag the status line
1795 count = row - dragwin->w_winrow - dragwin->w_height + 1
1796 - on_status_line;
1797 win_drag_status_line(dragwin, count);
1798 did_drag |= count;
1799 }
1800 return IN_STATUS_LINE; // Cursor didn't move
1801 }
1802 else if (on_sep_line && which_button == MOUSE_LEFT)
1803 {
1804 if (dragwin != NULL)
1805 {
1806 // Drag the separator column
1807 count = col - dragwin->w_wincol - dragwin->w_width + 1
1808 - on_sep_line;
1809 win_drag_vsep_line(dragwin, count);
1810 did_drag |= count;
1811 }
1812 return IN_SEP_LINE; // Cursor didn't move
1813 }
1814#ifdef FEAT_MENU
1815 else if (in_winbar)
1816 {
1817 // After a click on the window toolbar don't start Visual mode.
1818 return IN_OTHER_WIN | MOUSE_WINBAR;
1819 }
1820#endif
1821 else // keep_window_focus must be TRUE
1822 {
1823 // before moving the cursor for a left click, stop Visual mode
1824 if (flags & MOUSE_MAY_STOP_VIS)
1825 {
Bram Moolenaar4f3c57f2021-06-03 22:11:08 +02001826 end_visual_mode_keep_button();
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001827 redraw_curbuf_later(INVERTED); // delete the inversion
1828 }
1829
1830#if defined(FEAT_CMDWIN) && defined(FEAT_CLIPBOARD)
1831 // Continue a modeless selection in another window.
1832 if (cmdwin_type != 0 && row < curwin->w_winrow)
1833 return IN_OTHER_WIN;
1834#endif
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01001835#ifdef FEAT_PROP_POPUP
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001836 if (in_popup_win)
1837 {
1838 if (popup_dragwin != NULL)
1839 {
1840 // dragging a popup window
1841 popup_drag(popup_dragwin);
1842 return IN_UNKNOWN;
1843 }
1844 // continue a modeless selection in a popup window
1845 click_in_popup_win = NULL;
1846 return IN_OTHER_WIN;
1847 }
1848#endif
1849
1850 row -= W_WINROW(curwin);
1851 col -= curwin->w_wincol;
1852
1853 // When clicking beyond the end of the window, scroll the screen.
1854 // Scroll by however many rows outside the window we are.
1855 if (row < 0)
1856 {
1857 count = 0;
1858 for (first = TRUE; curwin->w_topline > 1; )
1859 {
1860#ifdef FEAT_DIFF
1861 if (curwin->w_topfill < diff_check(curwin, curwin->w_topline))
1862 ++count;
1863 else
1864#endif
1865 count += plines(curwin->w_topline - 1);
1866 if (!first && count > -row)
1867 break;
1868 first = FALSE;
1869#ifdef FEAT_FOLDING
1870 (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
1871#endif
1872#ifdef FEAT_DIFF
1873 if (curwin->w_topfill < diff_check(curwin, curwin->w_topline))
1874 ++curwin->w_topfill;
1875 else
1876#endif
1877 {
1878 --curwin->w_topline;
1879#ifdef FEAT_DIFF
1880 curwin->w_topfill = 0;
1881#endif
1882 }
1883 }
1884#ifdef FEAT_DIFF
1885 check_topfill(curwin, FALSE);
1886#endif
1887 curwin->w_valid &=
1888 ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
1889 redraw_later(VALID);
1890 row = 0;
1891 }
1892 else if (row >= curwin->w_height)
1893 {
1894 count = 0;
1895 for (first = TRUE; curwin->w_topline < curbuf->b_ml.ml_line_count; )
1896 {
1897#ifdef FEAT_DIFF
1898 if (curwin->w_topfill > 0)
1899 ++count;
1900 else
1901#endif
1902 count += plines(curwin->w_topline);
1903 if (!first && count > row - curwin->w_height + 1)
1904 break;
1905 first = FALSE;
1906#ifdef FEAT_FOLDING
1907 if (hasFolding(curwin->w_topline, NULL, &curwin->w_topline)
1908 && curwin->w_topline == curbuf->b_ml.ml_line_count)
1909 break;
1910#endif
1911#ifdef FEAT_DIFF
1912 if (curwin->w_topfill > 0)
1913 --curwin->w_topfill;
1914 else
1915#endif
1916 {
1917 ++curwin->w_topline;
1918#ifdef FEAT_DIFF
1919 curwin->w_topfill =
1920 diff_check_fill(curwin, curwin->w_topline);
1921#endif
1922 }
1923 }
1924#ifdef FEAT_DIFF
1925 check_topfill(curwin, FALSE);
1926#endif
1927 redraw_later(VALID);
1928 curwin->w_valid &=
1929 ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
1930 row = curwin->w_height - 1;
1931 }
1932 else if (row == 0)
1933 {
1934 // When dragging the mouse, while the text has been scrolled up as
1935 // far as it goes, moving the mouse in the top line should scroll
1936 // the text down (done later when recomputing w_topline).
1937 if (mouse_dragging > 0
1938 && curwin->w_cursor.lnum
1939 == curwin->w_buffer->b_ml.ml_line_count
1940 && curwin->w_cursor.lnum == curwin->w_topline)
1941 curwin->w_valid &= ~(VALID_TOPLINE);
1942 }
1943 }
1944
1945#ifdef FEAT_FOLDING
1946 // Check for position outside of the fold column.
1947 if (
1948# ifdef FEAT_RIGHTLEFT
1949 curwin->w_p_rl ? col < curwin->w_width - curwin->w_p_fdc :
1950# endif
1951 col >= curwin->w_p_fdc
1952# ifdef FEAT_CMDWIN
1953 + (cmdwin_type == 0 ? 0 : 1)
1954# endif
1955 )
1956 mouse_char = ' ';
1957#endif
1958
1959 // compute the position in the buffer line from the posn on the screen
1960 if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum, NULL))
1961 mouse_past_bottom = TRUE;
1962
1963 // Start Visual mode before coladvance(), for when 'sel' != "old"
1964 if ((flags & MOUSE_MAY_VIS) && !VIsual_active)
1965 {
1966 check_visual_highlight();
1967 VIsual = old_cursor;
1968 VIsual_active = TRUE;
1969 VIsual_reselect = TRUE;
1970 // if 'selectmode' contains "mouse", start Select mode
1971 may_start_select('o');
1972 setmouse();
1973 if (p_smd && msg_silent == 0)
1974 redraw_cmdline = TRUE; // show visual mode later
1975 }
1976
1977 curwin->w_curswant = col;
1978 curwin->w_set_curswant = FALSE; // May still have been TRUE
1979 if (coladvance(col) == FAIL) // Mouse click beyond end of line
1980 {
1981 if (inclusive != NULL)
1982 *inclusive = TRUE;
1983 mouse_past_eol = TRUE;
1984 }
1985 else if (inclusive != NULL)
1986 *inclusive = FALSE;
1987
1988 count = IN_BUFFER;
1989 if (curwin != old_curwin || curwin->w_cursor.lnum != old_cursor.lnum
1990 || curwin->w_cursor.col != old_cursor.col)
1991 count |= CURSOR_MOVED; // Cursor has moved
1992
1993# ifdef FEAT_FOLDING
Bram Moolenaar3aca5a62021-02-17 13:14:07 +01001994 if (mouse_char == fill_foldclosed)
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02001995 count |= MOUSE_FOLD_OPEN;
1996 else if (mouse_char != ' ')
1997 count |= MOUSE_FOLD_CLOSE;
1998# endif
1999
2000 return count;
2001}
2002
2003/*
2004 * Mouse scroll wheel: Default action is to scroll three lines, or one page
2005 * when Shift or Ctrl is used.
2006 * K_MOUSEUP (cap->arg == 1) or K_MOUSEDOWN (cap->arg == 0) or
2007 * K_MOUSELEFT (cap->arg == -1) or K_MOUSERIGHT (cap->arg == -2)
2008 */
2009 void
2010nv_mousescroll(cmdarg_T *cap)
2011{
2012 win_T *old_curwin = curwin, *wp;
2013
2014 if (mouse_row >= 0 && mouse_col >= 0)
2015 {
2016 int row, col;
2017
2018 row = mouse_row;
2019 col = mouse_col;
2020
2021 // find the window at the pointer coordinates
2022 wp = mouse_find_win(&row, &col, FIND_POPUP);
2023 if (wp == NULL)
2024 return;
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01002025#ifdef FEAT_PROP_POPUP
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002026 if (WIN_IS_POPUP(wp) && !wp->w_has_scrollbar)
2027 return;
2028#endif
2029 curwin = wp;
2030 curbuf = curwin->w_buffer;
2031 }
2032
2033 if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN)
2034 {
2035# ifdef FEAT_TERMINAL
2036 if (term_use_loop())
2037 // This window is a terminal window, send the mouse event there.
2038 // Set "typed" to FALSE to avoid an endless loop.
Bram Moolenaar1e814bc2019-11-03 21:19:41 +01002039 send_keys_to_term(curbuf->b_term, cap->cmdchar, mod_mask, FALSE);
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002040 else
2041# endif
2042 if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
2043 {
2044 (void)onepage(cap->arg ? FORWARD : BACKWARD, 1L);
2045 }
2046 else
2047 {
2048 // Don't scroll more than half the window height.
2049 if (curwin->w_height < 6)
2050 {
2051 cap->count1 = curwin->w_height / 2;
2052 if (cap->count1 == 0)
2053 cap->count1 = 1;
2054 }
2055 else
2056 cap->count1 = 3;
2057 cap->count0 = cap->count1;
2058 nv_scroll_line(cap);
2059 }
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01002060#ifdef FEAT_PROP_POPUP
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002061 if (WIN_IS_POPUP(curwin))
2062 popup_set_firstline(curwin);
2063#endif
2064 }
2065# ifdef FEAT_GUI
2066 else
2067 {
2068 // Horizontal scroll - only allowed when 'wrap' is disabled
2069 if (!curwin->w_p_wrap)
2070 {
2071 int val, step = 6;
2072
2073 if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))
2074 step = curwin->w_width;
2075 val = curwin->w_leftcol + (cap->arg == MSCR_RIGHT ? -step : +step);
2076 if (val < 0)
2077 val = 0;
2078
2079 gui_do_horiz_scroll(val, TRUE);
2080 }
2081 }
2082# endif
2083# ifdef FEAT_SYN_HL
2084 if (curwin != old_curwin && curwin->w_p_cul)
2085 redraw_for_cursorline(curwin);
2086# endif
2087
2088 curwin->w_redr_status = TRUE;
2089
2090 curwin = old_curwin;
2091 curbuf = curwin->w_buffer;
2092}
2093
2094/*
2095 * Mouse clicks and drags.
2096 */
2097 void
2098nv_mouse(cmdarg_T *cap)
2099{
2100 (void)do_mouse(cap->oap, cap->cmdchar, BACKWARD, cap->count1, 0);
2101}
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002102
Bram Moolenaar85eee5b2021-06-03 20:34:57 +02002103static int held_button = MOUSE_RELEASE;
2104
2105 void
2106reset_held_button()
2107{
2108 held_button = MOUSE_RELEASE;
2109}
2110
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002111/*
2112 * Check if typebuf 'tp' contains a terminal mouse code and returns the
2113 * modifiers found in typebuf in 'modifiers'.
2114 */
2115 int
2116check_termcode_mouse(
2117 char_u *tp,
2118 int *slen,
2119 char_u *key_name,
2120 char_u *modifiers_start,
2121 int idx,
2122 int *modifiers)
2123{
2124 int j;
2125 char_u *p;
2126# if !defined(UNIX) || defined(FEAT_MOUSE_XTERM) || defined(FEAT_GUI) \
2127 || defined(FEAT_MOUSE_GPM) || defined(FEAT_SYSMOUSE)
2128 char_u bytes[6];
2129 int num_bytes;
2130# endif
2131 int mouse_code = 0; // init for GCC
2132 int is_click, is_drag;
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002133 int is_release, release_is_ambiguous;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002134 int wheel_code = 0;
2135 int current_button;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002136 static int orig_num_clicks = 1;
2137 static int orig_mouse_code = 0x0;
2138# ifdef CHECK_DOUBLE_CLICK
2139 static int orig_mouse_col = 0;
2140 static int orig_mouse_row = 0;
2141 static struct timeval orig_mouse_time = {0, 0};
2142 // time of previous mouse click
2143 struct timeval mouse_time; // time of current mouse click
2144 long timediff; // elapsed time in msec
2145# endif
2146
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002147 is_click = is_drag = is_release = release_is_ambiguous = FALSE;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002148
2149# if !defined(UNIX) || defined(FEAT_MOUSE_XTERM) || defined(FEAT_GUI) \
2150 || defined(FEAT_MOUSE_GPM) || defined(FEAT_SYSMOUSE)
2151 if (key_name[0] == KS_MOUSE
2152# ifdef FEAT_MOUSE_GPM
2153 || key_name[0] == KS_GPM_MOUSE
2154# endif
2155 )
2156 {
2157 /*
Bram Moolenaar13c04632020-07-12 13:47:42 +02002158 * For xterm we get "<t_mouse>scr", where s == encoded button state:
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002159 * 0x20 = left button down
2160 * 0x21 = middle button down
2161 * 0x22 = right button down
2162 * 0x23 = any button release
2163 * 0x60 = button 4 down (scroll wheel down)
2164 * 0x61 = button 5 down (scroll wheel up)
2165 * add 0x04 for SHIFT
2166 * add 0x08 for ALT
2167 * add 0x10 for CTRL
2168 * add 0x20 for mouse drag (0x40 is drag with left button)
2169 * add 0x40 for mouse move (0x80 is move, 0x81 too)
2170 * 0x43 (drag + release) is also move
2171 * c == column + ' ' + 1 == column + 33
2172 * r == row + ' ' + 1 == row + 33
2173 *
Bram Moolenaar13c04632020-07-12 13:47:42 +02002174 * The coordinates are passed on through global variables. Ugly, but
2175 * this avoids trouble with mouse clicks at an unexpected moment and
2176 * allows for mapping them.
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002177 */
2178 for (;;)
2179 {
2180# ifdef FEAT_GUI
2181 if (gui.in_use)
2182 {
2183 // GUI uses more bits for columns > 223
2184 num_bytes = get_bytes_from_buf(tp + *slen, bytes, 5);
2185 if (num_bytes == -1) // not enough coordinates
2186 return -1;
2187 mouse_code = bytes[0];
2188 mouse_col = 128 * (bytes[1] - ' ' - 1)
2189 + bytes[2] - ' ' - 1;
2190 mouse_row = 128 * (bytes[3] - ' ' - 1)
2191 + bytes[4] - ' ' - 1;
2192 }
2193 else
2194# endif
2195 {
2196 num_bytes = get_bytes_from_buf(tp + *slen, bytes, 3);
2197 if (num_bytes == -1) // not enough coordinates
2198 return -1;
2199 mouse_code = bytes[0];
2200 mouse_col = bytes[1] - ' ' - 1;
2201 mouse_row = bytes[2] - ' ' - 1;
2202 }
2203 *slen += num_bytes;
2204
Bram Moolenaar13c04632020-07-12 13:47:42 +02002205 // If the following bytes is also a mouse code and it has the same
2206 // code, dump this one and get the next. This makes dragging a
2207 // whole lot faster.
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002208# ifdef FEAT_GUI
2209 if (gui.in_use)
2210 j = 3;
2211 else
2212# endif
2213 j = get_termcode_len(idx);
2214 if (STRNCMP(tp, tp + *slen, (size_t)j) == 0
2215 && tp[*slen + j] == mouse_code
2216 && tp[*slen + j + 1] != NUL
2217 && tp[*slen + j + 2] != NUL
2218# ifdef FEAT_GUI
2219 && (!gui.in_use
2220 || (tp[*slen + j + 3] != NUL
2221 && tp[*slen + j + 4] != NUL))
2222# endif
2223 )
2224 *slen += j;
2225 else
2226 break;
2227 }
2228 }
2229
2230 if (key_name[0] == KS_URXVT_MOUSE
2231 || key_name[0] == KS_SGR_MOUSE
2232 || key_name[0] == KS_SGR_MOUSE_RELEASE)
2233 {
2234 // URXVT 1015 mouse reporting mode:
Bram Moolenaar13c04632020-07-12 13:47:42 +02002235 // Almost identical to xterm mouse mode, except the values are decimal
2236 // instead of bytes.
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002237 //
2238 // \033[%d;%d;%dM
2239 // ^-- row
2240 // ^----- column
2241 // ^-------- code
2242 //
2243 // SGR 1006 mouse reporting mode:
Bram Moolenaar13c04632020-07-12 13:47:42 +02002244 // Almost identical to xterm mouse mode, except the values are decimal
2245 // instead of bytes.
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002246 //
2247 // \033[<%d;%d;%dM
2248 // ^-- row
2249 // ^----- column
2250 // ^-------- code
2251 //
2252 // \033[<%d;%d;%dm : mouse release event
2253 // ^-- row
2254 // ^----- column
2255 // ^-------- code
2256 p = modifiers_start;
2257 if (p == NULL)
2258 return -1;
2259
2260 mouse_code = getdigits(&p);
2261 if (*p++ != ';')
2262 return -1;
2263
2264 // when mouse reporting is SGR, add 32 to mouse code
2265 if (key_name[0] == KS_SGR_MOUSE
2266 || key_name[0] == KS_SGR_MOUSE_RELEASE)
2267 mouse_code += 32;
2268
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002269 mouse_col = getdigits(&p) - 1;
2270 if (*p++ != ';')
2271 return -1;
2272
2273 mouse_row = getdigits(&p) - 1;
2274
Bram Moolenaar13c04632020-07-12 13:47:42 +02002275 // The modifiers were the mouse coordinates, not the modifier keys
2276 // (alt/shift/ctrl/meta) state.
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002277 *modifiers = 0;
2278 }
2279
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002280 if (key_name[0] == KS_SGR_MOUSE
2281 || key_name[0] == KS_SGR_MOUSE_RELEASE)
2282 {
2283 if (key_name[0] == KS_SGR_MOUSE_RELEASE)
Bram Moolenaar13c04632020-07-12 13:47:42 +02002284 {
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002285 is_release = TRUE;
Bram Moolenaar13c04632020-07-12 13:47:42 +02002286 // This is used below to set held_button.
2287 mouse_code |= MOUSE_RELEASE;
2288 }
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002289 }
2290 else
2291 {
2292 release_is_ambiguous = TRUE;
2293 if ((mouse_code & MOUSE_RELEASE) == MOUSE_RELEASE)
2294 is_release = TRUE;
2295 }
2296
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002297 if (key_name[0] == KS_MOUSE
2298# ifdef FEAT_MOUSE_GPM
2299 || key_name[0] == KS_GPM_MOUSE
2300# endif
2301# ifdef FEAT_MOUSE_URXVT
2302 || key_name[0] == KS_URXVT_MOUSE
2303# endif
2304 || key_name[0] == KS_SGR_MOUSE
2305 || key_name[0] == KS_SGR_MOUSE_RELEASE)
2306 {
2307# if !defined(MSWIN)
2308 /*
Bram Moolenaar13c04632020-07-12 13:47:42 +02002309 * Handle old style mouse events.
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002310 * Recognize the xterm mouse wheel, but not in the GUI, the
2311 * Linux console with GPM and the MS-DOS or Win32 console
2312 * (multi-clicks use >= 0x60).
2313 */
2314 if (mouse_code >= MOUSEWHEEL_LOW
2315# ifdef FEAT_GUI
2316 && !gui.in_use
2317# endif
2318# ifdef FEAT_MOUSE_GPM
2319 && key_name[0] != KS_GPM_MOUSE
2320# endif
2321 )
2322 {
Bram Moolenaara1cb1d12019-10-17 23:00:07 +02002323# if defined(UNIX)
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002324 if (use_xterm_mouse() > 1 && mouse_code >= 0x80)
2325 // mouse-move event, using MOUSE_DRAG works
2326 mouse_code = MOUSE_DRAG;
2327 else
2328# endif
2329 // Keep the mouse_code before it's changed, so that we
2330 // remember that it was a mouse wheel click.
2331 wheel_code = mouse_code;
2332 }
2333# ifdef FEAT_MOUSE_XTERM
2334 else if (held_button == MOUSE_RELEASE
2335# ifdef FEAT_GUI
2336 && !gui.in_use
2337# endif
2338 && (mouse_code == 0x23 || mouse_code == 0x24
2339 || mouse_code == 0x40 || mouse_code == 0x41))
2340 {
2341 // Apparently 0x23 and 0x24 are used by rxvt scroll wheel.
2342 // And 0x40 and 0x41 are used by some xterm emulator.
2343 wheel_code = mouse_code - (mouse_code >= 0x40 ? 0x40 : 0x23)
2344 + MOUSEWHEEL_LOW;
2345 }
2346# endif
2347
Bram Moolenaara1cb1d12019-10-17 23:00:07 +02002348# if defined(UNIX)
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002349 else if (use_xterm_mouse() > 1)
2350 {
2351 if (mouse_code & MOUSE_DRAG_XTERM)
2352 mouse_code |= MOUSE_DRAG;
2353 }
2354# endif
2355# ifdef FEAT_XCLIPBOARD
2356 else if (!(mouse_code & MOUSE_DRAG & ~MOUSE_CLICK_MASK))
2357 {
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002358 if (is_release)
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002359 stop_xterm_trace();
2360 else
2361 start_xterm_trace(mouse_code);
2362 }
2363# endif
2364# endif
2365 }
2366# endif // !UNIX || FEAT_MOUSE_XTERM
2367# ifdef FEAT_MOUSE_NET
2368 if (key_name[0] == KS_NETTERM_MOUSE)
2369 {
2370 int mc, mr;
2371
2372 // expect a rather limited sequence like: balancing {
2373 // \033}6,45\r
2374 // '6' is the row, 45 is the column
2375 p = tp + *slen;
2376 mr = getdigits(&p);
2377 if (*p++ != ',')
2378 return -1;
2379 mc = getdigits(&p);
2380 if (*p++ != '\r')
2381 return -1;
2382
2383 mouse_col = mc - 1;
2384 mouse_row = mr - 1;
2385 mouse_code = MOUSE_LEFT;
2386 *slen += (int)(p - (tp + *slen));
2387 }
2388# endif // FEAT_MOUSE_NET
2389# ifdef FEAT_MOUSE_JSB
2390 if (key_name[0] == KS_JSBTERM_MOUSE)
2391 {
2392 int mult, val, iter, button, status;
2393
2394 /*
2395 * JSBTERM Input Model
2396 * \033[0~zw uniq escape sequence
2397 * (L-x) Left button pressed - not pressed x not reporting
2398 * (M-x) Middle button pressed - not pressed x not reporting
2399 * (R-x) Right button pressed - not pressed x not reporting
Bram Moolenaar13c04632020-07-12 13:47:42 +02002400 * (SDmdu) Single , Double click, m: mouse move, d: button down,
2401 * u: button up
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002402 * ### X cursor position padded to 3 digits
2403 * ### Y cursor position padded to 3 digits
2404 * (s-x) SHIFT key pressed - not pressed x not reporting
2405 * (c-x) CTRL key pressed - not pressed x not reporting
2406 * \033\\ terminating sequence
2407 */
2408 p = tp + *slen;
2409 button = mouse_code = 0;
2410 switch (*p++)
2411 {
2412 case 'L': button = 1; break;
2413 case '-': break;
2414 case 'x': break; // ignore sequence
2415 default: return -1; // Unknown Result
2416 }
2417 switch (*p++)
2418 {
2419 case 'M': button |= 2; break;
2420 case '-': break;
2421 case 'x': break; // ignore sequence
2422 default: return -1; // Unknown Result
2423 }
2424 switch (*p++)
2425 {
2426 case 'R': button |= 4; break;
2427 case '-': break;
2428 case 'x': break; // ignore sequence
2429 default: return -1; // Unknown Result
2430 }
2431 status = *p++;
2432 for (val = 0, mult = 100, iter = 0; iter < 3; iter++,
2433 mult /= 10, p++)
2434 if (*p >= '0' && *p <= '9')
2435 val += (*p - '0') * mult;
2436 else
2437 return -1;
2438 mouse_col = val;
2439 for (val = 0, mult = 100, iter = 0; iter < 3; iter++,
2440 mult /= 10, p++)
2441 if (*p >= '0' && *p <= '9')
2442 val += (*p - '0') * mult;
2443 else
2444 return -1;
2445 mouse_row = val;
2446 switch (*p++)
2447 {
2448 case 's': button |= 8; break; // SHIFT key Pressed
2449 case '-': break; // Not Pressed
2450 case 'x': break; // Not Reporting
2451 default: return -1; // Unknown Result
2452 }
2453 switch (*p++)
2454 {
2455 case 'c': button |= 16; break; // CTRL key Pressed
2456 case '-': break; // Not Pressed
2457 case 'x': break; // Not Reporting
2458 default: return -1; // Unknown Result
2459 }
2460 if (*p++ != '\033')
2461 return -1;
2462 if (*p++ != '\\')
2463 return -1;
2464 switch (status)
2465 {
2466 case 'D': // Double Click
2467 case 'S': // Single Click
2468 if (button & 1) mouse_code |= MOUSE_LEFT;
2469 if (button & 2) mouse_code |= MOUSE_MIDDLE;
2470 if (button & 4) mouse_code |= MOUSE_RIGHT;
2471 if (button & 8) mouse_code |= MOUSE_SHIFT;
2472 if (button & 16) mouse_code |= MOUSE_CTRL;
2473 break;
2474 case 'm': // Mouse move
2475 if (button & 1) mouse_code |= MOUSE_LEFT;
2476 if (button & 2) mouse_code |= MOUSE_MIDDLE;
2477 if (button & 4) mouse_code |= MOUSE_RIGHT;
2478 if (button & 8) mouse_code |= MOUSE_SHIFT;
2479 if (button & 16) mouse_code |= MOUSE_CTRL;
2480 if ((button & 7) != 0)
2481 {
2482 held_button = mouse_code;
2483 mouse_code |= MOUSE_DRAG;
2484 }
2485 is_drag = TRUE;
2486 showmode();
2487 break;
2488 case 'd': // Button Down
2489 if (button & 1) mouse_code |= MOUSE_LEFT;
2490 if (button & 2) mouse_code |= MOUSE_MIDDLE;
2491 if (button & 4) mouse_code |= MOUSE_RIGHT;
2492 if (button & 8) mouse_code |= MOUSE_SHIFT;
2493 if (button & 16) mouse_code |= MOUSE_CTRL;
2494 break;
2495 case 'u': // Button Up
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002496 is_release = TRUE;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002497 if (button & 1)
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002498 mouse_code |= MOUSE_LEFT;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002499 if (button & 2)
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002500 mouse_code |= MOUSE_MIDDLE;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002501 if (button & 4)
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002502 mouse_code |= MOUSE_RIGHT;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002503 if (button & 8)
2504 mouse_code |= MOUSE_SHIFT;
2505 if (button & 16)
2506 mouse_code |= MOUSE_CTRL;
2507 break;
2508 default: return -1; // Unknown Result
2509 }
2510
2511 *slen += (p - (tp + *slen));
2512 }
2513# endif // FEAT_MOUSE_JSB
2514# ifdef FEAT_MOUSE_DEC
2515 if (key_name[0] == KS_DEC_MOUSE)
2516 {
2517 /*
2518 * The DEC Locator Input Model
2519 * Netterm delivers the code sequence:
2520 * \033[2;4;24;80&w (left button down)
2521 * \033[3;0;24;80&w (left button up)
2522 * \033[6;1;24;80&w (right button down)
2523 * \033[7;0;24;80&w (right button up)
2524 * CSI Pe ; Pb ; Pr ; Pc ; Pp & w
2525 * Pe is the event code
2526 * Pb is the button code
2527 * Pr is the row coordinate
2528 * Pc is the column coordinate
2529 * Pp is the third coordinate (page number)
2530 * Pe, the event code indicates what event caused this report
2531 * The following event codes are defined:
Bram Moolenaar13c04632020-07-12 13:47:42 +02002532 * 0 - request, the terminal received an explicit request for a
2533 * locator report, but the locator is unavailable
2534 * 1 - request, the terminal received an explicit request for a
2535 * locator report
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002536 * 2 - left button down
2537 * 3 - left button up
2538 * 4 - middle button down
2539 * 5 - middle button up
2540 * 6 - right button down
2541 * 7 - right button up
2542 * 8 - fourth button down
2543 * 9 - fourth button up
2544 * 10 - locator outside filter rectangle
Bram Moolenaar13c04632020-07-12 13:47:42 +02002545 * Pb, the button code, ASCII decimal 0-15 indicating which buttons are
2546 * down if any. The state of the four buttons on the locator
2547 * correspond to the low four bits of the decimal value, "1" means
2548 * button depressed
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002549 * 0 - no buttons down,
2550 * 1 - right,
2551 * 2 - middle,
2552 * 4 - left,
2553 * 8 - fourth
2554 * Pr is the row coordinate of the locator position in the page,
Bram Moolenaar13c04632020-07-12 13:47:42 +02002555 * encoded as an ASCII decimal value. If Pr is omitted, the locator
2556 * position is undefined (outside the terminal window for example).
2557 * Pc is the column coordinate of the locator position in the page,
2558 * encoded as an ASCII decimal value. If Pc is omitted, the locator
2559 * position is undefined (outside the terminal window for example).
2560 * Pp is the page coordinate of the locator position encoded as an
2561 * ASCII decimal value. The page coordinate may be omitted if the
2562 * locator is on page one (the default). We ignore it anyway.
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002563 */
2564 int Pe, Pb, Pr, Pc;
2565
2566 p = tp + *slen;
2567
2568 // get event status
2569 Pe = getdigits(&p);
2570 if (*p++ != ';')
2571 return -1;
2572
2573 // get button status
2574 Pb = getdigits(&p);
2575 if (*p++ != ';')
2576 return -1;
2577
2578 // get row status
2579 Pr = getdigits(&p);
2580 if (*p++ != ';')
2581 return -1;
2582
2583 // get column status
2584 Pc = getdigits(&p);
2585
2586 // the page parameter is optional
2587 if (*p == ';')
2588 {
2589 p++;
2590 (void)getdigits(&p);
2591 }
2592 if (*p++ != '&')
2593 return -1;
2594 if (*p++ != 'w')
2595 return -1;
2596
2597 mouse_code = 0;
2598 switch (Pe)
2599 {
2600 case 0: return -1; // position request while unavailable
2601 case 1: // a response to a locator position request includes
2602 // the status of all buttons
2603 Pb &= 7; // mask off and ignore fourth button
2604 if (Pb & 4)
2605 mouse_code = MOUSE_LEFT;
2606 if (Pb & 2)
2607 mouse_code = MOUSE_MIDDLE;
2608 if (Pb & 1)
2609 mouse_code = MOUSE_RIGHT;
2610 if (Pb)
2611 {
2612 held_button = mouse_code;
2613 mouse_code |= MOUSE_DRAG;
2614 WantQueryMouse = TRUE;
2615 }
2616 is_drag = TRUE;
2617 showmode();
2618 break;
2619 case 2: mouse_code = MOUSE_LEFT;
2620 WantQueryMouse = TRUE;
2621 break;
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002622 case 3: mouse_code = MOUSE_LEFT;
2623 is_release = TRUE;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002624 break;
2625 case 4: mouse_code = MOUSE_MIDDLE;
2626 WantQueryMouse = TRUE;
2627 break;
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002628 case 5: mouse_code = MOUSE_MIDDLE;
2629 is_release = TRUE;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002630 break;
2631 case 6: mouse_code = MOUSE_RIGHT;
2632 WantQueryMouse = TRUE;
2633 break;
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002634 case 7: mouse_code = MOUSE_RIGHT;
2635 is_release = TRUE;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002636 break;
2637 case 8: return -1; // fourth button down
2638 case 9: return -1; // fourth button up
2639 case 10: return -1; // mouse outside of filter rectangle
2640 default: return -1; // should never occur
2641 }
2642
2643 mouse_col = Pc - 1;
2644 mouse_row = Pr - 1;
2645
2646 *slen += (int)(p - (tp + *slen));
2647 }
2648# endif // FEAT_MOUSE_DEC
2649# ifdef FEAT_MOUSE_PTERM
2650 if (key_name[0] == KS_PTERM_MOUSE)
2651 {
2652 int button, num_clicks, action;
2653
2654 p = tp + *slen;
2655
2656 action = getdigits(&p);
2657 if (*p++ != ';')
2658 return -1;
2659
2660 mouse_row = getdigits(&p);
2661 if (*p++ != ';')
2662 return -1;
2663 mouse_col = getdigits(&p);
2664 if (*p++ != ';')
2665 return -1;
2666
2667 button = getdigits(&p);
2668 mouse_code = 0;
2669
2670 switch (button)
2671 {
2672 case 4: mouse_code = MOUSE_LEFT; break;
2673 case 1: mouse_code = MOUSE_RIGHT; break;
2674 case 2: mouse_code = MOUSE_MIDDLE; break;
2675 default: return -1;
2676 }
2677
2678 switch (action)
2679 {
2680 case 31: // Initial press
2681 if (*p++ != ';')
2682 return -1;
2683
2684 num_clicks = getdigits(&p); // Not used
2685 break;
2686
2687 case 32: // Release
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002688 is_release = TRUE;
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002689 break;
2690
2691 case 33: // Drag
2692 held_button = mouse_code;
2693 mouse_code |= MOUSE_DRAG;
2694 break;
2695
2696 default:
2697 return -1;
2698 }
2699
2700 if (*p++ != 't')
2701 return -1;
2702
2703 *slen += (p - (tp + *slen));
2704 }
2705# endif // FEAT_MOUSE_PTERM
2706
2707 // Interpret the mouse code
2708 current_button = (mouse_code & MOUSE_CLICK_MASK);
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002709 if (is_release)
2710 current_button |= MOUSE_RELEASE;
2711
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002712 if (current_button == MOUSE_RELEASE
2713# ifdef FEAT_MOUSE_XTERM
2714 && wheel_code == 0
2715# endif
2716 )
2717 {
2718 /*
Bram Moolenaar13c04632020-07-12 13:47:42 +02002719 * If we get a mouse drag or release event when there is no mouse
2720 * button held down (held_button == MOUSE_RELEASE), produce a K_IGNORE
2721 * below.
2722 * (can happen when you hold down two buttons and then let them go, or
2723 * click in the menu bar, but not on a menu, and drag into the text).
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002724 */
2725 if ((mouse_code & MOUSE_DRAG) == MOUSE_DRAG)
2726 is_drag = TRUE;
2727 current_button = held_button;
2728 }
2729 else if (wheel_code == 0)
2730 {
2731# ifdef CHECK_DOUBLE_CLICK
2732# ifdef FEAT_MOUSE_GPM
2733 /*
Bram Moolenaar13c04632020-07-12 13:47:42 +02002734 * Only for Unix, when GUI not active, we handle multi-clicks here, but
2735 * not for GPM mouse events.
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002736 */
2737# ifdef FEAT_GUI
2738 if (key_name[0] != KS_GPM_MOUSE && !gui.in_use)
2739# else
2740 if (key_name[0] != KS_GPM_MOUSE)
2741# endif
2742# else
2743# ifdef FEAT_GUI
2744 if (!gui.in_use)
2745# endif
2746# endif
2747 {
2748 /*
2749 * Compute the time elapsed since the previous mouse click.
2750 */
2751 gettimeofday(&mouse_time, NULL);
2752 if (orig_mouse_time.tv_sec == 0)
2753 {
2754 /*
2755 * Avoid computing the difference between mouse_time
2756 * and orig_mouse_time for the first click, as the
2757 * difference would be huge and would cause
2758 * multiplication overflow.
2759 */
2760 timediff = p_mouset;
2761 }
2762 else
Bram Moolenaar85c35022019-11-22 22:21:59 +01002763 timediff = time_diff_ms(&orig_mouse_time, &mouse_time);
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002764 orig_mouse_time = mouse_time;
2765 if (mouse_code == orig_mouse_code
2766 && timediff < p_mouset
2767 && orig_num_clicks != 4
2768 && orig_mouse_col == mouse_col
2769 && orig_mouse_row == mouse_row
2770 && (is_mouse_topline(curwin)
2771 // Double click in tab pages line also works
2772 // when window contents changes.
2773 || (mouse_row == 0 && firstwin->w_winrow > 0))
2774 )
2775 ++orig_num_clicks;
2776 else
2777 orig_num_clicks = 1;
2778 orig_mouse_col = mouse_col;
2779 orig_mouse_row = mouse_row;
2780 set_mouse_topline(curwin);
2781 }
2782# if defined(FEAT_GUI) || defined(FEAT_MOUSE_GPM)
2783 else
2784 orig_num_clicks = NUM_MOUSE_CLICKS(mouse_code);
2785# endif
2786# else
2787 orig_num_clicks = NUM_MOUSE_CLICKS(mouse_code);
2788# endif
2789 is_click = TRUE;
2790 orig_mouse_code = mouse_code;
2791 }
2792 if (!is_drag)
2793 held_button = mouse_code & MOUSE_CLICK_MASK;
2794
2795 /*
2796 * Translate the actual mouse event into a pseudo mouse event.
2797 * First work out what modifiers are to be used.
2798 */
2799 if (orig_mouse_code & MOUSE_SHIFT)
2800 *modifiers |= MOD_MASK_SHIFT;
2801 if (orig_mouse_code & MOUSE_CTRL)
2802 *modifiers |= MOD_MASK_CTRL;
2803 if (orig_mouse_code & MOUSE_ALT)
2804 *modifiers |= MOD_MASK_ALT;
2805 if (orig_num_clicks == 2)
2806 *modifiers |= MOD_MASK_2CLICK;
2807 else if (orig_num_clicks == 3)
2808 *modifiers |= MOD_MASK_3CLICK;
2809 else if (orig_num_clicks == 4)
2810 *modifiers |= MOD_MASK_4CLICK;
2811
Bram Moolenaar13c04632020-07-12 13:47:42 +02002812 // Work out our pseudo mouse event. Note that MOUSE_RELEASE gets added,
2813 // then it's not mouse up/down.
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002814 key_name[0] = KS_EXTRA;
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002815 if (wheel_code != 0 && (!is_release || release_is_ambiguous))
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002816 {
2817 if (wheel_code & MOUSE_CTRL)
2818 *modifiers |= MOD_MASK_CTRL;
2819 if (wheel_code & MOUSE_ALT)
2820 *modifiers |= MOD_MASK_ALT;
Bram Moolenaard58d4f92020-07-01 15:49:29 +02002821
2822 if (wheel_code & 1 && wheel_code & 2)
2823 key_name[1] = (int)KE_MOUSELEFT;
2824 else if (wheel_code & 2)
2825 key_name[1] = (int)KE_MOUSERIGHT;
2826 else if (wheel_code & 1)
2827 key_name[1] = (int)KE_MOUSEUP;
2828 else
2829 key_name[1] = (int)KE_MOUSEDOWN;
2830
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002831 held_button = MOUSE_RELEASE;
2832 }
2833 else
Bram Moolenaar13c04632020-07-12 13:47:42 +02002834 key_name[1] = get_pseudo_mouse_code(current_button, is_click, is_drag);
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002835
Bram Moolenaar13c04632020-07-12 13:47:42 +02002836
2837 // Make sure the mouse position is valid. Some terminals may return weird
2838 // values.
Bram Moolenaarb8ff5c22019-09-23 21:16:54 +02002839 if (mouse_col >= Columns)
2840 mouse_col = Columns - 1;
2841 if (mouse_row >= Rows)
2842 mouse_row = Rows - 1;
2843
2844 return 0;
2845}
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002846
2847// Functions also used for popup windows.
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002848
2849/*
2850 * Compute the buffer line position from the screen position "rowp" / "colp" in
2851 * window "win".
Bram Moolenaar452143c2020-07-15 17:38:21 +02002852 * "plines_cache" can be NULL (no cache) or an array with "Rows" entries that
2853 * caches the plines_win() result from a previous call. Entry is zero if not
2854 * computed yet. There must be no text or setting changes since the entry is
2855 * put in the cache.
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002856 * Returns TRUE if the position is below the last line.
2857 */
2858 int
2859mouse_comp_pos(
2860 win_T *win,
2861 int *rowp,
2862 int *colp,
2863 linenr_T *lnump,
2864 int *plines_cache)
2865{
2866 int col = *colp;
2867 int row = *rowp;
2868 linenr_T lnum;
2869 int retval = FALSE;
2870 int off;
2871 int count;
2872
2873#ifdef FEAT_RIGHTLEFT
2874 if (win->w_p_rl)
2875 col = win->w_width - 1 - col;
2876#endif
2877
2878 lnum = win->w_topline;
2879
2880 while (row > 0)
2881 {
2882 int cache_idx = lnum - win->w_topline;
2883
Bram Moolenaar452143c2020-07-15 17:38:21 +02002884 // Only "Rows" lines are cached, with folding we'll run out of entries
2885 // and use the slow way.
2886 if (plines_cache != NULL && cache_idx < Rows
2887 && plines_cache[cache_idx] > 0)
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002888 count = plines_cache[cache_idx];
2889 else
2890 {
2891#ifdef FEAT_DIFF
2892 // Don't include filler lines in "count"
2893 if (win->w_p_diff
2894# ifdef FEAT_FOLDING
2895 && !hasFoldingWin(win, lnum, NULL, NULL, TRUE, NULL)
2896# endif
2897 )
2898 {
2899 if (lnum == win->w_topline)
2900 row -= win->w_topfill;
2901 else
2902 row -= diff_check_fill(win, lnum);
2903 count = plines_win_nofill(win, lnum, TRUE);
2904 }
2905 else
2906#endif
2907 count = plines_win(win, lnum, TRUE);
Bram Moolenaar452143c2020-07-15 17:38:21 +02002908 if (plines_cache != NULL && cache_idx < Rows)
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002909 plines_cache[cache_idx] = count;
2910 }
2911 if (count > row)
2912 break; // Position is in this buffer line.
2913#ifdef FEAT_FOLDING
2914 (void)hasFoldingWin(win, lnum, NULL, &lnum, TRUE, NULL);
2915#endif
2916 if (lnum == win->w_buffer->b_ml.ml_line_count)
2917 {
2918 retval = TRUE;
2919 break; // past end of file
2920 }
2921 row -= count;
2922 ++lnum;
2923 }
2924
2925 if (!retval)
2926 {
2927 // Compute the column without wrapping.
2928 off = win_col_off(win) - win_col_off2(win);
2929 if (col < off)
2930 col = off;
2931 col += row * (win->w_width - off);
2932 // add skip column (for long wrapping line)
2933 col += win->w_skipcol;
2934 }
2935
2936 if (!win->w_p_wrap)
2937 col += win->w_leftcol;
2938
2939 // skip line number and fold column in front of the line
2940 col -= win_col_off(win);
Bram Moolenaardbfa7952020-11-02 20:04:22 +01002941 if (col <= 0)
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002942 {
2943#ifdef FEAT_NETBEANS_INTG
Bram Moolenaardbfa7952020-11-02 20:04:22 +01002944 // if mouse is clicked on the gutter, then inform the netbeans server
2945 if (*colp < win_col_off(win))
2946 netbeans_gutter_click(lnum);
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002947#endif
2948 col = 0;
2949 }
2950
2951 *colp = col;
2952 *rowp = row;
2953 *lnump = lnum;
2954 return retval;
2955}
2956
2957/*
2958 * Find the window at screen position "*rowp" and "*colp". The positions are
2959 * updated to become relative to the top-left of the window.
2960 * When "popup" is FAIL_POPUP and the position is in a popup window then NULL
2961 * is returned. When "popup" is IGNORE_POPUP then do not even check popup
2962 * windows.
2963 * Returns NULL when something is wrong.
2964 */
2965 win_T *
2966mouse_find_win(int *rowp, int *colp, mouse_find_T popup UNUSED)
2967{
2968 frame_T *fp;
2969 win_T *wp;
2970
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01002971#ifdef FEAT_PROP_POPUP
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002972 win_T *pwp = NULL;
2973
2974 if (popup != IGNORE_POPUP)
2975 {
Bram Moolenaarafe45b62019-11-13 22:35:19 +01002976 popup_reset_handled(POPUP_HANDLED_1);
2977 while ((wp = find_next_popup(TRUE, POPUP_HANDLED_1)) != NULL)
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02002978 {
2979 if (*rowp >= wp->w_winrow && *rowp < wp->w_winrow + popup_height(wp)
2980 && *colp >= wp->w_wincol
2981 && *colp < wp->w_wincol + popup_width(wp))
2982 pwp = wp;
2983 }
2984 if (pwp != NULL)
2985 {
2986 if (popup == FAIL_POPUP)
2987 return NULL;
2988 *rowp -= pwp->w_winrow;
2989 *colp -= pwp->w_wincol;
2990 return pwp;
2991 }
2992 }
2993#endif
2994
2995 fp = topframe;
2996 *rowp -= firstwin->w_winrow;
2997 for (;;)
2998 {
2999 if (fp->fr_layout == FR_LEAF)
3000 break;
3001 if (fp->fr_layout == FR_ROW)
3002 {
3003 for (fp = fp->fr_child; fp->fr_next != NULL; fp = fp->fr_next)
3004 {
3005 if (*colp < fp->fr_width)
3006 break;
3007 *colp -= fp->fr_width;
3008 }
3009 }
3010 else // fr_layout == FR_COL
3011 {
3012 for (fp = fp->fr_child; fp->fr_next != NULL; fp = fp->fr_next)
3013 {
3014 if (*rowp < fp->fr_height)
3015 break;
3016 *rowp -= fp->fr_height;
3017 }
3018 }
3019 }
3020 // When using a timer that closes a window the window might not actually
3021 // exist.
3022 FOR_ALL_WINDOWS(wp)
3023 if (wp == fp->fr_win)
3024 {
3025#ifdef FEAT_MENU
3026 *rowp -= wp->w_winbar_height;
3027#endif
3028 return wp;
3029 }
3030 return NULL;
3031}
3032
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01003033#if defined(NEED_VCOL2COL) || defined(FEAT_BEVAL) || defined(FEAT_PROP_POPUP) \
Bram Moolenaarb20b9e12019-09-21 20:48:04 +02003034 || defined(PROTO)
3035/*
3036 * Convert a virtual (screen) column to a character column.
3037 * The first column is one.
3038 */
3039 int
3040vcol2col(win_T *wp, linenr_T lnum, int vcol)
3041{
3042 // try to advance to the specified column
3043 int count = 0;
3044 char_u *ptr;
3045 char_u *line;
3046
3047 line = ptr = ml_get_buf(wp->w_buffer, lnum, FALSE);
3048 while (count < vcol && *ptr != NUL)
3049 {
3050 count += win_lbr_chartabsize(wp, line, ptr, count, NULL);
3051 MB_PTR_ADV(ptr);
3052 }
3053 return (int)(ptr - line);
3054}
3055#endif
Bram Moolenaardb3a2052019-11-16 18:22:41 +01003056
3057#if defined(FEAT_EVAL) || defined(PROTO)
3058 void
3059f_getmousepos(typval_T *argvars UNUSED, typval_T *rettv)
3060{
3061 dict_T *d;
3062 win_T *wp;
3063 int row = mouse_row;
3064 int col = mouse_col;
3065 varnumber_T winid = 0;
3066 varnumber_T winrow = 0;
3067 varnumber_T wincol = 0;
Bram Moolenaar533870a2022-03-13 15:52:44 +00003068 linenr_T lnum = 0;
Bram Moolenaardb3a2052019-11-16 18:22:41 +01003069 varnumber_T column = 0;
3070
3071 if (rettv_dict_alloc(rettv) != OK)
3072 return;
3073 d = rettv->vval.v_dict;
3074
3075 dict_add_number(d, "screenrow", (varnumber_T)mouse_row + 1);
3076 dict_add_number(d, "screencol", (varnumber_T)mouse_col + 1);
3077
3078 wp = mouse_find_win(&row, &col, FIND_POPUP);
3079 if (wp != NULL)
3080 {
3081 int top_off = 0;
3082 int left_off = 0;
3083 int height = wp->w_height + wp->w_status_height;
3084
Bram Moolenaar05ad5ff2019-11-30 22:48:27 +01003085#ifdef FEAT_PROP_POPUP
Bram Moolenaardb3a2052019-11-16 18:22:41 +01003086 if (WIN_IS_POPUP(wp))
3087 {
3088 top_off = popup_top_extra(wp);
3089 left_off = popup_left_extra(wp);
3090 height = popup_height(wp);
3091 }
3092#endif
3093 if (row < height)
3094 {
3095 winid = wp->w_id;
3096 winrow = row + 1;
3097 wincol = col + 1;
3098 row -= top_off;
3099 col -= left_off;
3100 if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width)
3101 {
Bram Moolenaar533870a2022-03-13 15:52:44 +00003102 if (!mouse_comp_pos(wp, &row, &col, &lnum, NULL))
3103 col = vcol2col(wp, lnum, col);
Bram Moolenaardb3a2052019-11-16 18:22:41 +01003104 column = col + 1;
3105 }
3106 }
3107 }
3108 dict_add_number(d, "winid", winid);
3109 dict_add_number(d, "winrow", winrow);
3110 dict_add_number(d, "wincol", wincol);
Bram Moolenaar533870a2022-03-13 15:52:44 +00003111 dict_add_number(d, "line", (varnumber_T)lnum);
Bram Moolenaardb3a2052019-11-16 18:22:41 +01003112 dict_add_number(d, "column", column);
3113}
3114#endif