neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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, &reg)) {
    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, &reg)) {
    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 }