Files
neovim/src/nvim/mouse.c
bfredl d6ecead364 refactor(screen): screen.c delenda est
drawscreen.c vs screen.c makes absolutely no sense.
The screen exists only to draw upon it, therefore helper functions
are distributed randomly between screen.c and the file that
does the redrawing. In addition screen.c does a lot of drawing on the
screen.

It made more sense for vim/vim as our grid.c is their screen.c

Not sure if we want to dump all the code for option chars into
optionstr.c, so keep these in a optionchar.c for now.
2023-03-14 13:37:43 +01:00

1806 lines
56 KiB
C

// This is an open source non-commercial project. Dear PVS-Studio, please check
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/keycodes.h"
#include "nvim/macros.h"
#include "nvim/mark.h"
#include "nvim/mbyte.h"
#include "nvim/memline.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/plines.h"
#include "nvim/pos.h"
#include "nvim/search.h"
#include "nvim/state.h"
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
#include "nvim/types.h"
#include "nvim/ui.h"
#include "nvim/ui_compositor.h"
#include "nvim/vim.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;
int cclass;
line = ml_get(pos->lnum);
if (*p_sel == 'e' && pos->col > 0) {
pos->col--;
pos->col -= utf_head_off(line, line + pos->col);
}
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);
}
}
/// 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"
: "?")))
},
},
{
.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;
(void)call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv);
tv_clear(&rettv);
}
/// 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;
}
// winpos and height may change in win_enter()!
if (row + wp->w_winbar_height >= wp->w_height) { // In (or below) status line
return IN_STATUS_LINE;
}
if (col >= wp->w_width) { // In vertical separator line
return IN_SEP_LINE;
}
if (wp != curwin) {
return IN_UNKNOWN;
}
// compute the position in the buffer line from the posn on the screen
if (mouse_comp_pos(curwin, &row, &col, &mpos->lnum)) {
return IN_STATUS_LINE; // past bottom
}
mpos->col = vcol2col(wp, mpos->lnum, col);
mpos->coladd = 0;
return IN_BUFFER;
}
/// 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, long count, bool fixindent)
{
static bool got_click = false; // got a click some time back
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
int jump_flags = 0; // flags for jump_to_mouse()
pos_T start_visual;
bool moved; // Has cursor moved?
bool in_winbar; // mouse in window bar
bool in_statuscol; // mouse in status column
bool in_status_line; // mouse in status line
static bool in_tab_line = false; // mouse clicked in tab line
bool in_sep_line; // mouse in vertical separator line
int c1;
win_T *old_curwin = curwin;
static pos_T orig_cursor;
colnr_T leftcol, rightcol;
pos_T end_visual;
int old_active = VIsual_active;
int old_mode = VIsual_mode;
int regname;
pos_T save_cursor = curwin->w_cursor;
for (;;) {
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<MOUSE>"), or it is the middle button that is held down, ignore
// drag/release events.
if (!is_click && which_button == MOUSE_MIDDLE) {
return false;
}
if (oap != NULL) {
regname = oap->regname;
} else {
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")) {
regname = '*';
}
if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) {
insert_reg(regname, true);
} else {
do_put(regname, NULL, BACKWARD, 1L,
(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;
}
}
// When dragging or button-up stay in the same window.
if (!is_click) {
jump_flags |= MOUSE_FOCUS | MOUSE_DID_MOVE;
}
start_visual.lnum = 0;
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) {
in_tab_line = true;
c1 = tab_page_click_defs[mouse_col].tabnr;
switch (tab_page_click_defs[mouse_col].type) {
case kStlClickDisabled:
break;
case kStlClickTabClose: {
tabpage_T *tp;
// Close the current or specified tab page.
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);
}
break;
}
case kStlClickTabSwitch:
if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) {
// double click opens new page
end_visual_mode();
tabpage_new();
tabpage_move(c1 == 0 ? 9999 : c1 - 1);
} else {
// Go to specified tab page, or next one if not clicking
// on a label.
goto_tabpage(c1);
// It's like clicking on the status line of a window.
if (curwin != old_curwin) {
end_visual_mode();
}
}
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;
}
}
// 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()) {
if (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;
}
jump_flags = 0;
if (strcmp(p_mousem, "popup_setpos") == 0) {
// First set the cursor position before showing the popup
// menu.
if (VIsual_active) {
pos_T m_pos;
// set MOUSE_MAY_STOP_VIS if we are outside the
// selection or the current window (might have false
// negative here)
if (mouse_row < curwin->w_winrow
|| mouse_row > (curwin->w_winrow + curwin->w_height)) {
jump_flags = MOUSE_MAY_STOP_VIS;
} else if (get_fpos_of_mouse(&m_pos) != 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) {
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 & CURSOR_MOVED) != 0;
}
if (which_button == MOUSE_LEFT
&& (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_ALT))) {
which_button = MOUSE_RIGHT;
mod_mask &= ~MOD_MASK_SHIFT;
}
}
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!
jump_flags = jump_to_mouse(jump_flags,
oap == NULL ? NULL : &(oap->inclusive),
which_button);
moved = (jump_flags & CURSOR_MOVED);
in_winbar = (jump_flags & MOUSE_WINBAR);
in_statuscol = (jump_flags & MOUSE_STATUSCOL);
in_status_line = (jump_flags & IN_STATUS_LINE);
in_sep_line = (jump_flags & IN_SEP_LINE);
if ((in_winbar || in_status_line || in_statuscol) && is_click) {
// Handle click event on window bar or status lin
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 (click_defs != NULL) {
switch (click_defs[click_col].type) {
case kStlClickDisabled:
break;
case kStlClickFuncRun:
call_click_def_func(click_defs, click_col, which_button);
break;
default:
assert(false && "winbar and statusline only support %@ for clicks");
break;
}
}
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, 1L);
} else {
closeFold(curwin->w_cursor, 1L);
}
// 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, 1L);
mouse_row = 0;
}
if (start_visual.lnum) { // right click in visual mode
long 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) {
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(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) {
int c2;
if (regname == 0 && eval_has_provider("clipboard")) {
regname = '*';
}
if (yank_register_mline(regname)) {
if (mouse_past_bottom) {
dir = FORWARD;
}
} else if (mouse_past_eol) {
dir = FORWARD;
}
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;
}
/// 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
win_T *wp, *old_curwin;
pos_T old_cursor;
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...
}
old_curwin = curwin;
old_cursor = curwin->w_cursor;
if (row < 0 || col < 0) { // check if it makes sense
return IN_UNKNOWN;
}
// find the window where the row is in
wp = mouse_find_win(&grid, &row, &col);
if (wp == NULL) {
return IN_UNKNOWN;
}
on_status_line = (grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height)
? row + wp->w_winbar_height - wp->w_height + 1 == 1
: false;
on_winbar = (row == -1)
? wp->w_winbar_height != 0
: false;
on_statuscol = !on_status_line && !on_winbar && col < win_col_off(wp)
? *wp->w_p_stc != NUL
: false;
on_sep_line = grid == DEFAULT_GRID_HANDLE && col >= wp->w_width
? col - wp->w_width + 1 == 1
: false;
// 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;
}
if (!keep_focus) {
if (on_winbar) {
return IN_OTHER_WIN | MOUSE_WINBAR;
}
if (on_statuscol) {
return IN_OTHER_WIN | MOUSE_STATUSCOL;
}
fdc = win_fdccol_count(wp);
dragwin = NULL;
// winpos and height may change in win_enter()!
if (grid == DEFAULT_GRID_HANDLE && row + wp->w_winbar_height >= wp->w_height) {
// 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 + (cmdwin_type == 0 && wp == curwin ? 0 : 1))
&& (flags & MOUSE_MAY_STOP_VIS)))) {
end_visual_mode();
redraw_curbuf_later(UPD_INVERTED); // delete the inversion
}
if (cmdwin_type != 0 && wp != curwin) {
// 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 = curwin;
}
// 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;
(void)hasFolding(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 (hasFolding(curwin->w_topline, NULL, &curwin->w_topline)
&& curwin->w_topline == curbuf->b_ml.ml_line_count) {
break;
}
if (curwin->w_topfill > 0) {
curwin->w_topfill--;
} else {
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);
}
}
}
// 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;
}
if (!(flags & MOUSE_RELEASED) && which_button == MOUSE_LEFT) {
col = mouse_adjust_click(curwin, row, col);
}
// 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
}
}
curwin->w_curswant = col;
curwin->w_set_curswant = false; // May still have been true
if (coladvance(col) == FAIL) { // Mouse click beyond end of line
if (inclusive != NULL) {
*inclusive = true;
}
mouse_past_eol = true;
} else if (inclusive != NULL) {
*inclusive = false;
}
count = 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_check_fold();
return count;
}
// 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)
&& !hasFoldingWin(win, lnum, NULL, NULL, true, NULL)) {
if (lnum == win->w_topline) {
row -= win->w_topfill;
} else {
row -= win_get_fill(win, lnum);
}
count = plines_win_nofill(win, lnum, true);
} else {
count = plines_win(win, lnum, true);
}
if (count > row) {
break; // Position is in this buffer line.
}
(void)hasFoldingWin(win, lnum, NULL, &lnum, true, NULL);
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);
if (col < off) {
col = off;
}
col += row * (win->w_width_inner - off);
// add skip column (for long wrapping line)
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);
if (col <= 0) {
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;
for (;;) {
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_float_config.focusable)) {
*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 one.
colnr_T vcol2col(win_T *const wp, const linenr_T lnum, const colnr_T vcol)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
// try to advance to the specified column
char *line = ml_get_buf(wp->w_buffer, lnum, false);
chartabsize_T cts;
init_chartabsize_arg(&cts, wp, lnum, 0, line, line);
while (cts.cts_vcol < vcol && *cts.cts_ptr != NUL) {
cts.cts_vcol += win_lbr_chartabsize(&cts, NULL);
MB_PTR_ADV(cts.cts_ptr);
}
clear_chartabsize_arg(&cts);
return (colnr_T)(cts.cts_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) {
for (;;) {
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) {
long 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 > (colnr_T)max) {
max = len;
ret = lnum;
} else if (len == (colnr_T)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;
}
/// Do a horizontal scroll.
/// @return true if the cursor moved, false otherwise.
bool mouse_scroll_horiz(int dir)
{
if (curwin->w_p_wrap) {
return false;
}
int step = (int)p_mousescroll_hor;
if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) {
step = curwin->w_width_inner;
}
int leftcol = curwin->w_leftcol + (dir == MSCR_RIGHT ? -step : +step);
if (leftcol < 0) {
leftcol = 0;
}
if (curwin->w_leftcol == leftcol) {
return false;
}
curwin->w_leftcol = (colnr_T)leftcol;
// When the line of the cursor is too short, move the cursor to the
// longest visible line.
if (!virtual_active()
&& (colnr_T)leftcol > scroll_line_len(curwin->w_cursor.lnum)) {
curwin->w_cursor.lnum = find_longest_lnum();
curwin->w_cursor.col = 0;
}
return leftcol_changed();
}
/// Adjusts the clicked column position when 'conceallevel' > 0
static int mouse_adjust_click(win_T *wp, int row, int col)
{
if (!(wp->w_p_cole > 0 && curbuf->b_p_smc > 0
&& wp->w_leftcol < curbuf->b_p_smc && conceal_cursor_line(wp))) {
return col;
}
// `col` is the position within the current line that is highlighted by the
// cursor without consideration for concealed characters. The current line is
// scanned *up to* `col`, nudging it left or right when concealed characters
// are encountered.
//
// win_chartabsize() is used to keep track of the virtual column position
// relative to the line's bytes. For example: if col == 9 and the line
// starts with a tab that's 8 columns wide, we would want the cursor to be
// highlighting the second byte, not the ninth.
linenr_T lnum = wp->w_cursor.lnum;
char *line = ml_get(lnum);
char *ptr = line;
char *ptr_end;
char *ptr_row_offset = line; // Where we begin adjusting `ptr_end`
// Find the offset where scanning should begin.
int offset = wp->w_leftcol;
if (row > 0) {
offset += row * (wp->w_width_inner - win_col_off(wp) - win_col_off2(wp) -
wp->w_leftcol + wp->w_skipcol);
}
int vcol;
if (offset) {
// Skip everything up to an offset since nvim takes care of displaying the
// correct portion of the line when horizontally scrolling.
// When 'wrap' is enabled, only the row (of the wrapped line) needs to be
// checked for concealed characters.
vcol = 0;
while (vcol < offset && *ptr != NUL) {
vcol += win_chartabsize(curwin, ptr, vcol);
ptr += utfc_ptr2len(ptr);
}
ptr_row_offset = ptr;
}
// Align `ptr_end` with `col`
vcol = offset;
ptr_end = ptr_row_offset;
while (vcol < col && *ptr_end != NUL) {
vcol += win_chartabsize(curwin, ptr_end, vcol);
ptr_end += utfc_ptr2len(ptr_end);
}
int prev_matchid;
int nudge = 0;
vcol = offset;
#define INCR() nudge++; ptr_end += utfc_ptr2len((char *)ptr_end)
#define DECR() nudge--; ptr_end -= utfc_ptr2len((char *)ptr_end)
while (ptr < ptr_end && *ptr != NUL) {
int cwidth = win_chartabsize(curwin, ptr, vcol);
vcol += cwidth;
if (cwidth > 1 && *ptr == '\t' && nudge > 0) {
// A tab will "absorb" any previous adjustments.
cwidth = MIN(cwidth, nudge);
while (cwidth > 0) {
DECR();
cwidth--;
}
}
int matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line));
if (matchid != 0) {
if (wp->w_p_cole == 3) {
INCR();
} else {
if (!(row > 0 && ptr == ptr_row_offset)
&& (wp->w_p_cole == 1 || (wp->w_p_cole == 2
&& (wp->w_p_lcs_chars.conceal != NUL
|| syn_get_sub_char() != NUL)))) {
// At least one placeholder character will be displayed.
DECR();
}
prev_matchid = matchid;
while (prev_matchid == matchid && *ptr != NUL) {
INCR();
ptr += utfc_ptr2len(ptr);
matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line));
}
continue;
}
}
ptr += utfc_ptr2len(ptr);
}
return col + nudge;
}
// Check clicked cell is foldcolumn
int mouse_check_fold(void)
{
int click_grid = mouse_grid;
int click_row = mouse_row;
int click_col = mouse_col;
int mouse_char = ' ';
int max_row = Rows;
int max_col = Columns;
int multigrid = ui_has(kUIMultigrid);
win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col);
if (wp && multigrid) {
max_row = wp->w_grid_alloc.rows;
max_col = wp->w_grid_alloc.cols;
}
if (wp && mouse_row >= 0 && mouse_row < max_row
&& mouse_col >= 0 && mouse_col < max_col) {
ScreenGrid *gp = multigrid ? &wp->w_grid_alloc : &default_grid;
int fdc = win_fdccol_count(wp);
int row = multigrid && mouse_grid == 0 ? click_row : mouse_row;
int col = multigrid && mouse_grid == 0 ? click_col : mouse_col;
// Remember the character under the mouse, might be one of foldclose or
// foldopen fillchars in the fold column.
if (gp->chars != NULL) {
mouse_char = utf_ptr2char((char *)gp->chars[gp->line_offset[row]
+ (unsigned)col]);
}
// Check for position outside of the fold column.
if (wp->w_p_rl ? click_col < wp->w_width_inner - fdc :
click_col >= fdc + (cmdwin_type == 0 ? 0 : 1)) {
mouse_char = ' ';
}
}
if (wp && mouse_char == wp->w_p_fcs_chars.foldclosed) {
return MOUSE_FOLD_OPEN;
} else if (mouse_char != ' ') {
return MOUSE_FOLD_CLOSE;
}
return 0;
}