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