/* 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"), 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() { 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 "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