mouse.c (63147B)
1 #include <assert.h> 2 #include <stdbool.h> 3 #include <stdint.h> 4 #include <stdlib.h> 5 #include <string.h> 6 7 #include "nvim/ascii_defs.h" 8 #include "nvim/buffer.h" 9 #include "nvim/buffer_defs.h" 10 #include "nvim/charset.h" 11 #include "nvim/cursor.h" 12 #include "nvim/decoration.h" 13 #include "nvim/drawscreen.h" 14 #include "nvim/edit.h" 15 #include "nvim/eval.h" 16 #include "nvim/eval/typval.h" 17 #include "nvim/ex_docmd.h" 18 #include "nvim/fold.h" 19 #include "nvim/getchar.h" 20 #include "nvim/globals.h" 21 #include "nvim/grid.h" 22 #include "nvim/grid_defs.h" 23 #include "nvim/keycodes.h" 24 #include "nvim/macros_defs.h" 25 #include "nvim/mark_defs.h" 26 #include "nvim/mbyte.h" 27 #include "nvim/mbyte_defs.h" 28 #include "nvim/memline.h" 29 #include "nvim/memory.h" 30 #include "nvim/menu.h" 31 #include "nvim/message.h" 32 #include "nvim/mouse.h" 33 #include "nvim/move.h" 34 #include "nvim/normal.h" 35 #include "nvim/ops.h" 36 #include "nvim/option.h" 37 #include "nvim/option_vars.h" 38 #include "nvim/plines.h" 39 #include "nvim/popupmenu.h" 40 #include "nvim/pos_defs.h" 41 #include "nvim/register.h" 42 #include "nvim/search.h" 43 #include "nvim/state.h" 44 #include "nvim/state_defs.h" 45 #include "nvim/statusline.h" 46 #include "nvim/statusline_defs.h" 47 #include "nvim/strings.h" 48 #include "nvim/types_defs.h" 49 #include "nvim/ui.h" 50 #include "nvim/ui_compositor.h" 51 #include "nvim/vim_defs.h" 52 #include "nvim/window.h" 53 54 #include "mouse.c.generated.h" 55 56 static linenr_T orig_topline = 0; 57 static int orig_topfill = 0; 58 59 /// Get class of a character for selection: same class means same word. 60 /// 0: blank 61 /// 1: punctuation groups 62 /// 2: normal word character 63 /// >2: multi-byte word character. 64 static int get_mouse_class(char *p) 65 { 66 if (MB_BYTE2LEN((uint8_t)p[0]) > 1) { 67 return mb_get_class(p); 68 } 69 70 const int c = (uint8_t)(*p); 71 if (c == ' ' || c == '\t') { 72 return 0; 73 } 74 if (vim_iswordc(c)) { 75 return 2; 76 } 77 78 // There are a few special cases where we want certain combinations of 79 // characters to be considered as a single word. These are things like 80 // "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each 81 // character is in its own class. 82 if (c != NUL && vim_strchr("-+*/%<>&|^!=", c) != NULL) { 83 return 1; 84 } 85 return c; 86 } 87 88 /// Move "pos" back to the start of the word it's in. 89 static void find_start_of_word(pos_T *pos) 90 { 91 char *line = ml_get(pos->lnum); 92 int cclass = get_mouse_class(line + pos->col); 93 94 while (pos->col > 0) { 95 int col = pos->col - 1; 96 col -= utf_head_off(line, line + col); 97 if (get_mouse_class(line + col) != cclass) { 98 break; 99 } 100 pos->col = col; 101 } 102 } 103 104 /// Move "pos" forward to the end of the word it's in. 105 /// When 'selection' is "exclusive", the position is just after the word. 106 static void find_end_of_word(pos_T *pos) 107 { 108 char *line = ml_get(pos->lnum); 109 if (*p_sel == 'e' && pos->col > 0) { 110 pos->col--; 111 pos->col -= utf_head_off(line, line + pos->col); 112 } 113 int cclass = get_mouse_class(line + pos->col); 114 while (line[pos->col] != NUL) { 115 int col = pos->col + utfc_ptr2len(line + pos->col); 116 if (get_mouse_class(line + col) != cclass) { 117 if (*p_sel == 'e') { 118 pos->col = col; 119 } 120 break; 121 } 122 pos->col = col; 123 } 124 } 125 126 /// Move the current tab to tab in same column as mouse or to end of the 127 /// tabline if there is no tab there. 128 static void move_tab_to_mouse(void) 129 { 130 int tabnr = tab_page_click_defs[mouse_col].tabnr; 131 if (tabnr <= 0) { 132 tabpage_move(9999); 133 } else if (tabnr < tabpage_index(curtab)) { 134 tabpage_move(tabnr - 1); 135 } else { 136 tabpage_move(tabnr); 137 } 138 } 139 /// Close the current or specified tab page. 140 /// 141 /// @param c1 tabpage number, or 999 for the current tabpage 142 static void mouse_tab_close(int c1) 143 { 144 tabpage_T *tp; 145 146 if (c1 == 999) { 147 tp = curtab; 148 } else { 149 tp = find_tabpage(c1); 150 } 151 if (tp == curtab) { 152 if (first_tabpage->tp_next != NULL) { 153 tabpage_close(false); 154 } 155 } else if (tp != NULL) { 156 tabpage_close_other(tp, false); 157 } 158 } 159 160 static bool got_click = false; // got a click some time back 161 162 /// Call click definition function for column "col" in the "click_defs" array for button 163 /// "which_button". 164 static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button) 165 { 166 typval_T argv[] = { 167 { 168 .v_lock = VAR_FIXED, 169 .v_type = VAR_NUMBER, 170 .vval = { 171 .v_number = (varnumber_T)click_defs[col].tabnr 172 }, 173 }, 174 { 175 .v_lock = VAR_FIXED, 176 .v_type = VAR_NUMBER, 177 .vval = { 178 .v_number = ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK 179 ? 4 180 : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK 181 ? 3 182 : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK 183 ? 2 184 : 1))) 185 }, 186 }, 187 { 188 .v_lock = VAR_FIXED, 189 .v_type = VAR_STRING, 190 .vval = { 191 .v_string = (which_button == MOUSE_LEFT 192 ? "l" 193 : (which_button == MOUSE_RIGHT 194 ? "r" 195 : (which_button == MOUSE_MIDDLE 196 ? "m" 197 : (which_button == MOUSE_X1 198 ? "x1" 199 : (which_button == MOUSE_X2 200 ? "x2" 201 : "?"))))) 202 }, 203 }, 204 { 205 .v_lock = VAR_FIXED, 206 .v_type = VAR_STRING, 207 .vval = { 208 .v_string = (char[]) { 209 (char)(mod_mask & MOD_MASK_SHIFT ? 's' : ' '), 210 (char)(mod_mask & MOD_MASK_CTRL ? 'c' : ' '), 211 (char)(mod_mask & MOD_MASK_ALT ? 'a' : ' '), 212 (char)(mod_mask & MOD_MASK_META ? 'm' : ' '), 213 NUL 214 } 215 }, 216 } 217 }; 218 typval_T rettv; 219 call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv); 220 tv_clear(&rettv); 221 // Make sure next click does not register as drag when callback absorbs the release event. 222 got_click = false; 223 } 224 225 /// Translate window coordinates to buffer position without any side effects. 226 /// Returns IN_BUFFER and sets "mpos->col" to the column when in buffer text. 227 /// The column is one for the first column. 228 static int get_fpos_of_mouse(pos_T *mpos) 229 { 230 int grid = mouse_grid; 231 int row = mouse_row; 232 int col = mouse_col; 233 234 if (row < 0 || col < 0) { // check if it makes sense 235 return IN_UNKNOWN; 236 } 237 238 // find the window where the row is in 239 win_T *wp = mouse_find_win_inner(&grid, &row, &col); 240 if (wp == NULL) { 241 return IN_UNKNOWN; 242 } 243 int winrow = row; 244 int wincol = col; 245 246 // compute the position in the buffer line from the posn on the screen 247 bool below_buffer = mouse_comp_pos(wp, &row, &col, &mpos->lnum); 248 249 if (!below_buffer && *wp->w_p_stc != NUL 250 && (wp->w_p_rl 251 ? wincol >= wp->w_view_width - win_col_off(wp) 252 : wincol < win_col_off(wp))) { 253 return MOUSE_STATUSCOL; 254 } 255 256 // winpos and height may change in win_enter()! 257 if (winrow >= wp->w_view_height + wp->w_status_height) { // Below window 258 if (mouse_grid <= 1 && mouse_row < Rows - p_ch 259 && mouse_row >= Rows - p_ch - global_stl_height()) { // In global status line 260 return IN_STATUS_LINE; 261 } 262 return IN_UNKNOWN; 263 } else if (winrow >= wp->w_view_height) { // In window status line 264 return IN_STATUS_LINE; 265 } 266 267 if (winrow < 0 && winrow + wp->w_winbar_height >= 0) { // In winbar 268 return MOUSE_WINBAR; 269 } 270 271 if (wincol >= wp->w_view_width) { // In vertical separator line 272 return IN_SEP_LINE; 273 } 274 275 if (wp != curwin || below_buffer) { 276 return IN_UNKNOWN; 277 } 278 279 mpos->col = vcol2col(wp, mpos->lnum, col, &mpos->coladd); 280 return IN_BUFFER; 281 } 282 283 static int do_popup(int which_button, int m_pos_flag, pos_T m_pos) 284 { 285 int jump_flags = 0; 286 if (strcmp(p_mousem, "popup_setpos") == 0) { 287 // First set the cursor position before showing the popup menu. 288 if (VIsual_active) { 289 // set MOUSE_MAY_STOP_VIS if we are outside the selection 290 // or the current window (might have false negative here) 291 if (m_pos_flag != IN_BUFFER) { 292 jump_flags = MOUSE_MAY_STOP_VIS; 293 } else { 294 if (VIsual_mode == 'V') { 295 if ((curwin->w_cursor.lnum <= VIsual.lnum 296 && (m_pos.lnum < curwin->w_cursor.lnum || VIsual.lnum < m_pos.lnum)) 297 || (VIsual.lnum < curwin->w_cursor.lnum 298 && (m_pos.lnum < VIsual.lnum || curwin->w_cursor.lnum < m_pos.lnum))) { 299 jump_flags = MOUSE_MAY_STOP_VIS; 300 } 301 } else if ((ltoreq(curwin->w_cursor, VIsual) 302 && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) 303 || (lt(VIsual, curwin->w_cursor) 304 && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { 305 jump_flags = MOUSE_MAY_STOP_VIS; 306 } else if (VIsual_mode == Ctrl_V) { 307 colnr_T leftcol, rightcol; 308 getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); 309 getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); 310 if (m_pos.col < leftcol || m_pos.col > rightcol) { 311 jump_flags = MOUSE_MAY_STOP_VIS; 312 } 313 } 314 } 315 } else { 316 jump_flags = MOUSE_MAY_STOP_VIS; 317 } 318 } 319 if (jump_flags) { 320 jump_flags = jump_to_mouse(jump_flags, NULL, which_button); 321 redraw_curbuf_later(VIsual_active ? UPD_INVERTED : UPD_VALID); 322 update_screen(); 323 setcursor(); 324 ui_flush(); // Update before showing popup menu 325 } 326 show_popupmenu(); 327 got_click = false; // ignore release events 328 return jump_flags; 329 } 330 331 /// Do the appropriate action for the current mouse click in the current mode. 332 /// Not used for Command-line mode. 333 /// 334 /// Normal and Visual Mode: 335 /// event modi- position visual change action 336 /// fier cursor window 337 /// left press - yes end yes 338 /// left press C yes end yes "^]" (2) 339 /// left press S yes end (popup: extend) yes "*" (2) 340 /// left drag - yes start if moved no 341 /// left relse - yes start if moved no 342 /// middle press - yes if not active no put register 343 /// middle press - yes if active no yank and put 344 /// right press - yes start or extend yes 345 /// right press S yes no change yes "#" (2) 346 /// right drag - yes extend no 347 /// right relse - yes extend no 348 /// 349 /// Insert or Replace Mode: 350 /// event modi- position visual change action 351 /// fier cursor window 352 /// left press - yes (cannot be active) yes 353 /// left press C yes (cannot be active) yes "CTRL-O^]" (2) 354 /// left press S yes (cannot be active) yes "CTRL-O*" (2) 355 /// left drag - yes start or extend (1) no CTRL-O (1) 356 /// left relse - yes start or extend (1) no CTRL-O (1) 357 /// middle press - no (cannot be active) no put register 358 /// right press - yes start or extend yes CTRL-O 359 /// right press S yes (cannot be active) yes "CTRL-O#" (2) 360 /// 361 /// (1) only if mouse pointer moved since press 362 /// (2) only if click is in same buffer 363 /// 364 /// @param oap operator argument, can be NULL 365 /// @param c K_LEFTMOUSE, etc 366 /// @param dir Direction to 'put' if necessary 367 /// @param fixindent PUT_FIXINDENT if fixing indent necessary 368 /// 369 /// @return true if start_arrow() should be called for edit mode. 370 bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) 371 { 372 int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT 373 bool is_click; // If false it's a drag or release event 374 bool is_drag; // If true it's a drag event 375 static bool in_tab_line = false; // mouse clicked in tab line 376 static pos_T orig_cursor; 377 378 while (true) { 379 which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); 380 if (is_drag) { 381 // If the next character is the same mouse event then use that 382 // one. Speeds up dragging the status line. 383 // Note: Since characters added to the stuff buffer in the code 384 // below need to come before the next character, do not do this 385 // when the current character was stuffed. 386 if (!KeyStuffed && vpeekc() != NUL) { 387 int nc; 388 int save_mouse_grid = mouse_grid; 389 int save_mouse_row = mouse_row; 390 int save_mouse_col = mouse_col; 391 392 // Need to get the character, peeking doesn't get the actual one. 393 nc = safe_vgetc(); 394 if (c == nc) { 395 continue; 396 } 397 vungetc(nc); 398 mouse_grid = save_mouse_grid; 399 mouse_row = save_mouse_row; 400 mouse_col = save_mouse_col; 401 } 402 } 403 break; 404 } 405 406 if (c == K_MOUSEMOVE) { 407 // Mouse moved without a button pressed. 408 return false; 409 } 410 411 // Ignore drag and release events if we didn't get a click. 412 if (is_click) { 413 got_click = true; 414 } else { 415 if (!got_click) { // didn't get click, ignore 416 return false; 417 } 418 if (!is_drag) { // release, reset got_click 419 got_click = false; 420 if (in_tab_line) { 421 in_tab_line = false; 422 return false; 423 } 424 } 425 } 426 427 // CTRL right mouse button does CTRL-T 428 if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT) { 429 if (State & MODE_INSERT) { 430 stuffcharReadbuff(Ctrl_O); 431 } 432 if (count > 1) { 433 stuffnumReadbuff(count); 434 } 435 stuffcharReadbuff(Ctrl_T); 436 got_click = false; // ignore drag&release now 437 return false; 438 } 439 440 // CTRL only works with left mouse button 441 if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT) { 442 return false; 443 } 444 445 // When a modifier is down, ignore drag and release events, as well as 446 // multiple clicks and the middle mouse button. 447 // Accept shift-leftmouse drags when 'mousemodel' is "popup.*". 448 if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT 449 | MOD_MASK_META)) 450 && (!is_click 451 || (mod_mask & MOD_MASK_MULTI_CLICK) 452 || which_button == MOUSE_MIDDLE) 453 && !((mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)) 454 && mouse_model_popup() 455 && which_button == MOUSE_LEFT) 456 && !((mod_mask & MOD_MASK_ALT) 457 && !mouse_model_popup() 458 && which_button == MOUSE_RIGHT)) { 459 return false; 460 } 461 462 // If the button press was used as the movement command for an operator (eg 463 // "d<MOUSE>"), or it is the middle button that is held down, ignore 464 // drag/release events. 465 if (!is_click && which_button == MOUSE_MIDDLE) { 466 return false; 467 } 468 469 int regname = oap != NULL ? oap->regname : 0; 470 // Middle mouse button does a 'put' of the selected text 471 if (which_button == MOUSE_MIDDLE) { 472 if (State == MODE_NORMAL) { 473 // If an operator was pending, we don't know what the user wanted to do. 474 // Go back to normal mode: Clear the operator and beep(). 475 if (oap != NULL && oap->op_type != OP_NOP) { 476 clearopbeep(oap); 477 return false; 478 } 479 480 // If visual was active, yank the highlighted text and put it 481 // before the mouse pointer position. 482 // In Select mode replace the highlighted text with the clipboard. 483 if (VIsual_active) { 484 if (VIsual_select) { 485 stuffcharReadbuff(Ctrl_G); 486 stuffReadbuff("\"+p"); 487 } else { 488 stuffcharReadbuff('y'); 489 stuffcharReadbuff(K_MIDDLEMOUSE); 490 } 491 return false; 492 } 493 // The rest is below jump_to_mouse() 494 } else if ((State & MODE_INSERT) == 0) { 495 return false; 496 } 497 498 // Middle click in insert mode doesn't move the mouse, just insert the 499 // contents of a register. '.' register is special, can't insert that 500 // with do_put(). 501 // Also paste at the cursor if the current mode isn't in 'mouse' (only 502 // happens for the GUI). 503 if ((State & MODE_INSERT)) { 504 if (regname == '.') { 505 insert_reg(regname, NULL, true); 506 } else { 507 if (regname == 0 && eval_has_provider("clipboard", false)) { 508 regname = '*'; 509 } 510 yankreg_T *reg = NULL; 511 if ((State & REPLACE_FLAG) && !yank_register_mline(regname, ®)) { 512 insert_reg(regname, reg, true); 513 } else { 514 do_put(regname, reg, BACKWARD, 1, 515 (fixindent ? PUT_FIXINDENT : 0) | PUT_CURSEND); 516 517 // Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r 518 AppendCharToRedobuff(Ctrl_R); 519 AppendCharToRedobuff(fixindent ? Ctrl_P : Ctrl_O); 520 AppendCharToRedobuff(regname == 0 ? '"' : regname); 521 } 522 } 523 return false; 524 } 525 } 526 527 // flags for jump_to_mouse() 528 // When dragging or button-up stay in the same window. 529 int jump_flags = is_click ? 0 : (MOUSE_FOCUS|MOUSE_DID_MOVE); 530 win_T *old_curwin = curwin; 531 532 if (tab_page_click_defs != NULL) { // only when initialized 533 // Check for clicking in the tab page line. 534 if (mouse_grid <= 1 && mouse_row == 0 && firstwin->w_winrow > 0) { 535 if (is_drag) { 536 if (in_tab_line) { 537 move_tab_to_mouse(); 538 } 539 return false; 540 } 541 542 // click in a tab selects that tab page 543 if (is_click && cmdwin_type == 0 && mouse_col < Columns) { 544 int tabnr = tab_page_click_defs[mouse_col].tabnr; 545 in_tab_line = true; 546 547 switch (tab_page_click_defs[mouse_col].type) { 548 case kStlClickDisabled: 549 break; 550 case kStlClickTabSwitch: 551 if (which_button != MOUSE_MIDDLE) { 552 if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { 553 // double click opens new page 554 end_visual_mode(); 555 tabpage_new(); 556 tabpage_move(tabnr == 0 ? 9999 : tabnr - 1); 557 } else { 558 // Go to specified tab page, or next one if not clicking 559 // on a label. 560 goto_tabpage(tabnr); 561 562 // It's like clicking on the status line of a window. 563 if (curwin != old_curwin) { 564 end_visual_mode(); 565 } 566 } 567 break; 568 } 569 FALLTHROUGH; 570 case kStlClickTabClose: 571 mouse_tab_close(tabnr); 572 break; 573 case kStlClickFuncRun: 574 call_click_def_func(tab_page_click_defs, mouse_col, which_button); 575 break; 576 } 577 } 578 return true; 579 } else if (is_drag && in_tab_line) { 580 move_tab_to_mouse(); 581 return false; 582 } 583 } 584 585 int m_pos_flag = 0; 586 pos_T m_pos = { 0 }; 587 // When 'mousemodel' is "popup" or "popup_setpos", translate mouse events: 588 // right button up -> pop-up menu 589 // shift-left button -> right button 590 // alt-left button -> alt-right button 591 if (mouse_model_popup()) { 592 m_pos_flag = get_fpos_of_mouse(&m_pos); 593 if (!(m_pos_flag & (IN_STATUS_LINE|MOUSE_WINBAR|MOUSE_STATUSCOL)) 594 && which_button == MOUSE_RIGHT && !(mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) { 595 if (!is_click) { 596 // Ignore right button release events, only shows the popup 597 // menu on the button down event. 598 return false; 599 } 600 return (do_popup(which_button, m_pos_flag, m_pos) & CURSOR_MOVED); 601 } 602 // Only do this translation when mouse is over the buffer text 603 if (!(m_pos_flag & (IN_STATUS_LINE|MOUSE_WINBAR|MOUSE_STATUSCOL)) 604 && (which_button == MOUSE_LEFT && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)))) { 605 which_button = MOUSE_RIGHT; 606 mod_mask &= ~MOD_MASK_SHIFT; 607 } 608 } 609 610 pos_T end_visual = { 0 }; 611 pos_T start_visual = { 0 }; 612 if ((State & (MODE_NORMAL | MODE_INSERT)) 613 && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { 614 if (which_button == MOUSE_LEFT) { 615 if (is_click) { 616 // stop Visual mode for a left click in a window, but not when on a status line 617 if (VIsual_active) { 618 jump_flags |= MOUSE_MAY_STOP_VIS; 619 } 620 } else { 621 jump_flags |= MOUSE_MAY_VIS; 622 } 623 } else if (which_button == MOUSE_RIGHT) { 624 if (is_click && VIsual_active) { 625 // Remember the start and end of visual before moving the cursor. 626 if (lt(curwin->w_cursor, VIsual)) { 627 start_visual = curwin->w_cursor; 628 end_visual = VIsual; 629 } else { 630 start_visual = VIsual; 631 end_visual = curwin->w_cursor; 632 } 633 } 634 jump_flags |= MOUSE_FOCUS; 635 jump_flags |= MOUSE_MAY_VIS; 636 } 637 } 638 639 // If an operator is pending, ignore all drags and releases until the next mouse click. 640 if (!is_drag && oap != NULL && oap->op_type != OP_NOP) { 641 got_click = false; 642 oap->motion_type = kMTCharWise; 643 } 644 645 // When releasing the button let jump_to_mouse() know. 646 if (!is_click && !is_drag) { 647 jump_flags |= MOUSE_RELEASED; 648 } 649 650 // JUMP! 651 int old_active = VIsual_active; 652 pos_T save_cursor = curwin->w_cursor; 653 jump_flags = jump_to_mouse(jump_flags, oap == NULL ? NULL : &(oap->inclusive), which_button); 654 655 bool moved = (jump_flags & CURSOR_MOVED); 656 bool in_winbar = (jump_flags & MOUSE_WINBAR); 657 bool in_statuscol = (jump_flags & MOUSE_STATUSCOL); 658 bool in_status_line = (jump_flags & IN_STATUS_LINE); 659 bool in_global_statusline = in_status_line && global_stl_height() > 0; 660 bool in_sep_line = (jump_flags & IN_SEP_LINE); 661 662 if ((in_winbar || in_status_line || in_statuscol) && is_click) { 663 // Handle click event on window bar, status line or status column 664 int click_grid = mouse_grid; 665 int click_row = mouse_row; 666 int click_col = mouse_col; 667 win_T *wp = mouse_find_win_inner(&click_grid, &click_row, &click_col); 668 if (wp == NULL) { 669 return false; 670 } 671 672 StlClickDefinition *click_defs = in_status_line ? wp->w_status_click_defs 673 : in_winbar ? wp->w_winbar_click_defs 674 : wp->w_statuscol_click_defs; 675 676 if (in_global_statusline) { 677 // global statusline is displayed for the current window, 678 // and spans the whole screen. 679 click_defs = curwin->w_status_click_defs; 680 click_col = mouse_col; 681 } 682 683 if (in_statuscol && wp->w_p_rl) { 684 click_col = wp->w_view_width - click_col - 1; 685 } 686 687 if ((in_statuscol && click_col >= (int)wp->w_statuscol_click_defs_size) 688 || (in_status_line 689 && click_col >= 690 (int)(in_global_statusline ? curwin : wp)->w_status_click_defs_size)) { 691 return false; 692 } 693 694 if (click_defs != NULL) { 695 switch (click_defs[click_col].type) { 696 case kStlClickDisabled: 697 // If there is no click definition, still open the popupmenu for a 698 // statuscolumn click like a click in the sign/number column does. 699 if (in_statuscol && mouse_model_popup() 700 && which_button == MOUSE_RIGHT && !(mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) { 701 do_popup(which_button, m_pos_flag, m_pos); 702 } 703 break; 704 case kStlClickFuncRun: 705 call_click_def_func(click_defs, click_col, which_button); 706 break; 707 default: 708 assert(false && "winbar, statusline and statuscolumn only support %@ for clicks"); 709 break; 710 } 711 } 712 713 if (!(in_statuscol && (jump_flags & (MOUSE_FOLD_CLOSE|MOUSE_FOLD_OPEN)))) { 714 return false; 715 } 716 } else if (in_winbar || in_statuscol) { 717 // A drag or release event in the window bar and status column has no side effects. 718 return false; 719 } 720 721 // When jumping to another window, clear a pending operator. That's a bit 722 // friendlier than beeping and not jumping to that window. 723 if (curwin != old_curwin && oap != NULL && oap->op_type != OP_NOP) { 724 clearop(oap); 725 } 726 727 if (mod_mask == 0 728 && !is_drag 729 && (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN)) 730 && which_button == MOUSE_LEFT) { 731 // open or close a fold at this line 732 if (jump_flags & MOUSE_FOLD_OPEN) { 733 openFold(curwin->w_cursor, 1); 734 } else { 735 closeFold(curwin->w_cursor, 1); 736 } 737 // don't move the cursor if still in the same window 738 if (curwin == old_curwin) { 739 curwin->w_cursor = save_cursor; 740 } 741 } 742 743 // Set global flag that we are extending the Visual area with mouse dragging; 744 // temporarily minimize 'scrolloff'. 745 if (VIsual_active && is_drag && get_scrolloff_value(curwin)) { 746 // In the very first line, allow scrolling one line 747 if (mouse_row == 0) { 748 mouse_dragging = 2; 749 } else { 750 mouse_dragging = 1; 751 } 752 } 753 754 // When dragging the mouse above the window, scroll down. 755 if (is_drag && mouse_row < 0 && !in_status_line) { 756 scroll_redraw(false, 1); 757 mouse_row = 0; 758 } 759 760 int old_mode = VIsual_mode; 761 if (start_visual.lnum) { // right click in visual mode 762 linenr_T diff; 763 // When ALT is pressed make Visual mode blockwise. 764 if (mod_mask & MOD_MASK_ALT) { 765 VIsual_mode = Ctrl_V; 766 } 767 768 // In Visual-block mode, divide the area in four, pick up the corner 769 // that is in the quarter that the cursor is in. 770 if (VIsual_mode == Ctrl_V) { 771 colnr_T leftcol, rightcol; 772 getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol); 773 if (curwin->w_curswant > (leftcol + rightcol) / 2) { 774 end_visual.col = leftcol; 775 } else { 776 end_visual.col = rightcol; 777 } 778 if (curwin->w_cursor.lnum >= 779 (start_visual.lnum + end_visual.lnum) / 2) { 780 end_visual.lnum = start_visual.lnum; 781 } 782 783 // move VIsual to the right column 784 start_visual = curwin->w_cursor; // save the cursor pos 785 curwin->w_cursor = end_visual; 786 coladvance(curwin, end_visual.col); 787 VIsual = curwin->w_cursor; 788 curwin->w_cursor = start_visual; // restore the cursor 789 } else { 790 // If the click is before the start of visual, change the start. 791 // If the click is after the end of visual, change the end. If 792 // the click is inside the visual, change the closest side. 793 if (lt(curwin->w_cursor, start_visual)) { 794 VIsual = end_visual; 795 } else if (lt(end_visual, curwin->w_cursor)) { 796 VIsual = start_visual; 797 } else { 798 // In the same line, compare column number 799 if (end_visual.lnum == start_visual.lnum) { 800 if (curwin->w_cursor.col - start_visual.col > 801 end_visual.col - curwin->w_cursor.col) { 802 VIsual = start_visual; 803 } else { 804 VIsual = end_visual; 805 } 806 } else { 807 // In different lines, compare line number 808 diff = (curwin->w_cursor.lnum - start_visual.lnum) - 809 (end_visual.lnum - curwin->w_cursor.lnum); 810 811 if (diff > 0) { // closest to end 812 VIsual = start_visual; 813 } else if (diff < 0) { // closest to start 814 VIsual = end_visual; 815 } else { // in the middle line 816 if (curwin->w_cursor.col < 817 (start_visual.col + end_visual.col) / 2) { 818 VIsual = end_visual; 819 } else { 820 VIsual = start_visual; 821 } 822 } 823 } 824 } 825 } 826 } else if ((State & MODE_INSERT) && VIsual_active) { 827 // If Visual mode started in insert mode, execute "CTRL-O" 828 stuffcharReadbuff(Ctrl_O); 829 } 830 831 // Middle mouse click: Put text before cursor. 832 if (which_button == MOUSE_MIDDLE) { 833 if (regname == 0 && eval_has_provider("clipboard", false)) { 834 regname = '*'; 835 } 836 yankreg_T *reg = NULL; 837 if (yank_register_mline(regname, ®)) { 838 if (mouse_past_bottom) { 839 dir = FORWARD; 840 } 841 } else if (mouse_past_eol) { 842 dir = FORWARD; 843 } 844 845 int c1, c2; 846 if (fixindent) { 847 c1 = (dir == BACKWARD) ? '[' : ']'; 848 c2 = 'p'; 849 } else { 850 c1 = (dir == FORWARD) ? 'p' : 'P'; 851 c2 = NUL; 852 } 853 prep_redo(regname, count, NUL, c1, NUL, c2, NUL); 854 855 // Remember where the paste started, so in edit() Insstart can be set to this position 856 if (restart_edit != 0) { 857 where_paste_started = curwin->w_cursor; 858 } 859 do_put(regname, reg, dir, count, 860 (fixindent ? PUT_FIXINDENT : 0)| PUT_CURSEND); 861 } else if (((mod_mask & MOD_MASK_CTRL) || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) 862 && bt_quickfix(curbuf)) { 863 // Ctrl-Mouse click or double click in a quickfix window jumps to the 864 // error under the mouse pointer. 865 if (curwin->w_llist_ref == NULL) { // quickfix window 866 do_cmdline_cmd(".cc"); 867 } else { // location list window 868 do_cmdline_cmd(".ll"); 869 } 870 got_click = false; // ignore drag&release now 871 } else if ((mod_mask & MOD_MASK_CTRL) 872 || (curbuf->b_help && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) { 873 // Ctrl-Mouse click (or double click in a help window) jumps to the tag 874 // under the mouse pointer. 875 if (State & MODE_INSERT) { 876 stuffcharReadbuff(Ctrl_O); 877 } 878 stuffcharReadbuff(Ctrl_RSB); 879 got_click = false; // ignore drag&release now 880 } else if ((mod_mask & MOD_MASK_SHIFT)) { 881 // Shift-Mouse click searches for the next occurrence of the word under 882 // the mouse pointer 883 if (State & MODE_INSERT || (VIsual_active && VIsual_select)) { 884 stuffcharReadbuff(Ctrl_O); 885 } 886 if (which_button == MOUSE_LEFT) { 887 stuffcharReadbuff('*'); 888 } else { // MOUSE_RIGHT 889 stuffcharReadbuff('#'); 890 } 891 } else if (in_status_line || in_sep_line) { 892 // Do nothing if on status line or vertical separator 893 // Handle double clicks otherwise 894 } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (MODE_NORMAL | MODE_INSERT))) { 895 if (is_click || !VIsual_active) { 896 if (VIsual_active) { 897 orig_cursor = VIsual; 898 } else { 899 VIsual = curwin->w_cursor; 900 orig_cursor = VIsual; 901 VIsual_active = true; 902 VIsual_reselect = true; 903 // start Select mode if 'selectmode' contains "mouse" 904 may_start_select('o'); 905 setmouse(); 906 } 907 if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { 908 // Double click with ALT pressed makes it blockwise. 909 if (mod_mask & MOD_MASK_ALT) { 910 VIsual_mode = Ctrl_V; 911 } else { 912 VIsual_mode = 'v'; 913 } 914 } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK) { 915 VIsual_mode = 'V'; 916 } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK) { 917 VIsual_mode = Ctrl_V; 918 } 919 } 920 // A double click selects a word or a block. 921 if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { 922 pos_T *pos = NULL; 923 924 if (is_click) { 925 // If the character under the cursor (skipping white space) is 926 // not a word character, try finding a match and select a (), 927 // {}, [], #if/#endif, etc. block. 928 end_visual = curwin->w_cursor; 929 int gc; 930 while (gc = gchar_pos(&end_visual), ascii_iswhite(gc)) { 931 inc(&end_visual); 932 } 933 if (oap != NULL) { 934 oap->motion_type = kMTCharWise; 935 } 936 if (oap != NULL 937 && VIsual_mode == 'v' 938 && !vim_iswordc(gchar_pos(&end_visual)) 939 && equalpos(curwin->w_cursor, VIsual) 940 && (pos = findmatch(oap, NUL)) != NULL) { 941 curwin->w_cursor = *pos; 942 if (oap->motion_type == kMTLineWise) { 943 VIsual_mode = 'V'; 944 } else if (*p_sel == 'e') { 945 if (lt(curwin->w_cursor, VIsual)) { 946 VIsual.col++; 947 } else { 948 curwin->w_cursor.col++; 949 } 950 } 951 } 952 } 953 954 if (pos == NULL && (is_click || is_drag)) { 955 // When not found a match or when dragging: extend to include a word. 956 if (lt(curwin->w_cursor, orig_cursor)) { 957 find_start_of_word(&curwin->w_cursor); 958 find_end_of_word(&VIsual); 959 } else { 960 find_start_of_word(&VIsual); 961 if (*p_sel == 'e' && *get_cursor_pos_ptr() != NUL) { 962 curwin->w_cursor.col += 963 utfc_ptr2len(get_cursor_pos_ptr()); 964 } 965 find_end_of_word(&curwin->w_cursor); 966 } 967 } 968 curwin->w_set_curswant = true; 969 } 970 if (is_click) { 971 redraw_curbuf_later(UPD_INVERTED); // update the inversion 972 } 973 } else if (VIsual_active && !old_active) { 974 if (mod_mask & MOD_MASK_ALT) { 975 VIsual_mode = Ctrl_V; 976 } else { 977 VIsual_mode = 'v'; 978 } 979 } 980 981 // If Visual mode changed show it later. 982 if ((!VIsual_active && old_active && mode_displayed) 983 || (VIsual_active && p_smd && msg_silent == 0 984 && (!old_active || VIsual_mode != old_mode))) { 985 redraw_cmdline = true; 986 } 987 988 return moved; 989 } 990 991 void ins_mouse(int c) 992 { 993 win_T *old_curwin = curwin; 994 995 undisplay_dollar(); 996 pos_T tpos = curwin->w_cursor; 997 if (do_mouse(NULL, c, BACKWARD, 1, 0)) { 998 win_T *new_curwin = curwin; 999 1000 if (curwin != old_curwin && win_valid(old_curwin)) { 1001 // Mouse took us to another window. We need to go back to the 1002 // previous one to stop insert there properly. 1003 curwin = old_curwin; 1004 curbuf = curwin->w_buffer; 1005 if (bt_prompt(curbuf)) { 1006 // Restart Insert mode when re-entering the prompt buffer. 1007 curbuf->b_prompt_insert = 'A'; 1008 } 1009 } 1010 start_arrow(curwin == old_curwin ? &tpos : NULL); 1011 if (curwin != new_curwin && win_valid(new_curwin)) { 1012 curwin = new_curwin; 1013 curbuf = curwin->w_buffer; 1014 } 1015 set_can_cindent(true); 1016 } 1017 1018 // redraw status lines (in case another window became active) 1019 redraw_statuslines(); 1020 } 1021 1022 /// Common mouse wheel scrolling, shared between Insert mode and NV modes. 1023 /// Default action is to scroll mouse_vert_step lines (or mouse_hor_step columns 1024 /// depending on the scroll direction) or one page when Shift or Ctrl is used. 1025 /// Direction is indicated by "cap->arg": 1026 /// K_MOUSEUP - MSCR_UP 1027 /// K_MOUSEDOWN - MSCR_DOWN 1028 /// K_MOUSELEFT - MSCR_LEFT 1029 /// K_MOUSERIGHT - MSCR_RIGHT 1030 /// "curwin" may have been changed to the window that should be scrolled and 1031 /// differ from the window that actually has focus. 1032 void do_mousescroll(cmdarg_T *cap) 1033 { 1034 bool shift_or_ctrl = mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL); 1035 1036 if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN) { 1037 // Vertical scrolling 1038 if ((State & MODE_NORMAL) && shift_or_ctrl) { 1039 // whole page up or down 1040 pagescroll(cap->arg ? FORWARD : BACKWARD, 1, false); 1041 } else { 1042 if (shift_or_ctrl) { 1043 // whole page up or down 1044 cap->count1 = curwin->w_botline - curwin->w_topline; 1045 } else { 1046 cap->count1 = (int)p_mousescroll_vert; 1047 } 1048 if (cap->count1 > 0) { 1049 cap->count0 = cap->count1; 1050 nv_scroll_line(cap); 1051 } 1052 } 1053 } else { 1054 // Horizontal scrolling 1055 int step = shift_or_ctrl ? curwin->w_view_width : (int)p_mousescroll_hor; 1056 colnr_T leftcol = curwin->w_leftcol + (cap->arg == MSCR_RIGHT ? -step : +step); 1057 leftcol = MAX(leftcol, 0); 1058 do_mousescroll_horiz(leftcol); 1059 } 1060 } 1061 1062 /// Implementation for scrolling in Insert mode in direction "dir", which is one 1063 /// of the MSCR_ values. 1064 void ins_mousescroll(int dir) 1065 { 1066 cmdarg_T cap; 1067 oparg_T oa; 1068 CLEAR_FIELD(cap); 1069 clear_oparg(&oa); 1070 cap.oap = &oa; 1071 cap.arg = dir; 1072 1073 switch (dir) { 1074 case MSCR_UP: 1075 cap.cmdchar = K_MOUSEUP; 1076 break; 1077 case MSCR_DOWN: 1078 cap.cmdchar = K_MOUSEDOWN; 1079 break; 1080 case MSCR_LEFT: 1081 cap.cmdchar = K_MOUSELEFT; 1082 break; 1083 case MSCR_RIGHT: 1084 cap.cmdchar = K_MOUSERIGHT; 1085 break; 1086 default: 1087 siemsg("Invalid ins_mousescroll() argument: %d", dir); 1088 } 1089 1090 win_T *old_curwin = curwin; 1091 if (mouse_row >= 0 && mouse_col >= 0) { 1092 // Find the window at the mouse pointer coordinates. 1093 // NOTE: Must restore "curwin" to "old_curwin" before returning! 1094 int grid = mouse_grid; 1095 int row = mouse_row; 1096 int col = mouse_col; 1097 curwin = mouse_find_win_inner(&grid, &row, &col); 1098 if (curwin == NULL) { 1099 curwin = old_curwin; 1100 return; 1101 } 1102 curbuf = curwin->w_buffer; 1103 } 1104 1105 if (curwin == old_curwin) { 1106 // Don't scroll the current window if the popup menu is visible. 1107 if (pum_visible()) { 1108 return; 1109 } 1110 1111 undisplay_dollar(); 1112 } 1113 1114 pos_T orig_cursor = curwin->w_cursor; 1115 1116 // Call the common mouse scroll function shared with other modes. 1117 do_mousescroll(&cap); 1118 1119 curwin->w_redr_status = true; 1120 curwin = old_curwin; 1121 curbuf = curwin->w_buffer; 1122 1123 if (!equalpos(curwin->w_cursor, orig_cursor)) { 1124 start_arrow(&orig_cursor); 1125 set_can_cindent(true); 1126 } 1127 } 1128 1129 /// Return true if "c" is a mouse key. 1130 bool is_mouse_key(int c) 1131 { 1132 return c == K_LEFTMOUSE 1133 || c == K_LEFTMOUSE_NM 1134 || c == K_LEFTDRAG 1135 || c == K_LEFTRELEASE 1136 || c == K_LEFTRELEASE_NM 1137 || c == K_MOUSEMOVE 1138 || c == K_MIDDLEMOUSE 1139 || c == K_MIDDLEDRAG 1140 || c == K_MIDDLERELEASE 1141 || c == K_RIGHTMOUSE 1142 || c == K_RIGHTDRAG 1143 || c == K_RIGHTRELEASE 1144 || c == K_MOUSEDOWN 1145 || c == K_MOUSEUP 1146 || c == K_MOUSELEFT 1147 || c == K_MOUSERIGHT 1148 || c == K_X1MOUSE 1149 || c == K_X1DRAG 1150 || c == K_X1RELEASE 1151 || c == K_X2MOUSE 1152 || c == K_X2DRAG 1153 || c == K_X2RELEASE; 1154 } 1155 1156 /// @return true when 'mousemodel' is set to "popup" or "popup_setpos". 1157 static bool mouse_model_popup(void) 1158 { 1159 return p_mousem[0] == 'p'; 1160 } 1161 1162 static win_T *dragwin = NULL; ///< window being dragged 1163 1164 /// Reset the window being dragged. To be called when switching tab page. 1165 void reset_dragwin(void) 1166 { 1167 dragwin = NULL; 1168 } 1169 1170 /// Move the cursor to the specified row and column on the screen. 1171 /// Change current window if necessary. Returns an integer with the 1172 /// CURSOR_MOVED bit set if the cursor has moved or unset otherwise. 1173 /// 1174 /// The MOUSE_FOLD_CLOSE bit is set when clicked on the '-' in a fold column. 1175 /// The MOUSE_FOLD_OPEN bit is set when clicked on the '+' in a fold column. 1176 /// 1177 /// If flags has MOUSE_FOCUS, then the current window will not be changed, and 1178 /// if the mouse is outside the window then the text will scroll, or if the 1179 /// mouse was previously on a status line, then the status line may be dragged. 1180 /// 1181 /// If flags has MOUSE_MAY_VIS, then VIsual mode will be started before the 1182 /// cursor is moved unless the cursor was on a status line or window bar. 1183 /// This function returns one of IN_UNKNOWN, IN_BUFFER, IN_STATUS_LINE or 1184 /// IN_SEP_LINE depending on where the cursor was clicked. 1185 /// 1186 /// If flags has MOUSE_MAY_STOP_VIS, then Visual mode will be stopped, unless 1187 /// the mouse is on the status line or window bar of the same window. 1188 /// 1189 /// If flags has MOUSE_DID_MOVE, nothing is done if the mouse didn't move since 1190 /// the last call. 1191 /// 1192 /// If flags has MOUSE_SETPOS, nothing is done, only the current position is 1193 /// remembered. 1194 /// 1195 /// @param inclusive used for inclusive operator, can be NULL 1196 /// @param which_button MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE 1197 int jump_to_mouse(int flags, bool *inclusive, int which_button) 1198 { 1199 static int status_line_offset = 0; // #lines offset from status line 1200 static int sep_line_offset = 0; // #cols offset from sep line 1201 static bool on_status_line = false; 1202 static bool on_sep_line = false; 1203 static bool on_winbar = false; 1204 static bool on_statuscol = false; 1205 static int prev_row = -1; 1206 static int prev_col = -1; 1207 static int did_drag = false; // drag was noticed 1208 1209 int count; 1210 bool first; 1211 int row = mouse_row; 1212 int col = mouse_col; 1213 int grid = mouse_grid; 1214 int fdc = 0; 1215 bool keep_focus = flags & MOUSE_FOCUS; 1216 1217 mouse_past_bottom = false; 1218 mouse_past_eol = false; 1219 1220 if (flags & MOUSE_RELEASED) { 1221 // On button release we may change window focus if positioned on a 1222 // status line and no dragging happened. 1223 if (dragwin != NULL && !did_drag) { 1224 flags &= ~(MOUSE_FOCUS | MOUSE_DID_MOVE); 1225 } 1226 dragwin = NULL; 1227 did_drag = false; 1228 } 1229 1230 if ((flags & MOUSE_DID_MOVE) 1231 && prev_row == mouse_row 1232 && prev_col == mouse_col) { 1233 retnomove: 1234 // before moving the cursor for a left click which is NOT in a status 1235 // line, stop Visual mode 1236 if (status_line_offset) { 1237 return IN_STATUS_LINE; 1238 } 1239 if (sep_line_offset) { 1240 return IN_SEP_LINE; 1241 } 1242 if (on_winbar) { 1243 return IN_OTHER_WIN | MOUSE_WINBAR; 1244 } 1245 if (on_statuscol) { 1246 return IN_OTHER_WIN | MOUSE_STATUSCOL; 1247 } 1248 if (flags & MOUSE_MAY_STOP_VIS) { 1249 end_visual_mode(); 1250 redraw_curbuf_later(UPD_INVERTED); // delete the inversion 1251 } 1252 return IN_BUFFER; 1253 } 1254 1255 prev_row = mouse_row; 1256 prev_col = mouse_col; 1257 1258 if (flags & MOUSE_SETPOS) { 1259 goto retnomove; // ugly goto... 1260 } 1261 1262 if (row < 0 || col < 0) { // check if it makes sense 1263 return IN_UNKNOWN; 1264 } 1265 1266 // find the window where the row is in and adjust "row" and "col" to be 1267 // relative to top-left of the window inner area 1268 win_T *wp = mouse_find_win_inner(&grid, &row, &col); 1269 if (wp == NULL) { 1270 return IN_UNKNOWN; 1271 } 1272 1273 bool below_window = grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height; 1274 on_status_line = below_window && row + wp->w_winbar_height - wp->w_height + 1 == 1; 1275 on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width && col - wp->w_width + 1 == 1; 1276 on_winbar = row < 0 && row + wp->w_winbar_height >= 0; 1277 on_statuscol = !below_window && !on_status_line && !on_sep_line && !on_winbar 1278 && *wp->w_p_stc != NUL 1279 && (wp->w_p_rl 1280 ? col >= wp->w_view_width - win_col_off(wp) 1281 : col < win_col_off(wp)); 1282 1283 // The rightmost character of the status line might be a vertical 1284 // separator character if there is no connecting window to the right. 1285 if (on_status_line && on_sep_line) { 1286 if (stl_connected(wp)) { 1287 on_sep_line = false; 1288 } else { 1289 on_status_line = false; 1290 } 1291 } 1292 1293 if (keep_focus) { 1294 // If we can't change focus, set the value of row, col and grid back to absolute values 1295 // since the values relative to the window are only used when keep_focus is false 1296 row = mouse_row; 1297 col = mouse_col; 1298 grid = mouse_grid; 1299 } 1300 1301 win_T *old_curwin = curwin; 1302 pos_T old_cursor = curwin->w_cursor; 1303 if (!keep_focus) { 1304 if (on_winbar) { 1305 return IN_OTHER_WIN | MOUSE_WINBAR; 1306 } 1307 1308 if (on_statuscol) { 1309 goto foldclick; 1310 } 1311 1312 fdc = win_fdccol_count(wp); 1313 dragwin = NULL; 1314 1315 // winpos and height may change in win_enter()! 1316 if (below_window) { 1317 // In (or below) status line 1318 status_line_offset = row + wp->w_winbar_height - wp->w_height + 1; 1319 dragwin = wp; 1320 } else { 1321 status_line_offset = 0; 1322 } 1323 1324 if (grid == DEFAULT_GRID_HANDLE && col >= wp->w_width) { 1325 // In separator line 1326 sep_line_offset = col - wp->w_width + 1; 1327 dragwin = wp; 1328 } else { 1329 sep_line_offset = 0; 1330 } 1331 1332 // The rightmost character of the status line might be a vertical 1333 // separator character if there is no connecting window to the right. 1334 if (status_line_offset && sep_line_offset) { 1335 if (stl_connected(wp)) { 1336 sep_line_offset = 0; 1337 } else { 1338 status_line_offset = 0; 1339 } 1340 } 1341 1342 // Before jumping to another buffer, or moving the cursor for a left 1343 // click, stop Visual mode. 1344 if (VIsual_active 1345 && (wp->w_buffer != curwin->w_buffer 1346 || (!status_line_offset 1347 && !sep_line_offset 1348 && (wp->w_p_rl 1349 ? col < wp->w_view_width - fdc 1350 : col >= fdc + (wp != cmdwin_win ? 0 : 1)) 1351 && (flags & MOUSE_MAY_STOP_VIS)))) { 1352 end_visual_mode(); 1353 redraw_curbuf_later(UPD_INVERTED); // delete the inversion 1354 } 1355 if (cmdwin_type != 0 && wp != cmdwin_win) { 1356 // A click outside the command-line window: Use modeless 1357 // selection if possible. Allow dragging the status lines. 1358 sep_line_offset = 0; 1359 row = 0; 1360 col += wp->w_wincol; 1361 wp = cmdwin_win; 1362 } 1363 // Only change window focus when not clicking on or dragging the 1364 // status line. Do change focus when releasing the mouse button 1365 // (MOUSE_FOCUS was set above if we dragged first). 1366 if (dragwin == NULL || (flags & MOUSE_RELEASED)) { 1367 win_enter(wp, true); // can make wp invalid! 1368 } 1369 // set topline, to be able to check for double click ourselves 1370 if (curwin != old_curwin) { 1371 set_mouse_topline(curwin); 1372 } 1373 if (status_line_offset) { // In (or below) status line 1374 // Don't use start_arrow() if we're in the same window 1375 if (curwin == old_curwin) { 1376 return IN_STATUS_LINE; 1377 } 1378 return IN_STATUS_LINE | CURSOR_MOVED; 1379 } 1380 if (sep_line_offset) { // In (or below) status line 1381 // Don't use start_arrow() if we're in the same window 1382 if (curwin == old_curwin) { 1383 return IN_SEP_LINE; 1384 } 1385 return IN_SEP_LINE | CURSOR_MOVED; 1386 } 1387 1388 curwin->w_cursor.lnum = curwin->w_topline; 1389 } else if (status_line_offset) { 1390 if (which_button == MOUSE_LEFT && dragwin != NULL) { 1391 // Drag the status line 1392 count = row - dragwin->w_winrow - dragwin->w_height + 1 1393 - status_line_offset; 1394 win_drag_status_line(dragwin, count); 1395 did_drag |= count; 1396 } 1397 return IN_STATUS_LINE; // Cursor didn't move 1398 } else if (sep_line_offset && which_button == MOUSE_LEFT) { 1399 if (dragwin != NULL) { 1400 // Drag the separator column 1401 count = col - dragwin->w_wincol - dragwin->w_width + 1 1402 - sep_line_offset; 1403 win_drag_vsep_line(dragwin, count); 1404 did_drag |= count; 1405 } 1406 return IN_SEP_LINE; // Cursor didn't move 1407 } else if (on_status_line && which_button == MOUSE_RIGHT) { 1408 return IN_STATUS_LINE; 1409 } else if (on_winbar && which_button == MOUSE_RIGHT) { 1410 // After a click on the window bar don't start Visual mode. 1411 return IN_OTHER_WIN | MOUSE_WINBAR; 1412 } else if (on_statuscol && which_button == MOUSE_RIGHT) { 1413 // After a click on the status column don't start Visual mode. 1414 return IN_OTHER_WIN | MOUSE_STATUSCOL; 1415 } else { 1416 // keep_window_focus must be true 1417 // before moving the cursor for a left click, stop Visual mode 1418 if (flags & MOUSE_MAY_STOP_VIS) { 1419 end_visual_mode(); 1420 redraw_curbuf_later(UPD_INVERTED); // delete the inversion 1421 } 1422 1423 if (grid == 0) { 1424 row -= curwin->w_grid_alloc.comp_row + curwin->w_grid.row_offset; 1425 col -= curwin->w_grid_alloc.comp_col + curwin->w_grid.col_offset; 1426 } else if (grid != DEFAULT_GRID_HANDLE) { 1427 row -= curwin->w_grid.row_offset; 1428 col -= curwin->w_grid.col_offset; 1429 } 1430 1431 // When clicking beyond the end of the window, scroll the screen. 1432 // Scroll by however many rows outside the window we are. 1433 if (row < 0) { 1434 count = 0; 1435 for (first = true; curwin->w_topline > 1;) { 1436 if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) { 1437 count++; 1438 } else { 1439 count += plines_win(curwin, curwin->w_topline - 1, true); 1440 } 1441 if (!first && count > -row) { 1442 break; 1443 } 1444 first = false; 1445 hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL); 1446 if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) { 1447 curwin->w_topfill++; 1448 } else { 1449 curwin->w_topline--; 1450 curwin->w_topfill = 0; 1451 } 1452 } 1453 check_topfill(curwin, false); 1454 curwin->w_valid &= 1455 ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); 1456 redraw_later(curwin, UPD_VALID); 1457 row = 0; 1458 } else if (row >= curwin->w_view_height) { 1459 count = 0; 1460 for (first = true; curwin->w_topline < curbuf->b_ml.ml_line_count;) { 1461 if (curwin->w_topfill > 0) { 1462 count++; 1463 } else { 1464 count += plines_win(curwin, curwin->w_topline, true); 1465 } 1466 1467 if (!first && count > row - curwin->w_view_height + 1) { 1468 break; 1469 } 1470 first = false; 1471 1472 if (curwin->w_topfill > 0) { 1473 curwin->w_topfill--; 1474 } else { 1475 if (hasFolding(curwin, curwin->w_topline, NULL, &curwin->w_topline) 1476 && curwin->w_topline == curbuf->b_ml.ml_line_count) { 1477 break; 1478 } 1479 curwin->w_topline++; 1480 curwin->w_topfill = win_get_fill(curwin, curwin->w_topline); 1481 } 1482 } 1483 check_topfill(curwin, false); 1484 redraw_later(curwin, UPD_VALID); 1485 curwin->w_valid &= 1486 ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); 1487 row = curwin->w_view_height - 1; 1488 } else if (row == 0) { 1489 // When dragging the mouse, while the text has been scrolled up as 1490 // far as it goes, moving the mouse in the top line should scroll 1491 // the text down (done later when recomputing w_topline). 1492 if (mouse_dragging > 0 1493 && curwin->w_cursor.lnum 1494 == curwin->w_buffer->b_ml.ml_line_count 1495 && curwin->w_cursor.lnum == curwin->w_topline) { 1496 curwin->w_valid &= ~(VALID_TOPLINE); 1497 } 1498 } 1499 } 1500 1501 foldclick:; 1502 colnr_T col_from_screen = -1; 1503 int mouse_fold_flags = 0; 1504 mouse_check_grid(&col_from_screen, &mouse_fold_flags); 1505 1506 // compute the position in the buffer line from the posn on the screen 1507 if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) { 1508 mouse_past_bottom = true; 1509 } 1510 1511 // Start Visual mode before coladvance(), for when 'sel' != "old" 1512 if ((flags & MOUSE_MAY_VIS) && !VIsual_active) { 1513 VIsual = old_cursor; 1514 VIsual_active = true; 1515 VIsual_reselect = true; 1516 // if 'selectmode' contains "mouse", start Select mode 1517 may_start_select('o'); 1518 setmouse(); 1519 1520 if (p_smd && msg_silent == 0) { 1521 redraw_cmdline = true; // show visual mode later 1522 } 1523 } 1524 1525 if (col_from_screen >= 0) { 1526 col = col_from_screen; 1527 } 1528 1529 curwin->w_curswant = col; 1530 curwin->w_set_curswant = false; // May still have been true 1531 if (coladvance(curwin, col) == FAIL) { // Mouse click beyond end of line 1532 if (inclusive != NULL) { 1533 *inclusive = true; 1534 } 1535 mouse_past_eol = true; 1536 } else if (inclusive != NULL) { 1537 *inclusive = false; 1538 } 1539 1540 count = on_statuscol ? (IN_OTHER_WIN|MOUSE_STATUSCOL) : IN_BUFFER; 1541 if (curwin != old_curwin || curwin->w_cursor.lnum != old_cursor.lnum 1542 || curwin->w_cursor.col != old_cursor.col) { 1543 count |= CURSOR_MOVED; // Cursor has moved 1544 } 1545 1546 count |= mouse_fold_flags; 1547 1548 return count; 1549 } 1550 1551 /// Make a horizontal scroll to "leftcol". 1552 /// @return true if the cursor moved, false otherwise. 1553 static bool do_mousescroll_horiz(colnr_T leftcol) 1554 { 1555 if (curwin->w_p_wrap) { 1556 return false; // no horizontal scrolling when wrapping 1557 } 1558 if (curwin->w_leftcol == leftcol) { 1559 return false; // already there 1560 } 1561 1562 // When the line of the cursor is too short, move the cursor to the 1563 // longest visible line. 1564 if (!virtual_active(curwin) 1565 && leftcol > scroll_line_len(curwin->w_cursor.lnum)) { 1566 curwin->w_cursor.lnum = find_longest_lnum(); 1567 curwin->w_cursor.col = 0; 1568 } 1569 1570 return set_leftcol(leftcol); 1571 } 1572 1573 /// Normal and Visual modes implementation for scrolling in direction 1574 /// "cap->arg", which is one of the MSCR_ values. 1575 void nv_mousescroll(cmdarg_T *cap) 1576 { 1577 win_T *const old_curwin = curwin; 1578 1579 if (mouse_row >= 0 && mouse_col >= 0) { 1580 // Find the window at the mouse pointer coordinates. 1581 // NOTE: Must restore "curwin" to "old_curwin" before returning! 1582 int grid = mouse_grid; 1583 int row = mouse_row; 1584 int col = mouse_col; 1585 curwin = mouse_find_win_inner(&grid, &row, &col); 1586 if (curwin == NULL) { 1587 curwin = old_curwin; 1588 return; 1589 } 1590 curbuf = curwin->w_buffer; 1591 } 1592 1593 // Call the common mouse scroll function shared with other modes. 1594 do_mousescroll(cap); 1595 1596 curwin->w_redr_status = true; 1597 curwin = old_curwin; 1598 curbuf = curwin->w_buffer; 1599 } 1600 1601 /// Mouse clicks and drags. 1602 void nv_mouse(cmdarg_T *cap) 1603 { 1604 do_mouse(cap->oap, cap->cmdchar, BACKWARD, cap->count1, 0); 1605 } 1606 1607 /// Compute the buffer line position from the screen position "rowp" / "colp" in 1608 /// window "win". 1609 /// Returns true if the position is below the last line. 1610 bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) 1611 { 1612 int col = *colp; 1613 int row = *rowp; 1614 bool retval = false; 1615 int count; 1616 1617 if (win->w_p_rl) { 1618 col = win->w_view_width - 1 - col; 1619 } 1620 1621 linenr_T lnum = win->w_topline; 1622 1623 while (row > 0) { 1624 // Don't include filler lines in "count" 1625 if (win_may_fill(win)) { 1626 row -= lnum == win->w_topline ? win->w_topfill 1627 : win_get_fill(win, lnum); 1628 count = plines_win_nofill(win, lnum, false); 1629 } else { 1630 count = plines_win(win, lnum, false); 1631 } 1632 1633 if (win->w_skipcol > 0 && lnum == win->w_topline) { 1634 int width1 = win->w_view_width - win_col_off(win); 1635 1636 if (width1 > 0) { 1637 int skip_lines = 0; 1638 1639 // Adjust for 'smoothscroll' clipping the top screen lines. 1640 // A similar formula is used in curs_columns(). 1641 if (win->w_skipcol > width1) { 1642 skip_lines = (win->w_skipcol - width1) / (width1 + win_col_off2(win)) + 1; 1643 } else if (win->w_skipcol > 0) { 1644 skip_lines = 1; 1645 } 1646 1647 count -= skip_lines; 1648 } 1649 } 1650 1651 if (count > row) { 1652 break; // Position is in this buffer line. 1653 } 1654 1655 hasFolding(win, lnum, NULL, &lnum); 1656 1657 if (lnum == win->w_buffer->b_ml.ml_line_count) { 1658 retval = true; 1659 break; // past end of file 1660 } 1661 row -= count; 1662 lnum++; 1663 } 1664 1665 // Mouse row reached, adjust lnum for concealed lines. 1666 while (lnum < win->w_buffer->b_ml.ml_line_count 1667 && decor_conceal_line(win, lnum - 1, false)) { 1668 lnum++; 1669 hasFolding(win, lnum, NULL, &lnum); 1670 } 1671 1672 if (!retval) { 1673 // Compute the column without wrapping. 1674 int off = win_col_off(win) - win_col_off2(win); 1675 col = MAX(col, off); 1676 col += row * (win->w_view_width - off); 1677 1678 // Add skip column for the topline. 1679 if (lnum == win->w_topline) { 1680 col += win->w_skipcol; 1681 } 1682 } 1683 1684 if (!win->w_p_wrap) { 1685 col += win->w_leftcol; 1686 } 1687 1688 // skip line number and fold column in front of the line 1689 col -= win_col_off(win); 1690 col = MAX(col, 0); 1691 1692 *colp = col; 1693 *rowp = row; 1694 *lnump = lnum; 1695 return retval; 1696 } 1697 1698 /// Find the window at "grid" position "*rowp" and "*colp". The positions are 1699 /// updated to become relative to the top-left of the window inner area. 1700 /// 1701 /// @return NULL when something is wrong. 1702 win_T *mouse_find_win_inner(int *gridp, int *rowp, int *colp) 1703 { 1704 win_T *wp_grid = mouse_find_grid_win(gridp, rowp, colp); 1705 if (wp_grid) { 1706 return wp_grid; 1707 } else if (*gridp > 1) { 1708 return NULL; 1709 } 1710 1711 frame_T *fp = topframe; 1712 *rowp -= firstwin->w_winrow; 1713 while (true) { 1714 if (fp->fr_layout == FR_LEAF) { 1715 break; 1716 } 1717 if (fp->fr_layout == FR_ROW) { 1718 for (fp = fp->fr_child; fp->fr_next != NULL; fp = fp->fr_next) { 1719 if (*colp < fp->fr_width) { 1720 break; 1721 } 1722 *colp -= fp->fr_width; 1723 } 1724 } else { // fr_layout == FR_COL 1725 for (fp = fp->fr_child; fp->fr_next != NULL; fp = fp->fr_next) { 1726 if (*rowp < fp->fr_height) { 1727 break; 1728 } 1729 *rowp -= fp->fr_height; 1730 } 1731 } 1732 } 1733 // When using a timer that closes a window the window might not actually 1734 // exist. 1735 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 1736 if (wp == fp->fr_win) { 1737 *rowp -= wp->w_winbar_height; 1738 return wp; 1739 } 1740 } 1741 return NULL; 1742 } 1743 1744 /// Find the window at "grid" position "*rowp" and "*colp". The positions are 1745 /// updated to become relative to the top-left of the window. 1746 /// 1747 /// @return NULL when something is wrong. 1748 win_T *mouse_find_win_outer(int *gridp, int *rowp, int *colp) 1749 { 1750 win_T *wp = mouse_find_win_inner(gridp, rowp, colp); 1751 if (wp) { 1752 *rowp += wp->w_winrow_off; 1753 *colp += wp->w_wincol_off; 1754 } 1755 return wp; 1756 } 1757 1758 static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) 1759 { 1760 if (*gridp == msg_grid.handle) { 1761 *rowp += msg_grid_pos; 1762 *gridp = DEFAULT_GRID_HANDLE; 1763 } else if (*gridp > 1) { 1764 win_T *wp = get_win_by_grid_handle(*gridp); 1765 if (wp && wp->w_grid_alloc.chars 1766 && !(wp->w_floating && !wp->w_config.mouse)) { 1767 *rowp = MIN(*rowp - wp->w_grid.row_offset, wp->w_view_height - 1); 1768 *colp = MIN(*colp - wp->w_grid.col_offset, wp->w_view_width - 1); 1769 return wp; 1770 } 1771 } else if (*gridp == 0) { 1772 ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp); 1773 if (grid == &pum_grid) { 1774 *gridp = grid->handle; 1775 *rowp -= grid->comp_row; 1776 *colp -= grid->comp_col; 1777 // The popup menu doesn't have a window, so return NULL 1778 return NULL; 1779 } else { 1780 FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { 1781 if (&wp->w_grid_alloc != grid) { 1782 continue; 1783 } 1784 *gridp = grid->handle; 1785 *rowp -= wp->w_winrow + wp->w_grid.row_offset; 1786 *colp -= wp->w_wincol + wp->w_grid.col_offset; 1787 return wp; 1788 } 1789 } 1790 1791 // No grid found, return the default grid. With multigrid this happens for split separators for 1792 // example. 1793 *gridp = DEFAULT_GRID_HANDLE; 1794 } 1795 return NULL; 1796 } 1797 1798 /// Convert a virtual (screen) column to a character column. 1799 /// The first column is zero. 1800 colnr_T vcol2col(win_T *wp, linenr_T lnum, colnr_T vcol, colnr_T *coladdp) 1801 FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT 1802 { 1803 // try to advance to the specified column 1804 char *line = ml_get_buf(wp->w_buffer, lnum); 1805 CharsizeArg csarg; 1806 CSType cstype = init_charsize_arg(&csarg, wp, lnum, line); 1807 StrCharInfo ci = utf_ptr2StrCharInfo(line); 1808 int cur_vcol = 0; 1809 while (cur_vcol < vcol && *ci.ptr != NUL) { 1810 int next_vcol = cur_vcol + win_charsize(cstype, cur_vcol, ci.ptr, ci.chr.value, &csarg).width; 1811 if (next_vcol > vcol) { 1812 break; 1813 } 1814 cur_vcol = next_vcol; 1815 ci = utfc_next(ci); 1816 } 1817 1818 if (coladdp != NULL) { 1819 *coladdp = vcol - cur_vcol; 1820 } 1821 return (colnr_T)(ci.ptr - line); 1822 } 1823 1824 /// Set UI mouse depending on current mode and 'mouse'. 1825 /// 1826 /// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty). 1827 void setmouse(void) 1828 { 1829 ui_cursor_shape(); 1830 ui_check_mouse(); 1831 } 1832 1833 // Set orig_topline. Used when jumping to another window, so that a double 1834 // click still works. 1835 static void set_mouse_topline(win_T *wp) 1836 { 1837 orig_topline = wp->w_topline; 1838 orig_topfill = wp->w_topfill; 1839 } 1840 1841 /// Return length of line "lnum" for horizontal scrolling. 1842 static colnr_T scroll_line_len(linenr_T lnum) 1843 { 1844 colnr_T col = 0; 1845 char *line = ml_get(lnum); 1846 if (*line != NUL) { 1847 while (true) { 1848 int numchar = win_chartabsize(curwin, line, col); 1849 MB_PTR_ADV(line); 1850 if (*line == NUL) { // don't count the last character 1851 break; 1852 } 1853 col += numchar; 1854 } 1855 } 1856 return col; 1857 } 1858 1859 /// Find longest visible line number. 1860 static linenr_T find_longest_lnum(void) 1861 { 1862 linenr_T ret = 0; 1863 1864 // Calculate maximum for horizontal scrollbar. Check for reasonable 1865 // line numbers, topline and botline can be invalid when displaying is 1866 // postponed. 1867 if (curwin->w_topline <= curwin->w_cursor.lnum 1868 && curwin->w_botline > curwin->w_cursor.lnum 1869 && curwin->w_botline <= curbuf->b_ml.ml_line_count + 1) { 1870 colnr_T max = 0; 1871 1872 // Use maximum of all visible lines. Remember the lnum of the 1873 // longest line, closest to the cursor line. Used when scrolling 1874 // below. 1875 for (linenr_T lnum = curwin->w_topline; lnum < curwin->w_botline; lnum++) { 1876 colnr_T len = scroll_line_len(lnum); 1877 if (len > max) { 1878 max = len; 1879 ret = lnum; 1880 } else if (len == max 1881 && abs(lnum - curwin->w_cursor.lnum) 1882 < abs(ret - curwin->w_cursor.lnum)) { 1883 ret = lnum; 1884 } 1885 } 1886 } else { 1887 // Use cursor line only. 1888 ret = curwin->w_cursor.lnum; 1889 } 1890 1891 return ret; 1892 } 1893 1894 /// Check clicked cell on its grid 1895 static void mouse_check_grid(colnr_T *vcolp, int *flagsp) 1896 FUNC_ATTR_NONNULL_ALL 1897 { 1898 int click_grid = mouse_grid; 1899 int click_row = mouse_row; 1900 int click_col = mouse_col; 1901 1902 // XXX: this doesn't change click_grid if it is 1, even with multigrid 1903 if (mouse_find_win_inner(&click_grid, &click_row, &click_col) != curwin 1904 // Only use vcols[] after the window was redrawn. Mainly matters 1905 // for tests, a user would not click before redrawing. 1906 || curwin->w_redr_type != 0) { 1907 return; 1908 } 1909 int start_row = 0; 1910 int start_col = 0; 1911 ScreenGrid *gp = grid_adjust(&curwin->w_grid, &start_row, &start_col); 1912 if (gp->handle != click_grid || gp->chars == NULL) { 1913 return; 1914 } 1915 click_row += start_row; 1916 click_col += start_col; 1917 if (click_row < 0 || click_row >= gp->rows 1918 || click_col < 0 || click_col >= gp->cols) { 1919 return; 1920 } 1921 1922 const size_t off = gp->line_offset[click_row] + (size_t)click_col; 1923 colnr_T col_from_screen = gp->vcols[off]; 1924 1925 if (col_from_screen >= 0) { 1926 // Use the virtual column from vcols[], it is accurate also after 1927 // concealed characters. 1928 *vcolp = col_from_screen; 1929 } 1930 1931 if (col_from_screen == -2) { 1932 *flagsp |= MOUSE_FOLD_OPEN; 1933 } else if (col_from_screen == -3) { 1934 *flagsp |= MOUSE_FOLD_CLOSE; 1935 } 1936 } 1937 1938 /// "getmousepos()" function 1939 void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) 1940 { 1941 int row = mouse_row; 1942 int col = mouse_col; 1943 int grid = mouse_grid; 1944 varnumber_T winid = 0; 1945 varnumber_T winrow = 0; 1946 varnumber_T wincol = 0; 1947 linenr_T lnum = 0; 1948 varnumber_T column = 0; 1949 colnr_T coladd = 0; 1950 1951 tv_dict_alloc_ret(rettv); 1952 dict_T *d = rettv->vval.v_dict; 1953 1954 tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1); 1955 tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1); 1956 1957 win_T *wp = mouse_find_win_inner(&grid, &row, &col); 1958 if (wp != NULL) { 1959 int height = wp->w_height + wp->w_hsep_height + wp->w_status_height; 1960 // The height is adjusted by 1 when there is a bottom border. This is not 1961 // necessary for a top border since `row` starts at -1 in that case. 1962 if (row < height + wp->w_border_adj[2]) { 1963 winid = wp->handle; 1964 winrow = row + 1 + wp->w_winrow_off; // Adjust by 1 for top border 1965 wincol = col + 1 + wp->w_wincol_off; // Adjust by 1 for left border 1966 if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) { 1967 mouse_comp_pos(wp, &row, &col, &lnum); 1968 col = vcol2col(wp, lnum, col, &coladd); 1969 column = col + 1; 1970 } 1971 } 1972 } 1973 tv_dict_add_nr(d, S_LEN("winid"), winid); 1974 tv_dict_add_nr(d, S_LEN("winrow"), winrow); 1975 tv_dict_add_nr(d, S_LEN("wincol"), wincol); 1976 tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum); 1977 tv_dict_add_nr(d, S_LEN("column"), column); 1978 tv_dict_add_nr(d, S_LEN("coladd"), coladd); 1979 }