diff options
Diffstat (limited to 'src/mouse.c')
-rw-r--r-- | src/mouse.c | 3275 |
1 files changed, 3275 insertions, 0 deletions
diff --git a/src/mouse.c b/src/mouse.c new file mode 100644 index 0000000..4e8dd4d --- /dev/null +++ b/src/mouse.c @@ -0,0 +1,3275 @@ +/* vi:set ts=8 sts=4 sw=4 noet: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * mouse.c: mouse handling functions + */ + +#include "vim.h" + +/* + * Horiziontal and vertical steps used when scrolling. + * When negative scroll by a whole page. + */ +static long mouse_hor_step = 6; +static long mouse_vert_step = 3; + + void +mouse_set_vert_scroll_step(long step) +{ + mouse_vert_step = step; +} + + void +mouse_set_hor_scroll_step(long step) +{ + mouse_hor_step = step; +} + +#ifdef CHECK_DOUBLE_CLICK +/* + * Return the duration from t1 to t2 in milliseconds. + */ + static long +time_diff_ms(struct timeval *t1, struct timeval *t2) +{ + // This handles wrapping of tv_usec correctly without any special case. + // Example of 2 pairs (tv_sec, tv_usec) with a duration of 5 ms: + // t1 = (1, 998000) t2 = (2, 3000) gives: + // (2 - 1) * 1000 + (3000 - 998000) / 1000 -> 5 ms. + return (t2->tv_sec - t1->tv_sec) * 1000 + + (t2->tv_usec - t1->tv_usec) / 1000; +} +#endif + +/* + * 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_u *p) +{ + int c; + + if (has_mbyte && MB_BYTE2LEN(p[0]) > 1) + return mb_get_class(p); + + c = *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((char_u *)"-+*/%<>&|^!=", 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_u *line; + int cclass; + int col; + + line = ml_get(pos->lnum); + cclass = get_mouse_class(line + pos->col); + + while (pos->col > 0) + { + col = pos->col - 1; + col -= (*mb_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_u *line; + int cclass; + int col; + + line = ml_get(pos->lnum); + if (*p_sel == 'e' && pos->col > 0) + { + --pos->col; + pos->col -= (*mb_head_off)(line, line + pos->col); + } + cclass = get_mouse_class(line + pos->col); + while (line[pos->col] != NUL) + { + col = pos->col + (*mb_ptr2len)(line + pos->col); + if (get_mouse_class(line + col) != cclass) + { + if (*p_sel == 'e') + pos->col = col; + break; + } + pos->col = col; + } +} + +#if defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_GTK) \ + || defined(FEAT_GUI_MSWIN) \ + || defined(FEAT_GUI_PHOTON) \ + || defined(FEAT_TERM_POPUP_MENU) +# define USE_POPUP_SETPOS +# define NEED_VCOL2COL + +/* + * 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) +{ + win_T *wp; + 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 + wp = mouse_find_win(&row, &col, FAIL_POPUP); + if (wp == NULL) + return IN_UNKNOWN; + // winpos and height may change in win_enter()! + if (row >= 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, NULL)) + return IN_STATUS_LINE; // past bottom + + mpos->col = vcol2col(wp, mpos->lnum, col); + + mpos->coladd = 0; + return IN_BUFFER; +} +#endif + +/* + * 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 + * + * Return TRUE if start_arrow() should be called for edit mode. + */ + int +do_mouse( + oparg_T *oap, // operator argument, can be NULL + int c, // K_LEFTMOUSE, etc + int dir, // Direction to 'put' if necessary + long count, + int fixindent) // PUT_FIXINDENT if fixing indent necessary +{ + static int do_always = FALSE; // ignore 'mouse' setting next time + static int got_click = FALSE; // got a click some time back + + int which_button; // MOUSE_LEFT, _MIDDLE or _RIGHT + int is_click = FALSE; // If FALSE it's a drag or release event + int is_drag = FALSE; // If TRUE it's a drag event + int jump_flags = 0; // flags for jump_to_mouse() + pos_T start_visual; + int moved; // Has cursor moved? + int in_status_line; // mouse in status line + static int in_tab_line = FALSE; // mouse clicked in tab line + int in_sep_line; // mouse in vertical separator line + int c1, c2; +#if defined(FEAT_FOLDING) + pos_T save_cursor; +#endif + win_T *old_curwin = curwin; + static pos_T orig_cursor; + colnr_T leftcol, rightcol; + pos_T end_visual; + int diff; + int old_active = VIsual_active; + int old_mode = VIsual_mode; + int regname; + +#if defined(FEAT_FOLDING) + save_cursor = curwin->w_cursor; +#endif + + // When GUI is active, always recognize mouse events, otherwise: + // - Ignore mouse event in normal mode if 'mouse' doesn't include 'n'. + // - Ignore mouse event in visual mode if 'mouse' doesn't include 'v'. + // - For command line and insert mode 'mouse' is checked before calling + // do_mouse(). + if (do_always) + do_always = FALSE; + else +#ifdef FEAT_GUI + if (!gui.in_use) +#endif + { + if (VIsual_active) + { + if (!mouse_has(MOUSE_VISUAL)) + return FALSE; + } + else if (State == MODE_NORMAL && !mouse_has(MOUSE_NORMAL)) + return FALSE; + } + + 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_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_row = save_mouse_row; + mouse_col = save_mouse_col; + } + } + break; + } + + if (c == K_MOUSEMOVE) + { + // Mouse moved without a button pressed. +#ifdef FEAT_BEVAL_TERM + ui_may_remove_balloon(); + if (p_bevalterm) + { + profile_setlimit(p_bdlay, &bevalexpr_due); + bevalexpr_due_set = TRUE; + } +#endif +#ifdef FEAT_PROP_POPUP + popup_handle_mouse_moved(); +#endif + return FALSE; + } + +#ifdef FEAT_MOUSESHAPE + // May have stopped dragging the status or separator line. The pointer is + // most likely still on the status or separator line. + if (!is_drag && drag_status_line) + { + drag_status_line = FALSE; + update_mouseshape(SHAPE_IDX_STATUS); + } + if (!is_drag && drag_sep_line) + { + drag_sep_line = FALSE; + update_mouseshape(SHAPE_IDX_VSEP); + } +#endif + + // 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((char_u *)"\"+p"); + } + else + { + stuffcharReadbuff('y'); + stuffcharReadbuff(K_MIDDLEMOUSE); + } + do_always = TRUE; // ignore 'mouse' setting next time + 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) || !mouse_has(MOUSE_NORMAL)) + { + if (regname == '.') + insert_reg(regname, TRUE); + else + { +#ifdef FEAT_CLIPBOARD + if (clip_star.available && regname == 0) + regname = '*'; +#endif + if ((State & REPLACE_FLAG) && !yank_register_mline(regname)) + insert_reg(regname, TRUE); + else + { + do_put(regname, NULL, BACKWARD, 1L, + fixindent | 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 (TabPageIdxs != NULL) // only when initialized + { + // Check for clicking in the tab page line. + if (mouse_row == 0 && firstwin->w_winrow > 0) + { + if (is_drag) + { + if (in_tab_line) + { + c1 = TabPageIdxs[mouse_col]; + tabpage_move(c1 <= 0 ? 9999 : c1 < tabpage_index(curtab) + ? c1 - 1 : c1); + } + return FALSE; + } + + // click in a tab selects that tab page + if (is_click && cmdwin_type == 0 && mouse_col < Columns) + { + in_tab_line = TRUE; + c1 = TabPageIdxs[mouse_col]; + if (c1 >= 0) + { + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) + { + // double click opens new page + end_visual_mode_keep_button(); + 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_keep_button(); + } + } + else + { + 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); + } + } + return TRUE; + } + else if (is_drag && in_tab_line) + { + c1 = TabPageIdxs[mouse_col]; + tabpage_move(c1 <= 0 ? 9999 : c1 - 1); + 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))) + { +#ifdef USE_POPUP_SETPOS +# ifdef FEAT_GUI + if (gui.in_use) + { +# if defined(FEAT_GUI_MOTIF) || defined(FEAT_GUI_GTK) \ + || defined(FEAT_GUI_PHOTON) + if (!is_click) + // Ignore right button release events, only shows the popup + // menu on the button down event. + return FALSE; +# endif +# if defined(FEAT_GUI_MSWIN) || defined(FEAT_GUI_HAIKU) + if (is_click || is_drag) + // Ignore right button down and drag mouse events. Windows + // only shows the popup menu on the button up event. + return FALSE; +# endif + } +# endif +# if defined(FEAT_GUI) && defined(FEAT_TERM_POPUP_MENU) + else +# endif +# if defined(FEAT_TERM_POPUP_MENU) + if (!is_click) + // Ignore right button release events, only shows the popup + // menu on the button down event. + return FALSE; +# endif + + 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_POS(curwin->w_cursor, VIsual) + && (LT_POS(m_pos, curwin->w_cursor) + || LT_POS(VIsual, m_pos))) + || (LT_POS(VIsual, curwin->w_cursor) + && (LT_POS(m_pos, VIsual) + || LT_POS(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); + update_curbuf(VIsual_active ? UPD_INVERTED : UPD_VALID); + setcursor(); + out_flush(); // Update before showing popup menu + } +# ifdef FEAT_MENU + show_popupmenu(); + got_click = FALSE; // ignore release events +# endif + return (jump_flags & CURSOR_MOVED) != 0; +#else + return FALSE; +#endif + } + 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 if (mouse_has(MOUSE_VISUAL)) + 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_POS(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; + if (mouse_has(MOUSE_VISUAL)) + 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 = MCHAR; + } + + // 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); + +#ifdef FEAT_MENU + // A click in the window toolbar has no side effects. + if (jump_flags & MOUSE_WINBAR) + return FALSE; +#endif + moved = (jump_flags & CURSOR_MOVED); + in_status_line = (jump_flags & IN_STATUS_LINE); + in_sep_line = (jump_flags & IN_SEP_LINE); + +#ifdef FEAT_NETBEANS_INTG + if (isNetbeansBuffer(curbuf) + && !(jump_flags & (IN_STATUS_LINE | IN_SEP_LINE))) + { + int key = KEY2TERMCAP1(c); + + if (key == (int)KE_LEFTRELEASE || key == (int)KE_MIDDLERELEASE + || key == (int)KE_RIGHTRELEASE) + netbeans_button_release(which_button); + } +#endif + + // 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); + +#ifdef FEAT_FOLDING + 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.lnum, 1L); + else + closeFold(curwin->w_cursor.lnum, 1L); + // don't move the cursor if still in the same window + if (curwin == old_curwin) + curwin->w_cursor = save_cursor; + } +#endif + +#if defined(FEAT_CLIPBOARD) + if ((jump_flags & IN_OTHER_WIN) && !VIsual_active && clip_star.available) + { + clip_modeless(which_button, is_click, is_drag); + return FALSE; + } +#endif + + // Set global flag that we are extending the Visual area with mouse + // dragging; temporarily minimize 'scrolloff'. + if (VIsual_active && is_drag && get_scrolloff_value()) + { + // 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 + { + // 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_POS(curwin->w_cursor, start_visual)) + VIsual = end_visual; + else if (LT_POS(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; + } + + // In different lines, compare line number + else + { + 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; + } + } + } + } + } + // If Visual mode started in insert mode, execute "CTRL-O" + else if ((State & MODE_INSERT) && VIsual_active) + stuffcharReadbuff(Ctrl_O); + + // Middle mouse click: Put text before cursor. + if (which_button == MOUSE_MIDDLE) + { +#ifdef FEAT_CLIPBOARD + if (clip_star.available && regname == 0) + regname = '*'; +#endif + 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_CURSEND); + } + +#if defined(FEAT_QUICKFIX) + // Ctrl-Mouse click or double click in a quickfix window jumps to the + // error under the mouse pointer. + else if (((mod_mask & MOD_MASK_CTRL) + || (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) + && bt_quickfix(curbuf)) + { + if (curwin->w_llist_ref == NULL) // quickfix window + do_cmdline_cmd((char_u *)".cc"); + else // location list window + do_cmdline_cmd((char_u *)".ll"); + got_click = FALSE; // ignore drag&release now + } +#endif + + // Ctrl-Mouse click (or double click in a help window) jumps to the tag + // under the mouse pointer. + else if ((mod_mask & MOD_MASK_CTRL) || (curbuf->b_help + && (mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK)) + { + if (State & MODE_INSERT) + stuffcharReadbuff(Ctrl_O); + stuffcharReadbuff(Ctrl_RSB); + got_click = FALSE; // ignore drag&release now + } + + // Shift-Mouse click searches for the next occurrence of the word under + // the mouse pointer + else if ((mod_mask & MOD_MASK_SHIFT)) + { + if ((State & MODE_INSERT) || (VIsual_active && VIsual_select)) + stuffcharReadbuff(Ctrl_O); + if (which_button == MOUSE_LEFT) + stuffcharReadbuff('*'); + else // MOUSE_RIGHT + stuffcharReadbuff('#'); + } + + // Handle double clicks, unless on status line + else if (in_status_line) + { +#ifdef FEAT_MOUSESHAPE + if ((is_drag || is_click) && !drag_status_line) + { + drag_status_line = TRUE; + update_mouseshape(-1); + } +#endif + } + else if (in_sep_line) + { +#ifdef FEAT_MOUSESHAPE + if ((is_drag || is_click) && !drag_sep_line) + { + drag_sep_line = TRUE; + update_mouseshape(-1); + } +#endif + } + else if ((mod_mask & MOD_MASK_MULTI_CLICK) + && (State & (MODE_NORMAL | MODE_INSERT)) + && mouse_has(MOUSE_VISUAL)) + { + if (is_click || !VIsual_active) + { + if (VIsual_active) + orig_cursor = VIsual; + else + { + check_visual_highlight(); + 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; +#ifdef FEAT_CLIPBOARD + // Make sure the clipboard gets updated. Needed because start and + // end may still be the same, and the selection needs to be owned + clip_star.vmode = NUL; +#endif + } + // A double click selects a word or a block. + if ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK) + { + pos_T *pos = NULL; + int gc; + + 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; + while (gc = gchar_pos(&end_visual), VIM_ISWHITE(gc)) + inc(&end_visual); + if (oap != NULL) + oap->motion_type = MCHAR; + if (oap != NULL + && VIsual_mode == 'v' + && !vim_iswordc(gchar_pos(&end_visual)) + && EQUAL_POS(curwin->w_cursor, VIsual) + && (pos = findmatch(oap, NUL)) != NULL) + { + curwin->w_cursor = *pos; + if (oap->motion_type == MLINE) + VIsual_mode = 'V'; + else if (*p_sel == 'e') + { + if (LT_POS(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_POS(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' && *ml_get_cursor() != NUL) + curwin->w_cursor.col += + (*mb_ptr2len)(ml_get_cursor()); + 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) +{ + pos_T tpos; + win_T *old_curwin = curwin; + +#ifdef FEAT_GUI + // When GUI is active, also move/paste when 'mouse' is empty + if (!gui.in_use) +#endif + if (!mouse_has(MOUSE_INSERT)) + return; + + undisplay_dollar(); + tpos = curwin->w_cursor; + if (do_mouse(NULL, c, BACKWARD, 1L, 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; +#ifdef FEAT_JOB_CHANNEL + if (bt_prompt(curbuf)) + // Restart Insert mode when re-entering the prompt buffer. + curbuf->b_prompt_insert = 'A'; +#endif + } + 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. + */ + static void +do_mousescroll(cmdarg_T *cap) +{ + int shift_or_ctrl = mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL); + +#ifdef FEAT_TERMINAL + if (term_use_loop()) + // This window is a terminal window, send the mouse event there. + // Set "typed" to FALSE to avoid an endless loop. + send_keys_to_term(curbuf->b_term, cap->cmdchar, mod_mask, FALSE); + else +#endif + if (cap->arg == MSCR_UP || cap->arg == MSCR_DOWN) + { + // Vertical scrolling + if (!(State & MODE_INSERT) && (mouse_vert_step < 0 || shift_or_ctrl)) + { + // whole page up or down + onepage(cap->arg == MSCR_UP ? FORWARD : BACKWARD, 1L); + } + else + { + if (mouse_vert_step < 0 || shift_or_ctrl) + { + // whole page up or down + cap->count1 = (long)(curwin->w_botline - curwin->w_topline); + } + // Don't scroll more than half the window height. + else if (curwin->w_height < mouse_vert_step * 2) + { + cap->count1 = curwin->w_height / 2; + if (cap->count1 == 0) + cap->count1 = 1; + } + else + { + cap->count1 = mouse_vert_step; + } + cap->count0 = cap->count1; + nv_scroll_line(cap); + } + +#ifdef FEAT_PROP_POPUP + if (WIN_IS_POPUP(curwin)) + popup_set_firstline(curwin); +#endif + } + else + { + // Horizontal scrolling + long step = (mouse_hor_step < 0 || shift_or_ctrl) + ? curwin->w_width : mouse_hor_step; + long leftcol = curwin->w_leftcol + + (cap->arg == MSCR_RIGHT ? -step : step); + if (leftcol < 0) + leftcol = 0; + do_mousescroll_horiz((long_u)leftcol); + } + may_trigger_win_scrolled_resized(); +} + +/* + * Insert mode implementation for scrolling 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 row = mouse_row; + int col = mouse_col; + curwin = mouse_find_win(&row, &col, FIND_POPUP); + 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(); + } + + linenr_T orig_topline = curwin->w_topline; + colnr_T orig_leftcol = curwin->w_leftcol; + pos_T orig_cursor = curwin->w_cursor; + + // Call the common mouse scroll function shared with other modes. + do_mousescroll(&cap); + + int did_scroll = (orig_topline != curwin->w_topline + || orig_leftcol != curwin->w_leftcol); + + curwin->w_redr_status = TRUE; + curwin = old_curwin; + curbuf = curwin->w_buffer; + + // If the window actually scrolled and the popup menu may overlay the + // window, need to redraw it. + if (did_scroll && pum_visible()) + { + // TODO: Would be more efficient to only redraw the windows that are + // overlapped by the popup menu. + redraw_all_later(UPD_NOT_VALID); + ins_compl_show_pum(); + } + + if (!EQUAL_POS(curwin->w_cursor, orig_cursor)) + { + start_arrow(&orig_cursor); + set_can_cindent(TRUE); + } +} + +/* + * Return TRUE if "c" is a mouse key. + */ + int +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; +} + +static struct mousetable +{ + int pseudo_code; // Code for pseudo mouse event + int button; // Which mouse button is it? + int is_click; // Is it a mouse button click event? + int is_drag; // Is it a mouse drag event? +} mouse_table[] = +{ + {(int)KE_LEFTMOUSE, MOUSE_LEFT, TRUE, FALSE}, +#ifdef FEAT_GUI + {(int)KE_LEFTMOUSE_NM, MOUSE_LEFT, TRUE, FALSE}, +#endif + {(int)KE_LEFTDRAG, MOUSE_LEFT, FALSE, TRUE}, + {(int)KE_LEFTRELEASE, MOUSE_LEFT, FALSE, FALSE}, +#ifdef FEAT_GUI + {(int)KE_LEFTRELEASE_NM, MOUSE_LEFT, FALSE, FALSE}, +#endif + {(int)KE_MIDDLEMOUSE, MOUSE_MIDDLE, TRUE, FALSE}, + {(int)KE_MIDDLEDRAG, MOUSE_MIDDLE, FALSE, TRUE}, + {(int)KE_MIDDLERELEASE, MOUSE_MIDDLE, FALSE, FALSE}, + {(int)KE_RIGHTMOUSE, MOUSE_RIGHT, TRUE, FALSE}, + {(int)KE_RIGHTDRAG, MOUSE_RIGHT, FALSE, TRUE}, + {(int)KE_RIGHTRELEASE, MOUSE_RIGHT, FALSE, FALSE}, + {(int)KE_X1MOUSE, MOUSE_X1, TRUE, FALSE}, + {(int)KE_X1DRAG, MOUSE_X1, FALSE, TRUE}, + {(int)KE_X1RELEASE, MOUSE_X1, FALSE, FALSE}, + {(int)KE_X2MOUSE, MOUSE_X2, TRUE, FALSE}, + {(int)KE_X2DRAG, MOUSE_X2, FALSE, TRUE}, + {(int)KE_X2RELEASE, MOUSE_X2, FALSE, FALSE}, + // DRAG without CLICK + {(int)KE_MOUSEMOVE, MOUSE_RELEASE, FALSE, TRUE}, + // RELEASE without CLICK + {(int)KE_IGNORE, MOUSE_RELEASE, FALSE, FALSE}, + {0, 0, 0, 0}, +}; + +/* + * Look up the given mouse code to return the relevant information in the other + * arguments. Return which button is down or was released. + */ + int +get_mouse_button(int code, int *is_click, int *is_drag) +{ + int i; + + for (i = 0; mouse_table[i].pseudo_code; i++) + if (code == mouse_table[i].pseudo_code) + { + *is_click = mouse_table[i].is_click; + *is_drag = mouse_table[i].is_drag; + return mouse_table[i].button; + } + return 0; // Shouldn't get here +} + +/* + * Return the appropriate pseudo mouse event token (KE_LEFTMOUSE etc) based on + * the given information about which mouse button is down, and whether the + * mouse was clicked, dragged or released. + */ + int +get_pseudo_mouse_code( + int button, // eg MOUSE_LEFT + int is_click, + int is_drag) +{ + int i; + + for (i = 0; mouse_table[i].pseudo_code; i++) + if (button == mouse_table[i].button + && is_click == mouse_table[i].is_click + && is_drag == mouse_table[i].is_drag) + { +#ifdef FEAT_GUI + // Trick: a non mappable left click and release has mouse_col -1 + // or added MOUSE_COLOFF. Used for 'mousefocus' in + // gui_mouse_moved() + if (mouse_col < 0 || mouse_col > MOUSE_COLOFF) + { + if (mouse_col < 0) + mouse_col = 0; + else + mouse_col -= MOUSE_COLOFF; + if (mouse_table[i].pseudo_code == (int)KE_LEFTMOUSE) + return (int)KE_LEFTMOUSE_NM; + if (mouse_table[i].pseudo_code == (int)KE_LEFTRELEASE) + return (int)KE_LEFTRELEASE_NM; + } +#endif + return mouse_table[i].pseudo_code; + } + return (int)KE_IGNORE; // not recognized, ignore it +} + +# define HMT_NORMAL 1 +# define HMT_NETTERM 2 +# define HMT_DEC 4 +# define HMT_JSBTERM 8 +# define HMT_PTERM 16 +# define HMT_URXVT 32 +# define HMT_GPM 64 +# define HMT_SGR 128 +# define HMT_SGR_REL 256 +static int has_mouse_termcode = 0; + + void +set_mouse_termcode( + int n, // KS_MOUSE, KS_NETTERM_MOUSE or KS_DEC_MOUSE + char_u *s) +{ + char_u name[2]; + + name[0] = n; + name[1] = KE_FILLER; + add_termcode(name, s, FALSE); +#ifdef FEAT_MOUSE_JSB + if (n == KS_JSBTERM_MOUSE) + has_mouse_termcode |= HMT_JSBTERM; + else +#endif +#ifdef FEAT_MOUSE_NET + if (n == KS_NETTERM_MOUSE) + has_mouse_termcode |= HMT_NETTERM; + else +#endif +#ifdef FEAT_MOUSE_DEC + if (n == KS_DEC_MOUSE) + has_mouse_termcode |= HMT_DEC; + else +#endif +#ifdef FEAT_MOUSE_PTERM + if (n == KS_PTERM_MOUSE) + has_mouse_termcode |= HMT_PTERM; + else +#endif +#ifdef FEAT_MOUSE_URXVT + if (n == KS_URXVT_MOUSE) + has_mouse_termcode |= HMT_URXVT; + else +#endif +#ifdef FEAT_MOUSE_GPM + if (n == KS_GPM_MOUSE) + has_mouse_termcode |= HMT_GPM; + else +#endif + if (n == KS_SGR_MOUSE) + has_mouse_termcode |= HMT_SGR; + else if (n == KS_SGR_MOUSE_RELEASE) + has_mouse_termcode |= HMT_SGR_REL; + else + has_mouse_termcode |= HMT_NORMAL; +} + +#if defined(UNIX) || defined(VMS) || defined(PROTO) + void +del_mouse_termcode( + int n) // KS_MOUSE, KS_NETTERM_MOUSE or KS_DEC_MOUSE +{ + char_u name[2]; + + name[0] = n; + name[1] = KE_FILLER; + del_termcode(name); +# ifdef FEAT_MOUSE_JSB + if (n == KS_JSBTERM_MOUSE) + has_mouse_termcode &= ~HMT_JSBTERM; + else +# endif +# ifdef FEAT_MOUSE_NET + if (n == KS_NETTERM_MOUSE) + has_mouse_termcode &= ~HMT_NETTERM; + else +# endif +# ifdef FEAT_MOUSE_DEC + if (n == KS_DEC_MOUSE) + has_mouse_termcode &= ~HMT_DEC; + else +# endif +# ifdef FEAT_MOUSE_PTERM + if (n == KS_PTERM_MOUSE) + has_mouse_termcode &= ~HMT_PTERM; + else +# endif +# ifdef FEAT_MOUSE_URXVT + if (n == KS_URXVT_MOUSE) + has_mouse_termcode &= ~HMT_URXVT; + else +# endif +# ifdef FEAT_MOUSE_GPM + if (n == KS_GPM_MOUSE) + has_mouse_termcode &= ~HMT_GPM; + else +# endif + if (n == KS_SGR_MOUSE) + has_mouse_termcode &= ~HMT_SGR; + else if (n == KS_SGR_MOUSE_RELEASE) + has_mouse_termcode &= ~HMT_SGR_REL; + else + has_mouse_termcode &= ~HMT_NORMAL; +} +#endif + +/* + * setmouse() - switch mouse on/off depending on current mode and 'mouse' + */ + void +setmouse(void) +{ + int checkfor; + +#ifdef FEAT_MOUSESHAPE + update_mouseshape(-1); +#endif + + // Should be outside proc, but may break MOUSESHAPE +#ifdef FEAT_GUI + // In the GUI the mouse is always enabled. + if (gui.in_use) + return; +#endif + // be quick when mouse is off + if (*p_mouse == NUL || has_mouse_termcode == 0) + return; + + // don't switch mouse on when not in raw mode (Ex mode) + if (cur_tmode != TMODE_RAW) + { + mch_setmouse(FALSE); + return; + } + + if (VIsual_active) + checkfor = MOUSE_VISUAL; + else if (State == MODE_HITRETURN || State == MODE_ASKMORE + || State == MODE_SETWSIZE) + checkfor = MOUSE_RETURN; + else if (State & MODE_INSERT) + checkfor = MOUSE_INSERT; + else if (State & MODE_CMDLINE) + checkfor = MOUSE_COMMAND; + else if (State == MODE_CONFIRM || State == MODE_EXTERNCMD) + checkfor = ' '; // don't use mouse for ":confirm" or ":!cmd" + else + checkfor = MOUSE_NORMAL; // assume normal mode + + if (mouse_has(checkfor)) + mch_setmouse(TRUE); + else + mch_setmouse(FALSE); +} + +/* + * Return TRUE if + * - "c" is in 'mouse', or + * - 'a' is in 'mouse' and "c" is in MOUSE_A, or + * - the current buffer is a help file and 'h' is in 'mouse' and we are in a + * normal editing mode (not at hit-return message). + */ + int +mouse_has(int c) +{ + char_u *p; + + for (p = p_mouse; *p; ++p) + switch (*p) + { + case 'a': if (vim_strchr((char_u *)MOUSE_A, c) != NULL) + return TRUE; + break; + case MOUSE_HELP: if (c != MOUSE_RETURN && curbuf->b_help) + return TRUE; + break; + default: if (c == *p) return TRUE; break; + } + return FALSE; +} + +/* + * Return TRUE when 'mousemodel' is set to "popup" or "popup_setpos". + */ + int +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. + * 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 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. + */ + int +jump_to_mouse( + int flags, + int *inclusive, // used for inclusive operator, can be NULL + int which_button) // MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE +{ + static int on_status_line = 0; // #lines below bottom of window + static int on_sep_line = 0; // on separator right of window +#ifdef FEAT_MENU + static int in_winbar = FALSE; +#endif +#ifdef FEAT_PROP_POPUP + static int in_popup_win = FALSE; + static win_T *click_in_popup_win = NULL; +#endif + 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; + int first; + int row = mouse_row; + int col = mouse_col; + colnr_T col_from_screen = -1; +#ifdef FEAT_FOLDING + int mouse_char = ' '; +#endif + + 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; +#ifdef FEAT_PROP_POPUP + if (click_in_popup_win != NULL && popup_dragwin == NULL) + popup_close_for_mouse_click(click_in_popup_win); + + popup_dragwin = NULL; + click_in_popup_win = NULL; +#endif + } + + 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 (on_status_line) + return IN_STATUS_LINE; + if (on_sep_line) + return IN_SEP_LINE; +#ifdef FEAT_MENU + if (in_winbar) + { + // A quick second click may arrive as a double-click, but we use it + // as a second click in the WinBar. + if ((mod_mask & MOD_MASK_MULTI_CLICK) && !(flags & MOUSE_RELEASED)) + { + wp = mouse_find_win(&row, &col, FAIL_POPUP); + if (wp == NULL) + return IN_UNKNOWN; + winbar_click(wp, col); + } + return IN_OTHER_WIN | MOUSE_WINBAR; + } +#endif + if (flags & MOUSE_MAY_STOP_VIS) + { + end_visual_mode_keep_button(); + redraw_curbuf_later(UPD_INVERTED); // delete the inversion + } +#if defined(FEAT_CLIPBOARD) + // Continue a modeless selection in another window. + if (cmdwin_type != 0 && row < curwin->w_winrow) + return IN_OTHER_WIN; +#endif +#ifdef FEAT_PROP_POPUP + // Continue a modeless selection in a popup window or dragging it. + if (in_popup_win) + { + click_in_popup_win = NULL; // don't close it on release + if (popup_dragwin != NULL) + { + // dragging a popup window + popup_drag(popup_dragwin); + return IN_UNKNOWN; + } + return IN_OTHER_WIN; + } +#endif + 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 (!(flags & MOUSE_FOCUS)) + { + if (row < 0 || col < 0) // check if it makes sense + return IN_UNKNOWN; + + // find the window where the row is in and adjust "row" and "col" to be + // relative to top-left of the window + wp = mouse_find_win(&row, &col, FIND_POPUP); + if (wp == NULL) + return IN_UNKNOWN; + dragwin = NULL; + +#ifdef FEAT_PROP_POPUP + // Click in a popup window may start dragging or modeless selection, + // but not much else. + if (WIN_IS_POPUP(wp)) + { + on_sep_line = 0; + on_status_line = 0; + in_popup_win = TRUE; + if (which_button == MOUSE_LEFT && popup_close_if_on_X(wp, row, col)) + { + return IN_UNKNOWN; + } + else if (((wp->w_popup_flags & (POPF_DRAG | POPF_RESIZE)) + && popup_on_border(wp, row, col)) + || (wp->w_popup_flags & POPF_DRAGALL)) + { + popup_dragwin = wp; + popup_start_drag(wp, row, col); + return IN_UNKNOWN; + } + // Only close on release, otherwise it's not possible to drag or do + // modeless selection. + else if (wp->w_popup_close == POPCLOSE_CLICK + && which_button == MOUSE_LEFT) + { + click_in_popup_win = wp; + } + else if (which_button == MOUSE_LEFT) + // If the click is in the scrollbar, may scroll up/down. + popup_handle_scrollbar_click(wp, row, col); +# ifdef FEAT_CLIPBOARD + return IN_OTHER_WIN; +# else + return IN_UNKNOWN; +# endif + } + in_popup_win = FALSE; + popup_dragwin = NULL; +#endif +#ifdef FEAT_MENU + if (row == -1) + { + // A click in the window toolbar does not enter another window or + // change Visual highlighting. + winbar_click(wp, col); + in_winbar = TRUE; + return IN_OTHER_WIN | MOUSE_WINBAR; + } + in_winbar = FALSE; +#endif + + // winpos and height may change in win_enter()! + if (row >= wp->w_height) // In (or below) status line + { + on_status_line = row - wp->w_height + 1; + dragwin = wp; + } + else + on_status_line = 0; + if (col >= wp->w_width) // In separator line + { + on_sep_line = col - wp->w_width + 1; + dragwin = wp; + } + else + on_sep_line = 0; + + // 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 = 0; + else + on_status_line = 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 + || (!on_status_line && !on_sep_line +#ifdef FEAT_FOLDING + && ( +# ifdef FEAT_RIGHTLEFT + wp->w_p_rl ? col < wp->w_width - wp->w_p_fdc : +# endif + col >= wp->w_p_fdc + (cmdwin_type == 0 && wp == curwin ? 0 : 1) + ) +#endif + && (flags & MOUSE_MAY_STOP_VIS)))) + { + end_visual_mode_keep_button(); + 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. + on_sep_line = 0; +#ifdef FEAT_CLIPBOARD + if (on_status_line) + return IN_STATUS_LINE; + return IN_OTHER_WIN; +#else + row = 0; + col += wp->w_wincol; + wp = curwin; +#endif + } +#if defined(FEAT_PROP_POPUP) && defined(FEAT_TERMINAL) + if (popup_is_popup(curwin) && curbuf->b_term != NULL) + // terminal in popup window: don't jump to another window + return IN_OTHER_WIN; +#endif + // 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! + + if (curwin != old_curwin) + { +#ifdef CHECK_DOUBLE_CLICK + // set topline, to be able to check for double click ourselves + set_mouse_topline(curwin); +#endif +#ifdef FEAT_TERMINAL + // when entering a terminal window may change state + term_win_entered(); +#endif + } + if (on_status_line) // 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; + else + return IN_STATUS_LINE | CURSOR_MOVED; + } + if (on_sep_line) // 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; + else + return IN_SEP_LINE | CURSOR_MOVED; + } + + curwin->w_cursor.lnum = curwin->w_topline; +#ifdef FEAT_GUI + // remember topline, needed for double click + gui_prev_topline = curwin->w_topline; +# ifdef FEAT_DIFF + gui_prev_topfill = curwin->w_topfill; +# endif +#endif + } + else if (on_status_line && which_button == MOUSE_LEFT) + { + if (dragwin != NULL) + { + // Drag the status line + count = row - W_WINROW(dragwin) - dragwin->w_height + 1 + - on_status_line; + win_drag_status_line(dragwin, count); + did_drag |= count; + } + return IN_STATUS_LINE; // Cursor didn't move + } + else if (on_sep_line && which_button == MOUSE_LEFT) + { + if (dragwin != NULL) + { + // Drag the separator column + count = col - dragwin->w_wincol - dragwin->w_width + 1 + - on_sep_line; + win_drag_vsep_line(dragwin, count); + did_drag |= count; + } + return IN_SEP_LINE; // Cursor didn't move + } +#ifdef FEAT_MENU + else if (in_winbar) + { + // After a click on the window toolbar don't start Visual mode. + return IN_OTHER_WIN | MOUSE_WINBAR; + } +#endif + 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_keep_button(); + redraw_curbuf_later(UPD_INVERTED); // delete the inversion + } + +#if defined(FEAT_CLIPBOARD) + // Continue a modeless selection in another window. + if (cmdwin_type != 0 && row < curwin->w_winrow) + return IN_OTHER_WIN; +#endif +#ifdef FEAT_PROP_POPUP + if (in_popup_win) + { + if (popup_dragwin != NULL) + { + // dragging a popup window + popup_drag(popup_dragwin); + return IN_UNKNOWN; + } + // continue a modeless selection in a popup window + click_in_popup_win = NULL; + return IN_OTHER_WIN; + } +#endif + + row -= W_WINROW(curwin); + col -= curwin->w_wincol; + + // 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; ) + { +#ifdef FEAT_DIFF + if (curwin->w_topfill < diff_check(curwin, curwin->w_topline)) + ++count; + else +#endif + count += plines(curwin->w_topline - 1); + if (!first && count > -row) + break; + first = FALSE; +#ifdef FEAT_FOLDING + (void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL); +#endif +#ifdef FEAT_DIFF + if (curwin->w_topfill < diff_check(curwin, curwin->w_topline)) + ++curwin->w_topfill; + else +#endif + { + --curwin->w_topline; +#ifdef FEAT_DIFF + curwin->w_topfill = 0; +#endif + } + } +#ifdef FEAT_DIFF + check_topfill(curwin, FALSE); +#endif + curwin->w_valid &= + ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); + redraw_later(UPD_VALID); + row = 0; + } + else if (row >= curwin->w_height) + { + count = 0; + for (first = TRUE; curwin->w_topline < curbuf->b_ml.ml_line_count; ) + { +#ifdef FEAT_DIFF + if (curwin->w_topfill > 0) + ++count; + else +#endif + count += plines(curwin->w_topline); + if (!first && count > row - curwin->w_height + 1) + break; + first = FALSE; +#ifdef FEAT_FOLDING + if (hasFolding(curwin->w_topline, NULL, &curwin->w_topline) + && curwin->w_topline == curbuf->b_ml.ml_line_count) + break; +#endif +#ifdef FEAT_DIFF + if (curwin->w_topfill > 0) + --curwin->w_topfill; + else +#endif + { + ++curwin->w_topline; +#ifdef FEAT_DIFF + curwin->w_topfill = + diff_check_fill(curwin, curwin->w_topline); +#endif + } + } +#ifdef FEAT_DIFF + check_topfill(curwin, FALSE); +#endif + redraw_later(UPD_VALID); + curwin->w_valid &= + ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP); + row = curwin->w_height - 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); + } + } + + if (prev_row >= 0 && prev_row < Rows && prev_col >= 0 && prev_col <= Columns + && ScreenLines != NULL) + { + int off = LineOffset[prev_row] + prev_col; + + // Only use ScreenCols[] after the window was redrawn. Mainly matters + // for tests, a user would not click before redrawing. + // Do not use when 'virtualedit' is active. + if (curwin->w_redr_type <= UPD_VALID_NO_UPDATE && !virtual_active()) + col_from_screen = ScreenCols[off]; +#ifdef FEAT_FOLDING + // Remember the character under the mouse, it might be a '-' or '+' in + // the fold column. + mouse_char = ScreenLines[off]; +#endif + } + +#ifdef FEAT_FOLDING + // Check for position outside of the fold column. + if ( +# ifdef FEAT_RIGHTLEFT + curwin->w_p_rl ? col < curwin->w_width - curwin->w_p_fdc : +# endif + col >= curwin->w_p_fdc + (cmdwin_type == 0 ? 0 : 1) + ) + mouse_char = ' '; +#endif + + // compute the position in the buffer line from the posn on the screen + if (mouse_comp_pos(curwin, &row, &col, &curwin->w_cursor.lnum, NULL)) + mouse_past_bottom = TRUE; + + // Start Visual mode before coladvance(), for when 'sel' != "old" + if ((flags & MOUSE_MAY_VIS) && !VIsual_active) + { + check_visual_highlight(); + 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) + { + // Use the column from ScreenCols[], it is accurate also after + // concealed characters. + curwin->w_cursor.col = col_from_screen; + if (col_from_screen == MAXCOL) + { + curwin->w_curswant = col_from_screen; + curwin->w_set_curswant = FALSE; // May still have been TRUE + mouse_past_eol = TRUE; + if (inclusive != NULL) + *inclusive = TRUE; + } + else + { + curwin->w_set_curswant = TRUE; + if (inclusive != NULL) + *inclusive = FALSE; + } + check_cursor_col(); + } + else + { + 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 + +#ifdef FEAT_FOLDING + if (mouse_char == curwin->w_fill_chars.foldclosed) + count |= MOUSE_FOLD_OPEN; + else if (mouse_char != ' ') + count |= MOUSE_FOLD_CLOSE; +#endif + + return count; +} + +/* + * Make a horizontal scroll to "leftcol". + * Return TRUE if the cursor moved, FALSE otherwise. + */ + int +do_mousescroll_horiz(long_u leftcol) +{ + if (curwin->w_p_wrap) + return FALSE; // no wrapping, no scrolling + + if (curwin->w_leftcol == (colnr_T)leftcol) + return FALSE; // already there + + // When the line of the cursor is too short, move the cursor to the + // longest visible line. + if ( +#ifdef FEAT_GUI + (!gui.in_use || vim_strchr(p_go, GO_HORSCROLL) == NULL) && +#endif + !virtual_active() + && (long)leftcol > scroll_line_len(curwin->w_cursor.lnum)) + { + curwin->w_cursor.lnum = ui_find_longest_lnum(); + curwin->w_cursor.col = 0; + } + + return set_leftcol((colnr_T)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 *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 row = mouse_row; + int col = mouse_col; + curwin = mouse_find_win(&row, &col, FIND_POPUP); + if (curwin == NULL) + { + curwin = old_curwin; + return; + } + +#ifdef FEAT_PROP_POPUP + if (WIN_IS_POPUP(curwin) && !curwin->w_has_scrollbar) + { + // cannot scroll this popup window + curwin = old_curwin; + return; + } +#endif + curbuf = curwin->w_buffer; + } + + // Call the common mouse scroll function shared with other modes. + do_mousescroll(cap); + +#ifdef FEAT_SYN_HL + if (curwin != old_curwin && curwin->w_p_cul) + redraw_for_cursorline(curwin); +#endif + curwin->w_redr_status = TRUE; + curwin = old_curwin; + curbuf = curwin->w_buffer; +} + +/* + * Mouse clicks and drags. + */ + void +nv_mouse(cmdarg_T *cap) +{ + (void)do_mouse(cap->oap, cap->cmdchar, BACKWARD, cap->count1, 0); +} + +static int held_button = MOUSE_RELEASE; + + void +reset_held_button(void) +{ + held_button = MOUSE_RELEASE; +} + +/* + * Check if typebuf 'tp' contains a terminal mouse code and returns the + * modifiers found in typebuf in 'modifiers'. + */ + int +check_termcode_mouse( + char_u *tp, + int *slen, + char_u *key_name, + char_u *modifiers_start, + int idx, + int *modifiers) +{ + int j; + char_u *p; +#if !defined(UNIX) || defined(FEAT_MOUSE_XTERM) || defined(FEAT_GUI) \ + || defined(FEAT_MOUSE_GPM) || defined(FEAT_SYSMOUSE) + char_u bytes[6]; + int num_bytes; +#endif + int mouse_code = 0; // init for GCC + int is_click, is_drag; + int is_release, release_is_ambiguous; + int wheel_code = 0; + int current_button; + static int orig_num_clicks = 1; + static int orig_mouse_code = 0x0; +#ifdef CHECK_DOUBLE_CLICK + static int orig_mouse_col = 0; + static int orig_mouse_row = 0; + static struct timeval orig_mouse_time = {0, 0}; + // time of previous mouse click + struct timeval mouse_time; // time of current mouse click + long timediff; // elapsed time in msec +#endif + + is_click = is_drag = is_release = release_is_ambiguous = FALSE; + +#if !defined(UNIX) || defined(FEAT_MOUSE_XTERM) || defined(FEAT_GUI) \ + || defined(FEAT_MOUSE_GPM) || defined(FEAT_SYSMOUSE) + if (key_name[0] == KS_MOUSE +# ifdef FEAT_MOUSE_GPM + || key_name[0] == KS_GPM_MOUSE +# endif + ) + { + /* + * For xterm we get "<t_mouse>scr", where s == encoded button state: + * 0x20 = left button down + * 0x21 = middle button down + * 0x22 = right button down + * 0x23 = any button release + * 0x60 = button 4 down (scroll wheel down) + * 0x61 = button 5 down (scroll wheel up) + * add 0x04 for SHIFT + * add 0x08 for ALT + * add 0x10 for CTRL + * add 0x20 for mouse drag (0x40 is drag with left button) + * add 0x40 for mouse move (0x80 is move, 0x81 too) + * 0x43 (drag + release) is also move + * c == column + ' ' + 1 == column + 33 + * r == row + ' ' + 1 == row + 33 + * + * The coordinates are passed on through global variables. Ugly, but + * this avoids trouble with mouse clicks at an unexpected moment and + * allows for mapping them. + */ + for (;;) + { + // For the GUI and for MS-Windows two bytes each are used for row + // and column. Allows for more than 223 columns. +# if defined(FEAT_GUI) || defined(MSWIN) + if (TRUE +# if defined(FEAT_GUI) && !defined(MSWIN) + && gui.in_use +# endif + ) + { + num_bytes = get_bytes_from_buf(tp + *slen, bytes, 5); + if (num_bytes == -1) // not enough coordinates + return -1; + mouse_code = bytes[0]; + mouse_col = 128 * (bytes[1] - ' ' - 1) + + bytes[2] - ' ' - 1; + mouse_row = 128 * (bytes[3] - ' ' - 1) + + bytes[4] - ' ' - 1; + } + else +# endif + { + num_bytes = get_bytes_from_buf(tp + *slen, bytes, 3); + if (num_bytes == -1) // not enough coordinates + return -1; + mouse_code = bytes[0]; + mouse_col = bytes[1] - ' ' - 1; + mouse_row = bytes[2] - ' ' - 1; + } + *slen += num_bytes; + + // If the following bytes is also a mouse code and it has the same + // code, dump this one and get the next. This makes dragging a + // whole lot faster. +# ifdef FEAT_GUI + if (gui.in_use) + j = 3; + else +# endif + j = get_termcode_len(idx); + if (STRNCMP(tp, tp + *slen, (size_t)j) == 0 + && tp[*slen + j] == mouse_code + && tp[*slen + j + 1] != NUL + && tp[*slen + j + 2] != NUL +# ifdef FEAT_GUI + && (!gui.in_use + || (tp[*slen + j + 3] != NUL + && tp[*slen + j + 4] != NUL)) +# endif + ) + *slen += j; + else + break; + } + } + + if (key_name[0] == KS_URXVT_MOUSE + || key_name[0] == KS_SGR_MOUSE + || key_name[0] == KS_SGR_MOUSE_RELEASE) + { + // URXVT 1015 mouse reporting mode: + // Almost identical to xterm mouse mode, except the values are decimal + // instead of bytes. + // + // \033[%d;%d;%dM + // ^-- row + // ^----- column + // ^-------- code + // + // SGR 1006 mouse reporting mode: + // Almost identical to xterm mouse mode, except the values are decimal + // instead of bytes. + // + // \033[<%d;%d;%dM + // ^-- row + // ^----- column + // ^-------- code + // + // \033[<%d;%d;%dm : mouse release event + // ^-- row + // ^----- column + // ^-------- code + p = modifiers_start; + if (p == NULL) + return -1; + + mouse_code = getdigits(&p); + if (*p++ != ';') + return -1; + + // when mouse reporting is SGR, add 32 to mouse code + if (key_name[0] == KS_SGR_MOUSE + || key_name[0] == KS_SGR_MOUSE_RELEASE) + mouse_code += 32; + + mouse_col = getdigits(&p) - 1; + if (*p++ != ';') + return -1; + + mouse_row = getdigits(&p) - 1; + + // The modifiers were the mouse coordinates, not the modifier keys + // (alt/shift/ctrl/meta) state. + *modifiers = 0; + } + + if (key_name[0] == KS_SGR_MOUSE + || key_name[0] == KS_SGR_MOUSE_RELEASE) + { + if (key_name[0] == KS_SGR_MOUSE_RELEASE) + { + is_release = TRUE; + // This is used below to set held_button. + mouse_code |= MOUSE_RELEASE; + } + } + else + { + release_is_ambiguous = TRUE; + if ((mouse_code & MOUSE_RELEASE) == MOUSE_RELEASE) + is_release = TRUE; + } + + if (key_name[0] == KS_MOUSE +# ifdef FEAT_MOUSE_GPM + || key_name[0] == KS_GPM_MOUSE +# endif +# ifdef FEAT_MOUSE_URXVT + || key_name[0] == KS_URXVT_MOUSE +# endif + || key_name[0] == KS_SGR_MOUSE + || key_name[0] == KS_SGR_MOUSE_RELEASE) + { +# if !defined(MSWIN) + /* + * Handle old style mouse events. + * Recognize the xterm mouse wheel, but not in the GUI, the + * Linux console with GPM and the MS-DOS or Win32 console + * (multi-clicks use >= 0x60). + */ + if (mouse_code >= MOUSEWHEEL_LOW +# ifdef FEAT_GUI + && !gui.in_use +# endif +# ifdef FEAT_MOUSE_GPM + && key_name[0] != KS_GPM_MOUSE +# endif + ) + { +# if defined(UNIX) + if (use_xterm_mouse() > 1 && mouse_code >= 0x80) + // mouse-move event, using MOUSE_DRAG works + mouse_code = MOUSE_DRAG; + else +# endif + // Keep the mouse_code before it's changed, so that we + // remember that it was a mouse wheel click. + wheel_code = mouse_code; + } +# ifdef FEAT_MOUSE_XTERM + else if (held_button == MOUSE_RELEASE +# ifdef FEAT_GUI + && !gui.in_use +# endif + && (mouse_code == 0x23 || mouse_code == 0x24 + || mouse_code == 0x40 || mouse_code == 0x41)) + { + // Apparently 0x23 and 0x24 are used by rxvt scroll wheel. + // And 0x40 and 0x41 are used by some xterm emulator. + wheel_code = mouse_code - (mouse_code >= 0x40 ? 0x40 : 0x23) + + MOUSEWHEEL_LOW; + } +# endif + +# if defined(UNIX) + else if (use_xterm_mouse() > 1) + { + if (mouse_code & MOUSE_DRAG_XTERM) + mouse_code |= MOUSE_DRAG; + } +# endif +# ifdef FEAT_XCLIPBOARD + else if (!(mouse_code & MOUSE_DRAG & ~MOUSE_CLICK_MASK)) + { + if (is_release) + stop_xterm_trace(); + else + start_xterm_trace(mouse_code); + } +# endif +# endif + } +#endif // !UNIX || FEAT_MOUSE_XTERM +#ifdef FEAT_MOUSE_NET + if (key_name[0] == KS_NETTERM_MOUSE) + { + int mc, mr; + + // expect a rather limited sequence like: balancing { + // \033}6,45\r + // '6' is the row, 45 is the column + p = tp + *slen; + mr = getdigits(&p); + if (*p++ != ',') + return -1; + mc = getdigits(&p); + if (*p++ != '\r') + return -1; + + mouse_col = mc - 1; + mouse_row = mr - 1; + mouse_code = MOUSE_LEFT; + *slen += (int)(p - (tp + *slen)); + } +#endif // FEAT_MOUSE_NET +#ifdef FEAT_MOUSE_JSB + if (key_name[0] == KS_JSBTERM_MOUSE) + { + int mult, val, iter, button, status; + + /* + * JSBTERM Input Model + * \033[0~zw uniq escape sequence + * (L-x) Left button pressed - not pressed x not reporting + * (M-x) Middle button pressed - not pressed x not reporting + * (R-x) Right button pressed - not pressed x not reporting + * (SDmdu) Single , Double click, m: mouse move, d: button down, + * u: button up + * ### X cursor position padded to 3 digits + * ### Y cursor position padded to 3 digits + * (s-x) SHIFT key pressed - not pressed x not reporting + * (c-x) CTRL key pressed - not pressed x not reporting + * \033\\ terminating sequence + */ + p = tp + *slen; + button = mouse_code = 0; + switch (*p++) + { + case 'L': button = 1; break; + case '-': break; + case 'x': break; // ignore sequence + default: return -1; // Unknown Result + } + switch (*p++) + { + case 'M': button |= 2; break; + case '-': break; + case 'x': break; // ignore sequence + default: return -1; // Unknown Result + } + switch (*p++) + { + case 'R': button |= 4; break; + case '-': break; + case 'x': break; // ignore sequence + default: return -1; // Unknown Result + } + status = *p++; + for (val = 0, mult = 100, iter = 0; iter < 3; iter++, + mult /= 10, p++) + if (*p >= '0' && *p <= '9') + val += (*p - '0') * mult; + else + return -1; + mouse_col = val; + for (val = 0, mult = 100, iter = 0; iter < 3; iter++, + mult /= 10, p++) + if (*p >= '0' && *p <= '9') + val += (*p - '0') * mult; + else + return -1; + mouse_row = val; + switch (*p++) + { + case 's': button |= 8; break; // SHIFT key Pressed + case '-': break; // Not Pressed + case 'x': break; // Not Reporting + default: return -1; // Unknown Result + } + switch (*p++) + { + case 'c': button |= 16; break; // CTRL key Pressed + case '-': break; // Not Pressed + case 'x': break; // Not Reporting + default: return -1; // Unknown Result + } + if (*p++ != '\033') + return -1; + if (*p++ != '\\') + return -1; + switch (status) + { + case 'D': // Double Click + case 'S': // Single Click + if (button & 1) mouse_code |= MOUSE_LEFT; + if (button & 2) mouse_code |= MOUSE_MIDDLE; + if (button & 4) mouse_code |= MOUSE_RIGHT; + if (button & 8) mouse_code |= MOUSE_SHIFT; + if (button & 16) mouse_code |= MOUSE_CTRL; + break; + case 'm': // Mouse move + if (button & 1) mouse_code |= MOUSE_LEFT; + if (button & 2) mouse_code |= MOUSE_MIDDLE; + if (button & 4) mouse_code |= MOUSE_RIGHT; + if (button & 8) mouse_code |= MOUSE_SHIFT; + if (button & 16) mouse_code |= MOUSE_CTRL; + if ((button & 7) != 0) + { + held_button = mouse_code; + mouse_code |= MOUSE_DRAG; + } + is_drag = TRUE; + showmode(); + break; + case 'd': // Button Down + if (button & 1) mouse_code |= MOUSE_LEFT; + if (button & 2) mouse_code |= MOUSE_MIDDLE; + if (button & 4) mouse_code |= MOUSE_RIGHT; + if (button & 8) mouse_code |= MOUSE_SHIFT; + if (button & 16) mouse_code |= MOUSE_CTRL; + break; + case 'u': // Button Up + is_release = TRUE; + if (button & 1) + mouse_code |= MOUSE_LEFT; + if (button & 2) + mouse_code |= MOUSE_MIDDLE; + if (button & 4) + mouse_code |= MOUSE_RIGHT; + if (button & 8) + mouse_code |= MOUSE_SHIFT; + if (button & 16) + mouse_code |= MOUSE_CTRL; + break; + default: return -1; // Unknown Result + } + + *slen += (p - (tp + *slen)); + } +#endif // FEAT_MOUSE_JSB +#ifdef FEAT_MOUSE_DEC + if (key_name[0] == KS_DEC_MOUSE) + { + /* + * The DEC Locator Input Model + * Netterm delivers the code sequence: + * \033[2;4;24;80&w (left button down) + * \033[3;0;24;80&w (left button up) + * \033[6;1;24;80&w (right button down) + * \033[7;0;24;80&w (right button up) + * CSI Pe ; Pb ; Pr ; Pc ; Pp & w + * Pe is the event code + * Pb is the button code + * Pr is the row coordinate + * Pc is the column coordinate + * Pp is the third coordinate (page number) + * Pe, the event code indicates what event caused this report + * The following event codes are defined: + * 0 - request, the terminal received an explicit request for a + * locator report, but the locator is unavailable + * 1 - request, the terminal received an explicit request for a + * locator report + * 2 - left button down + * 3 - left button up + * 4 - middle button down + * 5 - middle button up + * 6 - right button down + * 7 - right button up + * 8 - fourth button down + * 9 - fourth button up + * 10 - locator outside filter rectangle + * Pb, the button code, ASCII decimal 0-15 indicating which buttons are + * down if any. The state of the four buttons on the locator + * correspond to the low four bits of the decimal value, "1" means + * button depressed + * 0 - no buttons down, + * 1 - right, + * 2 - middle, + * 4 - left, + * 8 - fourth + * Pr is the row coordinate of the locator position in the page, + * encoded as an ASCII decimal value. If Pr is omitted, the locator + * position is undefined (outside the terminal window for example). + * Pc is the column coordinate of the locator position in the page, + * encoded as an ASCII decimal value. If Pc is omitted, the locator + * position is undefined (outside the terminal window for example). + * Pp is the page coordinate of the locator position encoded as an + * ASCII decimal value. The page coordinate may be omitted if the + * locator is on page one (the default). We ignore it anyway. + */ + int Pe, Pb, Pr, Pc; + + p = tp + *slen; + + // get event status + Pe = getdigits(&p); + if (*p++ != ';') + return -1; + + // get button status + Pb = getdigits(&p); + if (*p++ != ';') + return -1; + + // get row status + Pr = getdigits(&p); + if (*p++ != ';') + return -1; + + // get column status + Pc = getdigits(&p); + + // the page parameter is optional + if (*p == ';') + { + p++; + (void)getdigits(&p); + } + if (*p++ != '&') + return -1; + if (*p++ != 'w') + return -1; + + mouse_code = 0; + switch (Pe) + { + case 0: return -1; // position request while unavailable + case 1: // a response to a locator position request includes + // the status of all buttons + Pb &= 7; // mask off and ignore fourth button + if (Pb & 4) + mouse_code = MOUSE_LEFT; + if (Pb & 2) + mouse_code = MOUSE_MIDDLE; + if (Pb & 1) + mouse_code = MOUSE_RIGHT; + if (Pb) + { + held_button = mouse_code; + mouse_code |= MOUSE_DRAG; + WantQueryMouse = TRUE; + } + is_drag = TRUE; + showmode(); + break; + case 2: mouse_code = MOUSE_LEFT; + WantQueryMouse = TRUE; + break; + case 3: mouse_code = MOUSE_LEFT; + is_release = TRUE; + break; + case 4: mouse_code = MOUSE_MIDDLE; + WantQueryMouse = TRUE; + break; + case 5: mouse_code = MOUSE_MIDDLE; + is_release = TRUE; + break; + case 6: mouse_code = MOUSE_RIGHT; + WantQueryMouse = TRUE; + break; + case 7: mouse_code = MOUSE_RIGHT; + is_release = TRUE; + break; + case 8: return -1; // fourth button down + case 9: return -1; // fourth button up + case 10: return -1; // mouse outside of filter rectangle + default: return -1; // should never occur + } + + mouse_col = Pc - 1; + mouse_row = Pr - 1; + + *slen += (int)(p - (tp + *slen)); + } +#endif // FEAT_MOUSE_DEC +#ifdef FEAT_MOUSE_PTERM + if (key_name[0] == KS_PTERM_MOUSE) + { + int button, num_clicks, action; + + p = tp + *slen; + + action = getdigits(&p); + if (*p++ != ';') + return -1; + + mouse_row = getdigits(&p); + if (*p++ != ';') + return -1; + mouse_col = getdigits(&p); + if (*p++ != ';') + return -1; + + button = getdigits(&p); + mouse_code = 0; + + switch (button) + { + case 4: mouse_code = MOUSE_LEFT; break; + case 1: mouse_code = MOUSE_RIGHT; break; + case 2: mouse_code = MOUSE_MIDDLE; break; + default: return -1; + } + + switch (action) + { + case 31: // Initial press + if (*p++ != ';') + return -1; + + num_clicks = getdigits(&p); // Not used + break; + + case 32: // Release + is_release = TRUE; + break; + + case 33: // Drag + held_button = mouse_code; + mouse_code |= MOUSE_DRAG; + break; + + default: + return -1; + } + + if (*p++ != 't') + return -1; + + *slen += (p - (tp + *slen)); + } +#endif // FEAT_MOUSE_PTERM + + // Interpret the mouse code + current_button = (mouse_code & MOUSE_CLICK_MASK); + if (is_release) + current_button |= MOUSE_RELEASE; + + if (current_button == MOUSE_RELEASE +#ifdef FEAT_MOUSE_XTERM + && wheel_code == 0 +#endif + ) + { + /* + * If we get a mouse drag or release event when there is no mouse + * button held down (held_button == MOUSE_RELEASE), produce a K_IGNORE + * below. + * (can happen when you hold down two buttons and then let them go, or + * click in the menu bar, but not on a menu, and drag into the text). + */ + if ((mouse_code & MOUSE_DRAG) == MOUSE_DRAG) + is_drag = TRUE; + current_button = held_button; + } + else + { + if (wheel_code == 0) + { +#ifdef CHECK_DOUBLE_CLICK +# ifdef FEAT_MOUSE_GPM + /* + * Only for Unix, when GUI not active, we handle multi-clicks here, but + * not for GPM mouse events. + */ +# ifdef FEAT_GUI + if (key_name[0] != KS_GPM_MOUSE && !gui.in_use) +# else + if (key_name[0] != KS_GPM_MOUSE) +# endif +# else +# ifdef FEAT_GUI + if (!gui.in_use) +# endif +# endif + { + /* + * Compute the time elapsed since the previous mouse click. + */ + gettimeofday(&mouse_time, NULL); + if (orig_mouse_time.tv_sec == 0) + { + /* + * Avoid computing the difference between mouse_time + * and orig_mouse_time for the first click, as the + * difference would be huge and would cause + * multiplication overflow. + */ + timediff = p_mouset; + } + else + timediff = time_diff_ms(&orig_mouse_time, &mouse_time); + orig_mouse_time = mouse_time; + if (mouse_code == orig_mouse_code + && timediff < p_mouset + && orig_num_clicks != 4 + && orig_mouse_col == mouse_col + && orig_mouse_row == mouse_row + && (is_mouse_topline(curwin) + // Double click in tab pages line also works + // when window contents changes. + || (mouse_row == 0 && firstwin->w_winrow > 0)) + ) + ++orig_num_clicks; + else + orig_num_clicks = 1; + orig_mouse_col = mouse_col; + orig_mouse_row = mouse_row; + set_mouse_topline(curwin); + } +# if defined(FEAT_GUI) || defined(FEAT_MOUSE_GPM) + else + orig_num_clicks = NUM_MOUSE_CLICKS(mouse_code); +# endif +#else + orig_num_clicks = NUM_MOUSE_CLICKS(mouse_code); +#endif + is_click = TRUE; + } + orig_mouse_code = mouse_code; + } + if (!is_drag) + held_button = mouse_code & MOUSE_CLICK_MASK; + + /* + * Translate the actual mouse event into a pseudo mouse event. + * First work out what modifiers are to be used. + */ + if (orig_mouse_code & MOUSE_SHIFT) + *modifiers |= MOD_MASK_SHIFT; + if (orig_mouse_code & MOUSE_CTRL) + *modifiers |= MOD_MASK_CTRL; + if (orig_mouse_code & MOUSE_ALT) + *modifiers |= MOD_MASK_ALT; + if (orig_num_clicks == 2) + *modifiers |= MOD_MASK_2CLICK; + else if (orig_num_clicks == 3) + *modifiers |= MOD_MASK_3CLICK; + else if (orig_num_clicks == 4) + *modifiers |= MOD_MASK_4CLICK; + + // Work out our pseudo mouse event. Note that MOUSE_RELEASE gets added, + // then it's not mouse up/down. + key_name[0] = KS_EXTRA; + if (wheel_code != 0 && (!is_release || release_is_ambiguous)) + { + if (wheel_code & MOUSE_CTRL) + *modifiers |= MOD_MASK_CTRL; + if (wheel_code & MOUSE_ALT) + *modifiers |= MOD_MASK_ALT; + + if (wheel_code & 1 && wheel_code & 2) + key_name[1] = (int)KE_MOUSELEFT; + else if (wheel_code & 2) + key_name[1] = (int)KE_MOUSERIGHT; + else if (wheel_code & 1) + key_name[1] = (int)KE_MOUSEUP; + else + key_name[1] = (int)KE_MOUSEDOWN; + + held_button = MOUSE_RELEASE; + } + else + key_name[1] = get_pseudo_mouse_code(current_button, is_click, is_drag); + + + // Make sure the mouse position is valid. Some terminals may return weird + // values. + if (mouse_col >= Columns) + mouse_col = Columns - 1; + if (mouse_row >= Rows) + mouse_row = Rows - 1; + + return 0; +} + +// Functions also used for popup windows. + +/* + * Compute the buffer line position from the screen position "rowp" / "colp" in + * window "win". + * "plines_cache" can be NULL (no cache) or an array with "Rows" entries that + * caches the plines_win() result from a previous call. Entry is zero if not + * computed yet. There must be no text or setting changes since the entry is + * put in the cache. + * Returns TRUE if the position is below the last line. + */ + int +mouse_comp_pos( + win_T *win, + int *rowp, + int *colp, + linenr_T *lnump, + int *plines_cache) +{ + int col = *colp; + int row = *rowp; + linenr_T lnum; + int retval = FALSE; + int off; + int count; + +#ifdef FEAT_RIGHTLEFT + if (win->w_p_rl) + col = win->w_width - 1 - col; +#endif + + lnum = win->w_topline; + + while (row > 0) + { + int cache_idx = lnum - win->w_topline; + + // Only "Rows" lines are cached, with folding we'll run out of entries + // and use the slow way. + if (plines_cache != NULL && cache_idx < Rows + && plines_cache[cache_idx] > 0) + count = plines_cache[cache_idx]; + else + { +#ifdef FEAT_DIFF + // Don't include filler lines in "count" + if (win->w_p_diff +# ifdef FEAT_FOLDING + && !hasFoldingWin(win, lnum, NULL, NULL, TRUE, NULL) +# endif + ) + { + if (lnum == win->w_topline) + row -= win->w_topfill; + else + row -= diff_check_fill(win, lnum); + count = plines_win_nofill(win, lnum, FALSE); + } + else +#endif + count = plines_win(win, lnum, FALSE); + if (plines_cache != NULL && cache_idx < Rows) + plines_cache[cache_idx] = count; + } + + if (win->w_skipcol > 0 && lnum == win->w_topline) + { + // Adjust for 'smoothscroll' clipping the top screen lines. + // A similar formula is used in curs_columns(). + int width1 = win->w_width - win_col_off(win); + int skip_lines = 0; + 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. +#ifdef FEAT_FOLDING + (void)hasFoldingWin(win, lnum, NULL, &lnum, TRUE, NULL); +#endif + 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. + off = win_col_off(win) - win_col_off2(win); + if (col < off) + col = off; + col += row * (win->w_width - 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); + if (col <= 0) + { +#ifdef FEAT_NETBEANS_INTG + // if mouse is clicked on the gutter, then inform the netbeans server + if (*colp < win_col_off(win)) + netbeans_gutter_click(lnum); +#endif + col = 0; + } + + *colp = col; + *rowp = row; + *lnump = lnum; + return retval; +} + +/* + * Find the window at screen position "*rowp" and "*colp". The positions are + * updated to become relative to the top-left of the window. + * When "popup" is FAIL_POPUP and the position is in a popup window then NULL + * is returned. When "popup" is IGNORE_POPUP then do not even check popup + * windows. + * Returns NULL when something is wrong. + */ + win_T * +mouse_find_win(int *rowp, int *colp, mouse_find_T popup UNUSED) +{ + frame_T *fp; + win_T *wp; + +#ifdef FEAT_PROP_POPUP + win_T *pwp = NULL; + + if (popup != IGNORE_POPUP) + { + popup_reset_handled(POPUP_HANDLED_1); + while ((wp = find_next_popup(TRUE, POPUP_HANDLED_1)) != NULL) + { + if (*rowp >= wp->w_winrow && *rowp < wp->w_winrow + popup_height(wp) + && *colp >= wp->w_wincol + && *colp < wp->w_wincol + popup_width(wp)) + pwp = wp; + } + if (pwp != NULL) + { + if (popup == FAIL_POPUP) + return NULL; + *rowp -= pwp->w_winrow; + *colp -= pwp->w_wincol; + return pwp; + } + } +#endif + + 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(wp) + if (wp == fp->fr_win) + { +#ifdef FEAT_MENU + *rowp -= wp->w_winbar_height; +#endif + return wp; + } + return NULL; +} + +#if defined(NEED_VCOL2COL) || defined(FEAT_BEVAL) || defined(FEAT_PROP_POPUP) \ + || defined(FEAT_EVAL) || defined(PROTO) +/* + * Convert a virtual (screen) column to a character column. + * The first column is one. + */ + int +vcol2col(win_T *wp, linenr_T lnum, int vcol) +{ + char_u *line; + chartabsize_T cts; + + // try to advance to the specified column + line = ml_get_buf(wp->w_buffer, lnum, FALSE); + 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 (int)(cts.cts_ptr - line); +} +#endif + +#if defined(FEAT_EVAL) || defined(PROTO) +/* + * "getmousepos()" function. + */ + void +f_getmousepos(typval_T *argvars UNUSED, typval_T *rettv) +{ + dict_T *d; + win_T *wp; + int row = mouse_row; + int col = mouse_col; + varnumber_T winid = 0; + varnumber_T winrow = 0; + varnumber_T wincol = 0; + linenr_T lnum = 0; + varnumber_T column = 0; + + if (rettv_dict_alloc(rettv) == FAIL) + return; + d = rettv->vval.v_dict; + + dict_add_number(d, "screenrow", (varnumber_T)mouse_row + 1); + dict_add_number(d, "screencol", (varnumber_T)mouse_col + 1); + + wp = mouse_find_win(&row, &col, FIND_POPUP); + if (wp != NULL) + { + int top_off = 0; + int left_off = 0; + int height = wp->w_height + wp->w_status_height; + +# ifdef FEAT_PROP_POPUP + if (WIN_IS_POPUP(wp)) + { + top_off = popup_top_extra(wp); + left_off = popup_left_extra(wp); + height = popup_height(wp); + } +# endif + if (row < height) + { + winid = wp->w_id; + winrow = row + 1; + wincol = col + 1; + row -= top_off; + col -= left_off; + if (row >= 0 && row < wp->w_height && col >= 0 && col < wp->w_width) + { + (void)mouse_comp_pos(wp, &row, &col, &lnum, NULL); + col = vcol2col(wp, lnum, col); + column = col + 1; + } + } + } + dict_add_number(d, "winid", winid); + dict_add_number(d, "winrow", winrow); + dict_add_number(d, "wincol", wincol); + dict_add_number(d, "line", (varnumber_T)lnum); + dict_add_number(d, "column", column); +} +#endif |