#include #include #include #include #include #include "nvim/ascii_defs.h" #include "nvim/buffer.h" #include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/drawscreen.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" #include "nvim/ex_docmd.h" #include "nvim/fold.h" #include "nvim/getchar.h" #include "nvim/globals.h" #include "nvim/grid.h" #include "nvim/grid_defs.h" #include "nvim/keycodes.h" #include "nvim/macros_defs.h" #include "nvim/mark_defs.h" #include "nvim/mbyte.h" #include "nvim/mbyte_defs.h" #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/menu.h" #include "nvim/message.h" #include "nvim/mouse.h" #include "nvim/move.h" #include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/option_vars.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" #include "nvim/pos_defs.h" #include "nvim/search.h" #include "nvim/state.h" #include "nvim/state_defs.h" #include "nvim/statusline.h" #include "nvim/statusline_defs.h" #include "nvim/strings.h" #include "nvim/types_defs.h" #include "nvim/ui.h" #include "nvim/ui_compositor.h" #include "nvim/vim_defs.h" #include "nvim/window.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mouse.c.generated.h" #endif static linenr_T orig_topline = 0; static int orig_topfill = 0; /// Get class of a character for selection: same class means same word. /// 0: blank /// 1: punctuation groups /// 2: normal word character /// >2: multi-byte word character. static int get_mouse_class(char *p) { if (MB_BYTE2LEN((uint8_t)p[0]) > 1) { return mb_get_class(p); } const int c = (uint8_t)(*p); if (c == ' ' || c == '\t') { return 0; } if (vim_iswordc(c)) { return 2; } // There are a few special cases where we want certain combinations of // characters to be considered as a single word. These are things like // "->", "/ *", "*=", "+=", "&=", "<=", ">=", "!=" etc. Otherwise, each // character is in its own class. if (c != NUL && vim_strchr("-+*/%<>&|^!=", c) != NULL) { return 1; } return c; } /// Move "pos" back to the start of the word it's in. static void find_start_of_word(pos_T *pos) { char *line = ml_get(pos->lnum); int cclass = get_mouse_class(line + pos->col); while (pos->col > 0) { int col = pos->col - 1; col -= utf_head_off(line, line + col); if (get_mouse_class(line + col) != cclass) { break; } pos->col = col; } } /// Move "pos" forward to the end of the word it's in. /// When 'selection' is "exclusive", the position is just after the word. static void find_end_of_word(pos_T *pos) { char *line = ml_get(pos->lnum); if (*p_sel == 'e' && pos->col > 0) { pos->col--; pos->col -= utf_head_off(line, line + pos->col); } int cclass = get_mouse_class(line + pos->col); while (line[pos->col] != NUL) { int col = pos->col + utfc_ptr2len(line + pos->col); if (get_mouse_class(line + col) != cclass) { if (*p_sel == 'e') { pos->col = col; } break; } pos->col = col; } } /// Move the current tab to tab in same column as mouse or to end of the /// tabline if there is no tab there. static void move_tab_to_mouse(void) { int tabnr = tab_page_click_defs[mouse_col].tabnr; if (tabnr <= 0) { tabpage_move(9999); } else if (tabnr < tabpage_index(curtab)) { tabpage_move(tabnr - 1); } else { tabpage_move(tabnr); } } /// Close the current or specified tab page. /// /// @param c1 tabpage number, or 999 for the current tabpage static void mouse_tab_close(int c1) { tabpage_T *tp; if (c1 == 999) { tp = curtab; } else { tp = find_tabpage(c1); } if (tp == curtab) { if (first_tabpage->tp_next != NULL) { tabpage_close(false); } } else if (tp != NULL) { tabpage_close_other(tp, false); } } static bool got_click = false; // got a click some time back /// Call click definition function for column "col" in the "click_defs" array for button /// "which_button". static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button) { typval_T argv[] = { { .v_lock = VAR_FIXED, .v_type = VAR_NUMBER, .vval = { .v_number = (varnumber_T)click_defs[col].tabnr }, }, { .v_lock = VAR_FIXED, .v_type = VAR_NUMBER, .vval = { .v_number = ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK ? 4 : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK ? 3 : ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK ? 2 : 1))) }, }, { .v_lock = VAR_FIXED, .v_type = VAR_STRING, .vval = { .v_string = (which_button == MOUSE_LEFT ? "l" : (which_button == MOUSE_RIGHT ? "r" : (which_button == MOUSE_MIDDLE ? "m" : (which_button == MOUSE_X1 ? "x1" : (which_button == MOUSE_X2 ? "x2" : "?"))))) }, }, { .v_lock = VAR_FIXED, .v_type = VAR_STRING, .vval = { .v_string = (char[]) { (char)(mod_mask & MOD_MASK_SHIFT ? 's' : ' '), (char)(mod_mask & MOD_MASK_CTRL ? 'c' : ' '), (char)(mod_mask & MOD_MASK_ALT ? 'a' : ' '), (char)(mod_mask & MOD_MASK_META ? 'm' : ' '), NUL } }, } }; typval_T rettv; call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv); tv_clear(&rettv); // Make sure next click does not register as drag when callback absorbs the release event. got_click = false; } /// Translate window coordinates to buffer position without any side effects. /// Returns IN_BUFFER and sets "mpos->col" to the column when in buffer text. /// The column is one for the first column. static int get_fpos_of_mouse(pos_T *mpos) { int grid = mouse_grid; int row = mouse_row; int col = mouse_col; if (row < 0 || col < 0) { // check if it makes sense return IN_UNKNOWN; } // find the window where the row is in win_T *wp = mouse_find_win(&grid, &row, &col); if (wp == NULL) { return IN_UNKNOWN; } int winrow = row; int wincol = col; // compute the position in the buffer line from the posn on the screen bool below_buffer = mouse_comp_pos(wp, &row, &col, &mpos->lnum); if (!below_buffer && *wp->w_p_stc != NUL && (wp->w_p_rl ? wincol >= wp->w_width_inner - win_col_off(wp) : wincol < win_col_off(wp))) { return MOUSE_STATUSCOL; } // winpos and height may change in win_enter()! if (winrow >= wp->w_height_inner + wp->w_status_height) { // Below window if (mouse_grid <= 1 && mouse_row < Rows - p_ch && mouse_row >= Rows - p_ch - global_stl_height()) { // In global status line return IN_STATUS_LINE; } return IN_UNKNOWN; } else if (winrow >= wp->w_height_inner) { // In window status line return IN_STATUS_LINE; } if (winrow < 0 && winrow + wp->w_winbar_height >= 0) { // In winbar return MOUSE_WINBAR; } if (wincol >= wp->w_width_inner) { // In vertical separator line return IN_SEP_LINE; } if (wp != curwin || below_buffer) { return IN_UNKNOWN; } mpos->col = vcol2col(wp, mpos->lnum, col, &mpos->coladd); return IN_BUFFER; } static int do_popup(int which_button, int m_pos_flag, pos_T m_pos) { int jump_flags = 0; if (strcmp(p_mousem, "popup_setpos") == 0) { // First set the cursor position before showing the popup menu. if (VIsual_active) { // set MOUSE_MAY_STOP_VIS if we are outside the selection // or the current window (might have false negative here) if (m_pos_flag != IN_BUFFER) { jump_flags = MOUSE_MAY_STOP_VIS; } else { if (VIsual_mode == 'V') { if ((curwin->w_cursor.lnum <= VIsual.lnum && (m_pos.lnum < curwin->w_cursor.lnum || VIsual.lnum < m_pos.lnum)) || (VIsual.lnum < curwin->w_cursor.lnum && (m_pos.lnum < VIsual.lnum || curwin->w_cursor.lnum < m_pos.lnum))) { jump_flags = MOUSE_MAY_STOP_VIS; } } else if ((ltoreq(curwin->w_cursor, VIsual) && (lt(m_pos, curwin->w_cursor) || lt(VIsual, m_pos))) || (lt(VIsual, curwin->w_cursor) && (lt(m_pos, VIsual) || lt(curwin->w_cursor, m_pos)))) { jump_flags = MOUSE_MAY_STOP_VIS; } else if (VIsual_mode == Ctrl_V) { colnr_T leftcol, rightcol; getvcols(curwin, &curwin->w_cursor, &VIsual, &leftcol, &rightcol); getvcol(curwin, &m_pos, NULL, &m_pos.col, NULL); if (m_pos.col < leftcol || m_pos.col > rightcol) { jump_flags = MOUSE_MAY_STOP_VIS; } } } } else { jump_flags = MOUSE_MAY_STOP_VIS; } } if (jump_flags) { jump_flags = jump_to_mouse(jump_flags, NULL, which_button); redraw_curbuf_later(VIsual_active ? UPD_INVERTED : UPD_VALID); update_screen(); setcursor(); ui_flush(); // Update before showing popup menu } show_popupmenu(); got_click = false; // ignore release events return jump_flags; } /// Do the appropriate action for the current mouse click in the current mode. /// Not used for Command-line mode. /// /// Normal and Visual Mode: /// event modi- position visual change action /// fier cursor window /// left press - yes end yes /// left press C yes end yes "^]" (2) /// left press S yes end (popup: extend) yes "*" (2) /// left drag - yes start if moved no /// left relse - yes start if moved no /// middle press - yes if not active no put register /// middle press - yes if active no yank and put /// right press - yes start or extend yes /// right press S yes no change yes "#" (2) /// right drag - yes extend no /// right relse - yes extend no /// /// Insert or Replace Mode: /// event modi- position visual change action /// fier cursor window /// left press - yes (cannot be active) yes /// left press C yes (cannot be active) yes "CTRL-O^]" (2) /// left press S yes (cannot be active) yes "CTRL-O*" (2) /// left drag - yes start or extend (1) no CTRL-O (1) /// left relse - yes start or extend (1) no CTRL-O (1) /// middle press - no (cannot be active) no put register /// right press - yes start or extend yes CTRL-O /// right press S yes (cannot be active) yes "CTRL-O#" (2) /// /// (1) only if mouse pointer moved since press /// (2) only if click is in same buffer /// /// @param oap operator argument, can be NULL /// @param c K_LEFTMOUSE, etc /// @param dir Direction to 'put' if necessary /// @param fixindent PUT_FIXINDENT if fixing indent necessary /// /// @return true if start_arrow() should be called for edit mode. bool do_mouse(oparg_T *oap, int c, int dir, int count, bool fixindent) { int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT bool is_click; // If false it's a drag or release event bool is_drag; // If true it's a drag event static bool in_tab_line = false; // mouse clicked in tab line static pos_T orig_cursor; while (true) { which_button = get_mouse_button(KEY2TERMCAP1(c), &is_click, &is_drag); if (is_drag) { // If the next character is the same mouse event then use that // one. Speeds up dragging the status line. // Note: Since characters added to the stuff buffer in the code // below need to come before the next character, do not do this // when the current character was stuffed. if (!KeyStuffed && vpeekc() != NUL) { int nc; int save_mouse_grid = mouse_grid; int save_mouse_row = mouse_row; int save_mouse_col = mouse_col; // Need to get the character, peeking doesn't get the actual one. nc = safe_vgetc(); if (c == nc) { continue; } vungetc(nc); mouse_grid = save_mouse_grid; mouse_row = save_mouse_row; mouse_col = save_mouse_col; } } break; } if (c == K_MOUSEMOVE) { // Mouse moved without a button pressed. return false; } // Ignore drag and release events if we didn't get a click. if (is_click) { got_click = true; } else { if (!got_click) { // didn't get click, ignore return false; } if (!is_drag) { // release, reset got_click got_click = false; if (in_tab_line) { in_tab_line = false; return false; } } } // CTRL right mouse button does CTRL-T if (is_click && (mod_mask & MOD_MASK_CTRL) && which_button == MOUSE_RIGHT) { if (State & MODE_INSERT) { stuffcharReadbuff(Ctrl_O); } if (count > 1) { stuffnumReadbuff(count); } stuffcharReadbuff(Ctrl_T); got_click = false; // ignore drag&release now return false; } // CTRL only works with left mouse button if ((mod_mask & MOD_MASK_CTRL) && which_button != MOUSE_LEFT) { return false; } // When a modifier is down, ignore drag and release events, as well as // multiple clicks and the middle mouse button. // Accept shift-leftmouse drags when 'mousemodel' is "popup.*". if ((mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT | MOD_MASK_META)) && (!is_click || (mod_mask & MOD_MASK_MULTI_CLICK) || which_button == MOUSE_MIDDLE) && !((mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)) && mouse_model_popup() && which_button == MOUSE_LEFT) && !((mod_mask & MOD_MASK_ALT) && !mouse_model_popup() && which_button == MOUSE_RIGHT)) { return false; } // If the button press was used as the movement command for an operator (eg // "d"), or it is the middle button that is held down, ignore // drag/release events. if (!is_click && which_button == MOUSE_MIDDLE) { return false; } int regname = oap != NULL ? oap->regname : 0; // Middle mouse button does a 'put' of the selected text if (which_button == MOUSE_MIDDLE) { if (State == MODE_NORMAL) { // If an operator was pending, we don't know what the user wanted to do. // Go back to normal mode: Clear the operator and beep(). if (oap != NULL && oap->op_type != OP_NOP) { clearopbeep(oap); return false; } // If visual was active, yank the highlighted text and put it // before the mouse pointer position. // In Select mode replace the highlighted text with the clipboard. if (VIsual_active) { if (VIsual_select) { stuffcharReadbuff(Ctrl_G); stuffReadbuff("\"+p"); } else { stuffcharReadbuff('y'); stuffcharReadbuff(K_MIDDLEMOUSE); } return false; } // The rest is below jump_to_mouse() } else if ((State & MODE_INSERT) == 0) { return false; } // Middle click in insert mode doesn't move the mouse, just insert the // contents of a register. '.' register is special, can't insert that // with do_put(). // Also paste at the cursor if the current mode isn't in 'mouse' (only // happens for the GUI). if ((State & MODE_INSERT)) { if (regname == '.') { insert_reg(regname, true); } else { if (regname == 0 && eval_has_provider("clipboard", false)) { regname = '*'; } if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) { insert_reg(regname, true); } else { do_put(regname, NULL, BACKWARD, 1, (fixindent ? PUT_FIXINDENT : 0) | PUT_CURSEND); // Repeat it with CTRL-R CTRL-O r or CTRL-R CTRL-P r AppendCharToRedobuff(Ctrl_R); AppendCharToRedobuff(fixindent ? Ctrl_P : Ctrl_O); AppendCharToRedobuff(regname == 0 ? '"' : regname); } } return false; } } // flags for jump_to_mouse() // When dragging or button-up stay in the same window. int jump_flags = is_click ? 0 : (MOUSE_FOCUS|MOUSE_DID_MOVE); win_T *old_curwin = curwin; if (tab_page_click_defs != NULL) { // only when initialized // Check for clicking in the tab page line. if (mouse_grid <= 1 && mouse_row == 0 && firstwin->w_winrow > 0) { if (is_drag) { if (in_tab_line) { move_tab_to_mouse(); } return false; } // click in a tab selects that tab page if (is_click && cmdwin_type == 0 && mouse_col < Columns) { int tabnr = tab_page_click_defs[mouse_col].tabnr; in_tab_line = true; switch (tab_page_click_defs[mouse_col].type) { case kStlClickDisabled: break; case kStlClickTabSwitch: if (which_button != MOUSE_MIDDLE) { if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { // double click opens new page end_visual_mode(); tabpage_new(); tabpage_move(tabnr == 0 ? 9999 : tabnr - 1); } else { // Go to specified tab page, or next one if not clicking // on a label. goto_tabpage(tabnr); // It's like clicking on the status line of a window. if (curwin != old_curwin) { end_visual_mode(); } } break; } FALLTHROUGH; case kStlClickTabClose: mouse_tab_close(tabnr); break; case kStlClickFuncRun: call_click_def_func(tab_page_click_defs, mouse_col, which_button); break; } } return true; } else if (is_drag && in_tab_line) { move_tab_to_mouse(); return false; } } int m_pos_flag = 0; pos_T m_pos = { 0 }; // When 'mousemodel' is "popup" or "popup_setpos", translate mouse events: // right button up -> pop-up menu // shift-left button -> right button // alt-left button -> alt-right button if (mouse_model_popup()) { m_pos_flag = get_fpos_of_mouse(&m_pos); if (!(m_pos_flag & (IN_STATUS_LINE|MOUSE_WINBAR|MOUSE_STATUSCOL)) && which_button == MOUSE_RIGHT && !(mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) { if (!is_click) { // Ignore right button release events, only shows the popup // menu on the button down event. return false; } return (do_popup(which_button, m_pos_flag, m_pos) & CURSOR_MOVED); } // Only do this translation when mouse is over the buffer text if (!(m_pos_flag & (IN_STATUS_LINE|MOUSE_WINBAR|MOUSE_STATUSCOL)) && (which_button == MOUSE_LEFT && (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT)))) { which_button = MOUSE_RIGHT; mod_mask &= ~MOD_MASK_SHIFT; } } pos_T end_visual = { 0 }; pos_T start_visual = { 0 }; if ((State & (MODE_NORMAL | MODE_INSERT)) && !(mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL))) { if (which_button == MOUSE_LEFT) { if (is_click) { // stop Visual mode for a left click in a window, but not when on a status line if (VIsual_active) { jump_flags |= MOUSE_MAY_STOP_VIS; } } else { jump_flags |= MOUSE_MAY_VIS; } } else if (which_button == MOUSE_RIGHT) { if (is_click && VIsual_active) { // Remember the start and end of visual before moving the cursor. if (lt(curwin->w_cursor, VIsual)) { start_visual = curwin->w_cursor; end_visual = VIsual; } else { start_visual = VIsual; end_visual = curwin->w_cursor; } } jump_flags |= MOUSE_FOCUS; jump_flags |= MOUSE_MAY_VIS; } } // If an operator is pending, ignore all drags and releases until the next mouse click. if (!is_drag && oap != NULL && oap->op_type != OP_NOP) { got_click = false; oap->motion_type = kMTCharWise; } // When releasing the button let jump_to_mouse() know. if (!is_click && !is_drag) { jump_flags |= MOUSE_RELEASED; } // JUMP! int old_active = VIsual_active; pos_T save_cursor = curwin->w_cursor; jump_flags = jump_to_mouse(jump_flags, oap == NULL ? NULL : &(oap->inclusive), which_button); bool moved = (jump_flags & CURSOR_MOVED); bool in_winbar = (jump_flags & MOUSE_WINBAR); bool in_statuscol = (jump_flags & MOUSE_STATUSCOL); bool in_status_line = (jump_flags & IN_STATUS_LINE); bool in_sep_line = (jump_flags & IN_SEP_LINE); if ((in_winbar || in_status_line || in_statuscol) && is_click) { // Handle click event on window bar, status line or status column int click_grid = mouse_grid; int click_row = mouse_row; int click_col = mouse_col; win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col); if (wp == NULL) { return false; } StlClickDefinition *click_defs = in_status_line ? wp->w_status_click_defs : in_winbar ? wp->w_winbar_click_defs : wp->w_statuscol_click_defs; if (in_status_line && global_stl_height() > 0) { // global statusline is displayed for the current window, // and spans the whole screen. click_defs = curwin->w_status_click_defs; click_col = mouse_col; } if (in_statuscol && wp->w_p_rl) { click_col = wp->w_width_inner - click_col - 1; } if (in_statuscol && click_col >= (int)wp->w_statuscol_click_defs_size) { return false; } if (click_defs != NULL) { switch (click_defs[click_col].type) { case kStlClickDisabled: // If there is no click definition, still open the popupmenu for a // statuscolumn click like a click in the sign/number column does. if (in_statuscol && mouse_model_popup() && which_button == MOUSE_RIGHT && !(mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) { do_popup(which_button, m_pos_flag, m_pos); } break; case kStlClickFuncRun: call_click_def_func(click_defs, click_col, which_button); break; default: assert(false && "winbar, statusline and statuscolumn only support %@ for clicks"); break; } } if (!(in_statuscol && (jump_flags & (MOUSE_FOLD_CLOSE|MOUSE_FOLD_OPEN)))) { return false; } } else if (in_winbar || in_statuscol) { // A drag or release event in the window bar and status column has no side effects. return false; } // When jumping to another window, clear a pending operator. That's a bit // friendlier than beeping and not jumping to that window. if (curwin != old_curwin && oap != NULL && oap->op_type != OP_NOP) { clearop(oap); } if (mod_mask == 0 && !is_drag && (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN)) && which_button == MOUSE_LEFT) { // open or close a fold at this line if (jump_flags & MOUSE_FOLD_OPEN) { openFold(curwin->w_cursor, 1); } else { closeFold(curwin->w_cursor, 1); } // don't move the cursor if still in the same window if (curwin == old_curwin) { curwin->w_cursor = save_cursor; } } // Set global flag that we are extending the Visual area with mouse dragging; // temporarily minimize 'scrolloff'. if (VIsual_active && is_drag && get_scrolloff_value(curwin)) { // In the very first line, allow scrolling one line if (mouse_row == 0) { mouse_dragging = 2; } else { mouse_dragging = 1; } } // When dragging the mouse above the window, scroll down. if (is_drag && mouse_row < 0 && !in_status_line) { scroll_redraw(false, 1); mouse_row = 0; } int old_mode = VIsual_mode; if (start_visual.lnum) { // right click in visual mode linenr_T diff; // When ALT is pressed make Visual mode blockwise. if (mod_mask & MOD_MASK_ALT) { VIsual_mode = Ctrl_V; } // In Visual-block mode, divide the area in four, pick up the corner // that is in the quarter that the cursor is in. if (VIsual_mode == Ctrl_V) { colnr_T leftcol, rightcol; getvcols(curwin, &start_visual, &end_visual, &leftcol, &rightcol); if (curwin->w_curswant > (leftcol + rightcol) / 2) { end_visual.col = leftcol; } else { end_visual.col = rightcol; } if (curwin->w_cursor.lnum >= (start_visual.lnum + end_visual.lnum) / 2) { end_visual.lnum = start_visual.lnum; } // move VIsual to the right column start_visual = curwin->w_cursor; // save the cursor pos curwin->w_cursor = end_visual; coladvance(curwin, end_visual.col); VIsual = curwin->w_cursor; curwin->w_cursor = start_visual; // restore the cursor } else { // If the click is before the start of visual, change the start. // If the click is after the end of visual, change the end. If // the click is inside the visual, change the closest side. if (lt(curwin->w_cursor, start_visual)) { VIsual = end_visual; } else if (lt(end_visual, curwin->w_cursor)) { VIsual = start_visual; } else { // In the same line, compare column number if (end_visual.lnum == start_visual.lnum) { if (curwin->w_cursor.col - start_visual.col > end_visual.col - curwin->w_cursor.col) { VIsual = start_visual; } else { VIsual = end_visual; } } else { // In different lines, compare line number diff = (curwin->w_cursor.lnum - start_visual.lnum) - (end_visual.lnum - curwin->w_cursor.lnum); if (diff > 0) { // closest to end VIsual = start_visual; } else if (diff < 0) { // closest to start VIsual = end_visual; } else { // in the middle line if (curwin->w_cursor.col < (start_visual.col + end_visual.col) / 2) { VIsual = end_visual; } else { VIsual = start_visual; } } } } } } else if ((State & MODE_INSERT) && VIsual_active) { // If Visual mode started in insert mode, execute "CTRL-O" stuffcharReadbuff(Ctrl_O); } // Middle mouse click: Put text before cursor. if (which_button == MOUSE_MIDDLE) { if (regname == 0 && eval_has_provider("clipboard", false)) { regname = '*'; } if (yank_register_mline(regname)) { if (mouse_past_bottom) { dir = FORWARD; } } else if (mouse_past_eol) { dir = FORWARD; } int c1, c2; if (fixindent) { c1 = (dir == BACKWARD) ? '[' : ']'; c2 = 'p'; } else { c1 = (dir == FORWARD) ? 'p' : 'P'; c2 = NUL; } prep_redo(regname, count, NUL, c1, NUL, c2, NUL); // Remember where the paste started, so in edit() Insstart can be set to this position if (restart_edit != 0) { where_paste_started = curwin->w_cursor; } do_put(regname, NULL, dir, count, (fixindent ? PUT_FIXINDENT : 0)| PUT_CURSEND); } else if (((mod_mask & MOD_MASK_CTRL) || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) && bt_quickfix(curbuf)) { // Ctrl-Mouse click or double click in a quickfix window jumps to the // error under the mouse pointer. if (curwin->w_llist_ref == NULL) { // quickfix window do_cmdline_cmd(".cc"); } else { // location list window do_cmdline_cmd(".ll"); } got_click = false; // ignore drag&release now } else if ((mod_mask & MOD_MASK_CTRL) || (curbuf->b_help && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) { // Ctrl-Mouse click (or double click in a help window) jumps to the tag // under the mouse pointer. if (State & MODE_INSERT) { stuffcharReadbuff(Ctrl_O); } stuffcharReadbuff(Ctrl_RSB); got_click = false; // ignore drag&release now } else if ((mod_mask & MOD_MASK_SHIFT)) { // Shift-Mouse click searches for the next occurrence of the word under // the mouse pointer if (State & MODE_INSERT || (VIsual_active && VIsual_select)) { stuffcharReadbuff(Ctrl_O); } if (which_button == MOUSE_LEFT) { stuffcharReadbuff('*'); } else { // MOUSE_RIGHT stuffcharReadbuff('#'); } } else if (in_status_line || in_sep_line) { // Do nothing if on status line or vertical separator // Handle double clicks otherwise } else if ((mod_mask & MOD_MASK_MULTI_CLICK) && (State & (MODE_NORMAL | MODE_INSERT))) { if (is_click || !VIsual_active) { if (VIsual_active) { orig_cursor = VIsual; } else { VIsual = curwin->w_cursor; orig_cursor = VIsual; VIsual_active = true; VIsual_reselect = true; // start Select mode if 'selectmode' contains "mouse" may_start_select('o'); setmouse(); } if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { // Double click with ALT pressed makes it blockwise. if (mod_mask & MOD_MASK_ALT) { VIsual_mode = Ctrl_V; } else { VIsual_mode = 'v'; } } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK) { VIsual_mode = 'V'; } else if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK) { VIsual_mode = Ctrl_V; } } // A double click selects a word or a block. if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) { pos_T *pos = NULL; if (is_click) { // If the character under the cursor (skipping white space) is // not a word character, try finding a match and select a (), // {}, [], #if/#endif, etc. block. end_visual = curwin->w_cursor; int gc; while (gc = gchar_pos(&end_visual), ascii_iswhite(gc)) { inc(&end_visual); } if (oap != NULL) { oap->motion_type = kMTCharWise; } if (oap != NULL && VIsual_mode == 'v' && !vim_iswordc(gchar_pos(&end_visual)) && equalpos(curwin->w_cursor, VIsual) && (pos = findmatch(oap, NUL)) != NULL) { curwin->w_cursor = *pos; if (oap->motion_type == kMTLineWise) { VIsual_mode = 'V'; } else if (*p_sel == 'e') { if (lt(curwin->w_cursor, VIsual)) { VIsual.col++; } else { curwin->w_cursor.col++; } } } } if (pos == NULL && (is_click || is_drag)) { // When not found a match or when dragging: extend to include a word. if (lt(curwin->w_cursor, orig_cursor)) { find_start_of_word(&curwin->w_cursor); find_end_of_word(&VIsual); } else { find_start_of_word(&VIsual); if (*p_sel == 'e' && *get_cursor_pos_ptr() != NUL) { curwin->w_cursor.col += utfc_ptr2len(get_cursor_pos_ptr()); } find_end_of_word(&curwin->w_cursor); } } curwin->w_set_curswant = true; } if (is_click) { redraw_curbuf_later(UPD_INVERTED); // update the inversion } } else if (VIsual_active && !old_active) { if (mod_mask & MOD_MASK_ALT) { VIsual_mode = Ctrl_V; } else { VIsual_mode = 'v'; } } // If Visual mode changed show it later. if ((!VIsual_active && old_active && mode_displayed) || (VIsual_active && p_smd && msg_silent == 0 && (!old_active || VIsual_mode != old_mode))) { redraw_cmdline = true; } return moved; } void ins_mouse(int c) { win_T *old_curwin = curwin; undisplay_dollar(); pos_T tpos = curwin->w_cursor; if (do_mouse(NULL, c, BACKWARD, 1, 0)) { win_T *new_curwin = curwin; if (curwin != old_curwin && win_valid(old_curwin)) { // Mouse took us to another window. We need to go back to the // previous one to stop insert there properly. curwin = old_curwin; curbuf = curwin->w_buffer; if (bt_prompt(curbuf)) { // Restart Insert mode when re-entering the prompt buffer. curbuf->b_prompt_insert = 'A'; } } start_arrow(curwin == old_curwin ? &tpos : NULL); if (curwin != new_curwin && win_valid(new_curwin)) { curwin = new_curwin; curbuf = curwin->w_buffer; } set_can_cindent(true); } // redraw status lines (in case another window became active) redraw_statuslines(); } /// Common mouse wheel scrolling, shared between Insert mode and NV modes. /// Default action is to scroll mouse_vert_step lines (or mouse_hor_step columns /// depending on the scroll direction) or one page when Shift or Ctrl is used. /// Direction is indicated by "cap->arg": /// K_MOUSEUP - MSCR_UP /// K_MOUSEDOWN - MSCR_DOWN /// K_MOUSELEFT - MSCR_LEFT /// K_MOUSERIGHT - MSCR_RIGHT /// "curwin" may have been changed to the window that should be scrolled and /// differ from the window that actually has focus. void do_mousescroll(cmdarg_T *cap) { bool shift_or_ctrl = mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL); if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN) { // Vertical scrolling if ((State & MODE_NORMAL) && shift_or_ctrl) { // whole page up or down pagescroll(cap->arg ? FORWARD : BACKWARD, 1, false); } else { if (shift_or_ctrl) { // whole page up or down cap->count1 = curwin->w_botline - curwin->w_topline; } else { cap->count1 = (int)p_mousescroll_vert; } if (cap->count1 > 0) { cap->count0 = cap->count1; nv_scroll_line(cap); } } } else { // Horizontal scrolling int step = shift_or_ctrl ? curwin->w_width_inner : (int)p_mousescroll_hor; colnr_T leftcol = curwin->w_leftcol + (cap->arg == MSCR_RIGHT ? -step : +step); leftcol = MAX(leftcol, 0); do_mousescroll_horiz(leftcol); } } /// Implementation for scrolling in Insert mode in direction "dir", which is one /// of the MSCR_ values. void ins_mousescroll(int dir) { cmdarg_T cap; oparg_T oa; CLEAR_FIELD(cap); clear_oparg(&oa); cap.oap = &oa; cap.arg = dir; switch (dir) { case MSCR_UP: cap.cmdchar = K_MOUSEUP; break; case MSCR_DOWN: cap.cmdchar = K_MOUSEDOWN; break; case MSCR_LEFT: cap.cmdchar = K_MOUSELEFT; break; case MSCR_RIGHT: cap.cmdchar = K_MOUSERIGHT; break; default: siemsg("Invalid ins_mousescroll() argument: %d", dir); } win_T *old_curwin = curwin; if (mouse_row >= 0 && mouse_col >= 0) { // Find the window at the mouse pointer coordinates. // NOTE: Must restore "curwin" to "old_curwin" before returning! int grid = mouse_grid; int row = mouse_row; int col = mouse_col; curwin = mouse_find_win(&grid, &row, &col); if (curwin == NULL) { curwin = old_curwin; return; } curbuf = curwin->w_buffer; } if (curwin == old_curwin) { // Don't scroll the current window if the popup menu is visible. if (pum_visible()) { return; } undisplay_dollar(); } pos_T orig_cursor = curwin->w_cursor; // Call the common mouse scroll function shared with other modes. do_mousescroll(&cap); curwin->w_redr_status = true; curwin = old_curwin; curbuf = curwin->w_buffer; if (!equalpos(curwin->w_cursor, orig_cursor)) { start_arrow(&orig_cursor); set_can_cindent(true); } } /// Return true if "c" is a mouse key. bool is_mouse_key(int c) { return c == K_LEFTMOUSE || c == K_LEFTMOUSE_NM || c == K_LEFTDRAG || c == K_LEFTRELEASE || c == K_LEFTRELEASE_NM || c == K_MOUSEMOVE || c == K_MIDDLEMOUSE || c == K_MIDDLEDRAG || c == K_MIDDLERELEASE || c == K_RIGHTMOUSE || c == K_RIGHTDRAG || c == K_RIGHTRELEASE || c == K_MOUSEDOWN || c == K_MOUSEUP || c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_X1MOUSE || c == K_X1DRAG || c == K_X1RELEASE || c == K_X2MOUSE || c == K_X2DRAG || c == K_X2RELEASE; } /// @return true when 'mousemodel' is set to "popup" or "popup_setpos". static bool mouse_model_popup(void) { return p_mousem[0] == 'p'; } static win_T *dragwin = NULL; ///< window being dragged /// Reset the window being dragged. To be called when switching tab page. void reset_dragwin(void) { dragwin = NULL; } /// Move the cursor to the specified row and column on the screen. /// Change current window if necessary. Returns an integer with the /// CURSOR_MOVED bit set if the cursor has moved or unset otherwise. /// /// The MOUSE_FOLD_CLOSE bit is set when clicked on the '-' in a fold column. /// The MOUSE_FOLD_OPEN bit is set when clicked on the '+' in a fold column. /// /// If flags has MOUSE_FOCUS, then the current window will not be changed, and /// if the mouse is outside the window then the text will scroll, or if the /// mouse was previously on a status line, then the status line may be dragged. /// /// If flags has MOUSE_MAY_VIS, then VIsual mode will be started before the /// cursor is moved unless the cursor was on a status line or window bar. /// This function returns one of IN_UNKNOWN, IN_BUFFER, IN_STATUS_LINE or /// IN_SEP_LINE depending on where the cursor was clicked. /// /// If flags has MOUSE_MAY_STOP_VIS, then Visual mode will be stopped, unless /// the mouse is on the status line or window bar of the same window. /// /// If flags has MOUSE_DID_MOVE, nothing is done if the mouse didn't move since /// the last call. /// /// If flags has MOUSE_SETPOS, nothing is done, only the current position is /// remembered. /// /// @param inclusive used for inclusive operator, can be NULL /// @param which_button MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE int jump_to_mouse(int flags, bool *inclusive, int which_button) { static int status_line_offset = 0; // #lines offset from status line static int sep_line_offset = 0; // #cols offset from sep line static bool on_status_line = false; static bool on_sep_line = false; static bool on_winbar = false; static bool on_statuscol = false; static int prev_row = -1; static int prev_col = -1; static int did_drag = false; // drag was noticed int count; bool first; int row = mouse_row; int col = mouse_col; int grid = mouse_grid; int fdc = 0; bool keep_focus = flags & MOUSE_FOCUS; mouse_past_bottom = false; mouse_past_eol = false; if (flags & MOUSE_RELEASED) { // On button release we may change window focus if positioned on a // status line and no dragging happened. if (dragwin != NULL && !did_drag) { flags &= ~(MOUSE_FOCUS | MOUSE_DID_MOVE); } dragwin = NULL; did_drag = false; } if ((flags & MOUSE_DID_MOVE) && prev_row == mouse_row && prev_col == mouse_col) { retnomove: // before moving the cursor for a left click which is NOT in a status // line, stop Visual mode if (status_line_offset) { return IN_STATUS_LINE; } if (sep_line_offset) { return IN_SEP_LINE; } if (on_winbar) { return IN_OTHER_WIN | MOUSE_WINBAR; } if (on_statuscol) { return IN_OTHER_WIN | MOUSE_STATUSCOL; } if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); redraw_curbuf_later(UPD_INVERTED); // delete the inversion } return IN_BUFFER; } prev_row = mouse_row; prev_col = mouse_col; if (flags & MOUSE_SETPOS) { goto retnomove; // ugly goto... } if (row < 0 || col < 0) { // check if it makes sense return IN_UNKNOWN; } // find the window where the row is in win_T *wp = mouse_find_win(&grid, &row, &col); if (wp == NULL) { return IN_UNKNOWN; } bool below_window = grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height; on_status_line = below_window && row + wp->w_winbar_height - wp->w_height + 1 == 1; on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width && col - wp->w_width + 1 == 1; on_winbar = row < 0 && row + wp->w_winbar_height >= 0; on_statuscol = !below_window && !on_status_line && !on_sep_line && !on_winbar && *wp->w_p_stc != NUL && (wp->w_p_rl ? col >= wp->w_width_inner - win_col_off(wp) : col < win_col_off(wp)); // The rightmost character of the status line might be a vertical // separator character if there is no connecting window to the right. if (on_status_line && on_sep_line) { if (stl_connected(wp)) { on_sep_line = false; } else { on_status_line = false; } } if (keep_focus) { // If we can't change focus, set the value of row, col and grid back to absolute values // since the values relative to the window are only used when keep_focus is false row = mouse_row; col = mouse_col; grid = mouse_grid; } win_T *old_curwin = curwin; pos_T old_cursor = curwin->w_cursor; if (!keep_focus) { if (on_winbar) { return IN_OTHER_WIN | MOUSE_WINBAR; } if (on_statuscol) { goto foldclick; } fdc = win_fdccol_count(wp); dragwin = NULL; // winpos and height may change in win_enter()! if (below_window) { // In (or below) status line status_line_offset = row + wp->w_winbar_height - wp->w_height + 1; dragwin = wp; } else { status_line_offset = 0; } if (grid == DEFAULT_GRID_HANDLE && col >= wp->w_width) { // In separator line sep_line_offset = col - wp->w_width + 1; dragwin = wp; } else { sep_line_offset = 0; } // The rightmost character of the status line might be a vertical // separator character if there is no connecting window to the right. if (status_line_offset && sep_line_offset) { if (stl_connected(wp)) { sep_line_offset = 0; } else { status_line_offset = 0; } } // Before jumping to another buffer, or moving the cursor for a left // click, stop Visual mode. if (VIsual_active && (wp->w_buffer != curwin->w_buffer || (!status_line_offset && !sep_line_offset && (wp->w_p_rl ? col < wp->w_width_inner - fdc : col >= fdc + (wp != cmdwin_win ? 0 : 1)) && (flags & MOUSE_MAY_STOP_VIS)))) { end_visual_mode(); redraw_curbuf_later(UPD_INVERTED); // delete the inversion } if (cmdwin_type != 0 && wp != cmdwin_win) { // A click outside the command-line window: Use modeless // selection if possible. Allow dragging the status lines. sep_line_offset = 0; row = 0; col += wp->w_wincol; wp = cmdwin_win; } // Only change window focus when not clicking on or dragging the // status line. Do change focus when releasing the mouse button // (MOUSE_FOCUS was set above if we dragged first). if (dragwin == NULL || (flags & MOUSE_RELEASED)) { win_enter(wp, true); // can make wp invalid! } // set topline, to be able to check for double click ourselves if (curwin != old_curwin) { set_mouse_topline(curwin); } if (status_line_offset) { // In (or below) status line // Don't use start_arrow() if we're in the same window if (curwin == old_curwin) { return IN_STATUS_LINE; } return IN_STATUS_LINE | CURSOR_MOVED; } if (sep_line_offset) { // In (or below) status line // Don't use start_arrow() if we're in the same window if (curwin == old_curwin) { return IN_SEP_LINE; } return IN_SEP_LINE | CURSOR_MOVED; } curwin->w_cursor.lnum = curwin->w_topline; } else if (status_line_offset) { if (which_button == MOUSE_LEFT && dragwin != NULL) { // Drag the status line count = row - dragwin->w_winrow - dragwin->w_height + 1 - status_line_offset; win_drag_status_line(dragwin, count); did_drag |= count; } return IN_STATUS_LINE; // Cursor didn't move } else if (sep_line_offset && which_button == MOUSE_LEFT) { if (dragwin != NULL) { // Drag the separator column count = col - dragwin->w_wincol - dragwin->w_width + 1 - sep_line_offset; win_drag_vsep_line(dragwin, count); did_drag |= count; } return IN_SEP_LINE; // Cursor didn't move } else if (on_status_line && which_button == MOUSE_RIGHT) { return IN_STATUS_LINE; } else if (on_winbar && which_button == MOUSE_RIGHT) { // After a click on the window bar don't start Visual mode. return IN_OTHER_WIN | MOUSE_WINBAR; } else if (on_statuscol && which_button == MOUSE_RIGHT) { // After a click on the status column don't start Visual mode. return IN_OTHER_WIN | MOUSE_STATUSCOL; } else { // keep_window_focus must be true // before moving the cursor for a left click, stop Visual mode if (flags & MOUSE_MAY_STOP_VIS) { end_visual_mode(); redraw_curbuf_later(UPD_INVERTED); // delete the inversion } if (grid == 0) { row -= curwin->w_grid_alloc.comp_row + curwin->w_grid.row_offset; col -= curwin->w_grid_alloc.comp_col + curwin->w_grid.col_offset; } else if (grid != DEFAULT_GRID_HANDLE) { row -= curwin->w_grid.row_offset; col -= curwin->w_grid.col_offset; } // When clicking beyond the end of the window, scroll the screen. // Scroll by however many rows outside the window we are. if (row < 0) { count = 0; for (first = true; curwin->w_topline > 1;) { if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) { count++; } else { count += plines_win(curwin, curwin->w_topline - 1, true); } if (!first && count > -row) { break; } first = false; hasFolding(curwin, curwin->w_topline, &curwin->w_topline, NULL); if (curwin->w_topfill < win_get_fill(curwin, curwin->w_topline)) { curwin->w_topfill++; } else { curwin->w_topline--; curwin->w_topfill = 0; } } check_topfill(curwin, false); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); redraw_later(curwin, UPD_VALID); row = 0; } else if (row >= curwin->w_height_inner) { count = 0; for (first = true; curwin->w_topline < curbuf->b_ml.ml_line_count;) { if (curwin->w_topfill > 0) { count++; } else { count += plines_win(curwin, curwin->w_topline, true); } if (!first && count > row - curwin->w_height_inner + 1) { break; } first = false; if (curwin->w_topfill > 0) { curwin->w_topfill--; } else { if (hasFolding(curwin, curwin->w_topline, NULL, &curwin->w_topline) && curwin->w_topline == curbuf->b_ml.ml_line_count) { break; } curwin->w_topline++; curwin->w_topfill = win_get_fill(curwin, curwin->w_topline); } } check_topfill(curwin, false); redraw_later(curwin, UPD_VALID); curwin->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); row = curwin->w_height_inner - 1; } else if (row == 0) { // When dragging the mouse, while the text has been scrolled up as // far as it goes, moving the mouse in the top line should scroll // the text down (done later when recomputing w_topline). if (mouse_dragging > 0 && curwin->w_cursor.lnum == curwin->w_buffer->b_ml.ml_line_count && curwin->w_cursor.lnum == curwin->w_topline) { curwin->w_valid &= ~(VALID_TOPLINE); } } } foldclick:; colnr_T col_from_screen = -1; int mouse_fold_flags = 0; mouse_check_grid(&col_from_screen, &mouse_fold_flags); // compute the position in the buffer line from the posn on the screen if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum)) { mouse_past_bottom = true; } // Start Visual mode before coladvance(), for when 'sel' != "old" if ((flags & MOUSE_MAY_VIS) && !VIsual_active) { VIsual = old_cursor; VIsual_active = true; VIsual_reselect = true; // if 'selectmode' contains "mouse", start Select mode may_start_select('o'); setmouse(); if (p_smd && msg_silent == 0) { redraw_cmdline = true; // show visual mode later } } if (col_from_screen >= 0) { col = col_from_screen; } curwin->w_curswant = col; curwin->w_set_curswant = false; // May still have been true if (coladvance(curwin, col) == FAIL) { // Mouse click beyond end of line if (inclusive != NULL) { *inclusive = true; } mouse_past_eol = true; } else if (inclusive != NULL) { *inclusive = false; } count = on_statuscol ? (IN_OTHER_WIN|MOUSE_STATUSCOL) : IN_BUFFER; if (curwin != old_curwin || curwin->w_cursor.lnum != old_cursor.lnum || curwin->w_cursor.col != old_cursor.col) { count |= CURSOR_MOVED; // Cursor has moved } count |= mouse_fold_flags; return count; } /// Make a horizontal scroll to "leftcol". /// @return true if the cursor moved, false otherwise. static bool do_mousescroll_horiz(colnr_T leftcol) { if (curwin->w_p_wrap) { return false; // no horizontal scrolling when wrapping } if (curwin->w_leftcol == leftcol) { return false; // already there } // When the line of the cursor is too short, move the cursor to the // longest visible line. if (!virtual_active(curwin) && leftcol > scroll_line_len(curwin->w_cursor.lnum)) { curwin->w_cursor.lnum = find_longest_lnum(); curwin->w_cursor.col = 0; } return set_leftcol(leftcol); } /// Normal and Visual modes implementation for scrolling in direction /// "cap->arg", which is one of the MSCR_ values. void nv_mousescroll(cmdarg_T *cap) { win_T *const old_curwin = curwin; if (mouse_row >= 0 && mouse_col >= 0) { // Find the window at the mouse pointer coordinates. // NOTE: Must restore "curwin" to "old_curwin" before returning! int grid = mouse_grid; int row = mouse_row; int col = mouse_col; curwin = mouse_find_win(&grid, &row, &col); if (curwin == NULL) { curwin = old_curwin; return; } curbuf = curwin->w_buffer; } // Call the common mouse scroll function shared with other modes. do_mousescroll(cap); curwin->w_redr_status = true; curwin = old_curwin; curbuf = curwin->w_buffer; } /// Mouse clicks and drags. void nv_mouse(cmdarg_T *cap) { do_mouse(cap->oap, cap->cmdchar, BACKWARD, cap->count1, 0); } /// Compute the position in the buffer line from the posn on the screen in /// window "win". /// Returns true if the position is below the last line. bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump) { int col = *colp; int row = *rowp; bool retval = false; int count; if (win->w_p_rl) { col = win->w_width_inner - 1 - col; } linenr_T lnum = win->w_topline; while (row > 0) { // Don't include filler lines in "count" if (win_may_fill(win)) { row -= lnum == win->w_topline ? win->w_topfill : win_get_fill(win, lnum); count = plines_win_nofill(win, lnum, false); } else { count = plines_win(win, lnum, false); } if (win->w_skipcol > 0 && lnum == win->w_topline) { int width1 = win->w_width_inner - win_col_off(win); if (width1 > 0) { int skip_lines = 0; // Adjust for 'smoothscroll' clipping the top screen lines. // A similar formula is used in curs_columns(). if (win->w_skipcol > width1) { skip_lines = (win->w_skipcol - width1) / (width1 + win_col_off2(win)) + 1; } else if (win->w_skipcol > 0) { skip_lines = 1; } count -= skip_lines; } } if (count > row) { break; // Position is in this buffer line. } hasFolding(win, lnum, NULL, &lnum); if (lnum == win->w_buffer->b_ml.ml_line_count) { retval = true; break; // past end of file } row -= count; lnum++; } if (!retval) { // Compute the column without wrapping. int off = win_col_off(win) - win_col_off2(win); col = MAX(col, off); col += row * (win->w_width_inner - off); // Add skip column for the topline. if (lnum == win->w_topline) { col += win->w_skipcol; } } if (!win->w_p_wrap) { col += win->w_leftcol; } // skip line number and fold column in front of the line col -= win_col_off(win); col = MAX(col, 0); *colp = col; *rowp = row; *lnump = lnum; return retval; } /// Find the window at "grid" position "*rowp" and "*colp". The positions are /// updated to become relative to the top-left of the window. /// /// @return NULL when something is wrong. win_T *mouse_find_win(int *gridp, int *rowp, int *colp) { win_T *wp_grid = mouse_find_grid_win(gridp, rowp, colp); if (wp_grid) { return wp_grid; } else if (*gridp > 1) { return NULL; } frame_T *fp = topframe; *rowp -= firstwin->w_winrow; while (true) { if (fp->fr_layout == FR_LEAF) { break; } if (fp->fr_layout == FR_ROW) { for (fp = fp->fr_child; fp->fr_next != NULL; fp = fp->fr_next) { if (*colp < fp->fr_width) { break; } *colp -= fp->fr_width; } } else { // fr_layout == FR_COL for (fp = fp->fr_child; fp->fr_next != NULL; fp = fp->fr_next) { if (*rowp < fp->fr_height) { break; } *rowp -= fp->fr_height; } } } // When using a timer that closes a window the window might not actually // exist. FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp == fp->fr_win) { *rowp -= wp->w_winbar_height; return wp; } } return NULL; } static win_T *mouse_find_grid_win(int *gridp, int *rowp, int *colp) { if (*gridp == msg_grid.handle) { *rowp += msg_grid_pos; *gridp = DEFAULT_GRID_HANDLE; } else if (*gridp > 1) { win_T *wp = get_win_by_grid_handle(*gridp); if (wp && wp->w_grid_alloc.chars && !(wp->w_floating && !wp->w_config.mouse)) { *rowp = MIN(*rowp - wp->w_grid.row_offset, wp->w_grid.rows - 1); *colp = MIN(*colp - wp->w_grid.col_offset, wp->w_grid.cols - 1); return wp; } } else if (*gridp == 0) { ScreenGrid *grid = ui_comp_mouse_focus(*rowp, *colp); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (&wp->w_grid_alloc != grid) { continue; } *gridp = grid->handle; *rowp -= grid->comp_row + wp->w_grid.row_offset; *colp -= grid->comp_col + wp->w_grid.col_offset; return wp; } // no float found, click on the default grid // TODO(bfredl): grid can be &pum_grid, allow select pum items by mouse? *gridp = DEFAULT_GRID_HANDLE; } return NULL; } /// Convert a virtual (screen) column to a character column. /// The first column is zero. colnr_T vcol2col(win_T *wp, linenr_T lnum, colnr_T vcol, colnr_T *coladdp) FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT { // try to advance to the specified column char *line = ml_get_buf(wp->w_buffer, lnum); CharsizeArg csarg; CSType cstype = init_charsize_arg(&csarg, wp, lnum, line); StrCharInfo ci = utf_ptr2StrCharInfo(line); int cur_vcol = 0; while (cur_vcol < vcol && *ci.ptr != NUL) { int next_vcol = cur_vcol + win_charsize(cstype, cur_vcol, ci.ptr, ci.chr.value, &csarg).width; if (next_vcol > vcol) { break; } cur_vcol = next_vcol; ci = utfc_next(ci); } if (coladdp != NULL) { *coladdp = vcol - cur_vcol; } return (colnr_T)(ci.ptr - line); } /// Set UI mouse depending on current mode and 'mouse'. /// /// Emits mouse_on/mouse_off UI event (unless 'mouse' is empty). void setmouse(void) { ui_cursor_shape(); ui_check_mouse(); } // Set orig_topline. Used when jumping to another window, so that a double // click still works. static void set_mouse_topline(win_T *wp) { orig_topline = wp->w_topline; orig_topfill = wp->w_topfill; } /// Return length of line "lnum" for horizontal scrolling. static colnr_T scroll_line_len(linenr_T lnum) { colnr_T col = 0; char *line = ml_get(lnum); if (*line != NUL) { while (true) { int numchar = win_chartabsize(curwin, line, col); MB_PTR_ADV(line); if (*line == NUL) { // don't count the last character break; } col += numchar; } } return col; } /// Find longest visible line number. static linenr_T find_longest_lnum(void) { linenr_T ret = 0; // Calculate maximum for horizontal scrollbar. Check for reasonable // line numbers, topline and botline can be invalid when displaying is // postponed. if (curwin->w_topline <= curwin->w_cursor.lnum && curwin->w_botline > curwin->w_cursor.lnum && curwin->w_botline <= curbuf->b_ml.ml_line_count + 1) { colnr_T max = 0; // Use maximum of all visible lines. Remember the lnum of the // longest line, closest to the cursor line. Used when scrolling // below. for (linenr_T lnum = curwin->w_topline; lnum < curwin->w_botline; lnum++) { colnr_T len = scroll_line_len(lnum); if (len > max) { max = len; ret = lnum; } else if (len == max && abs(lnum - curwin->w_cursor.lnum) < abs(ret - curwin->w_cursor.lnum)) { ret = lnum; } } } else { // Use cursor line only. ret = curwin->w_cursor.lnum; } return ret; } /// Check clicked cell on its grid static void mouse_check_grid(colnr_T *vcolp, int *flagsp) FUNC_ATTR_NONNULL_ALL { int click_grid = mouse_grid; int click_row = mouse_row; int click_col = mouse_col; // XXX: this doesn't change click_grid if it is 1, even with multigrid if (mouse_find_win(&click_grid, &click_row, &click_col) != curwin // Only use vcols[] after the window was redrawn. Mainly matters // for tests, a user would not click before redrawing. || curwin->w_redr_type != 0) { return; } ScreenGrid *gp = &curwin->w_grid; int start_row = 0; int start_col = 0; grid_adjust(&gp, &start_row, &start_col); if (gp->handle != click_grid || gp->chars == NULL) { return; } click_row += start_row; click_col += start_col; if (click_row < 0 || click_row >= gp->rows || click_col < 0 || click_col >= gp->cols) { return; } const size_t off = gp->line_offset[click_row] + (size_t)click_col; colnr_T col_from_screen = gp->vcols[off]; if (col_from_screen >= 0) { // Use the virtual column from vcols[], it is accurate also after // concealed characters. *vcolp = col_from_screen; } if (col_from_screen == -2) { *flagsp |= MOUSE_FOLD_OPEN; } else if (col_from_screen == -3) { *flagsp |= MOUSE_FOLD_CLOSE; } } /// "getmousepos()" function void f_getmousepos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) { int row = mouse_row; int col = mouse_col; int grid = mouse_grid; varnumber_T winid = 0; varnumber_T winrow = 0; varnumber_T wincol = 0; linenr_T lnum = 0; varnumber_T column = 0; colnr_T coladd = 0; tv_dict_alloc_ret(rettv); dict_T *d = rettv->vval.v_dict; tv_dict_add_nr(d, S_LEN("screenrow"), (varnumber_T)mouse_row + 1); tv_dict_add_nr(d, S_LEN("screencol"), (varnumber_T)mouse_col + 1); win_T *wp = mouse_find_win(&grid, &row, &col); if (wp != NULL) { int height = wp->w_height + wp->w_hsep_height + wp->w_status_height; // The height is adjusted by 1 when there is a bottom border. This is not // necessary for a top border since `row` starts at -1 in that case. if (row < height + wp->w_border_adj[2]) { winid = wp->handle; winrow = row + 1 + wp->w_winrow_off; // Adjust by 1 for top border wincol = col + 1 + wp->w_wincol_off; // Adjust by 1 for left border if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) { mouse_comp_pos(wp, &row, &col, &lnum); col = vcol2col(wp, lnum, col, &coladd); column = col + 1; } } } tv_dict_add_nr(d, S_LEN("winid"), winid); tv_dict_add_nr(d, S_LEN("winrow"), winrow); tv_dict_add_nr(d, S_LEN("wincol"), wincol); tv_dict_add_nr(d, S_LEN("line"), (varnumber_T)lnum); tv_dict_add_nr(d, S_LEN("column"), column); tv_dict_add_nr(d, S_LEN("coladd"), coladd); }