diff options
-rw-r--r-- | runtime/doc/options.txt | 20 | ||||
-rw-r--r-- | runtime/doc/quickref.txt | 1 | ||||
-rw-r--r-- | runtime/doc/scroll.txt | 18 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 1 | ||||
-rw-r--r-- | src/nvim/edit.c | 17 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 7 | ||||
-rw-r--r-- | src/nvim/mark.c | 22 | ||||
-rw-r--r-- | src/nvim/mouse.c | 2 | ||||
-rw-r--r-- | src/nvim/normal.c | 1014 | ||||
-rw-r--r-- | src/nvim/ops.c | 4 | ||||
-rw-r--r-- | src/nvim/option.c | 65 | ||||
-rw-r--r-- | src/nvim/option_defs.h | 8 | ||||
-rw-r--r-- | src/nvim/options.lua | 8 | ||||
-rw-r--r-- | src/nvim/search.c | 13 | ||||
-rw-r--r-- | src/nvim/testdir/test_normal.vim | 41 | ||||
-rw-r--r-- | test/functional/editor/mark_spec.lua | 21 | ||||
-rw-r--r-- | test/functional/options/mousescroll_spec.lua | 151 |
17 files changed, 895 insertions, 518 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index eb377697e9..74ba353e0a 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -4210,6 +4210,26 @@ A jump table for the options with a short description can be found at |Q_op|. The 'mousemodel' option is set by the |:behave| command. + *mousescroll* +'mousescroll' string (default "ver:3,hor:6") + global + This option controls the number of lines / columns to scroll by when + scrolling with a mouse. The option is a comma separated list of parts. + Each part consists of a direction and a count as follows: + direction:count,direction:count + Direction is one of either "hor" or "ver", "hor" controls horizontal + scrolling and "ver" controls vertical scrolling. Count sets the amount + to scroll by for the given direction, it should be a non negative + integer. Each direction should be set at most once. If a direction + is omitted, a default value is used (6 for horizontal scrolling and 3 + for vertical scrolling). You can disable mouse scrolling by using + a count of 0. + + Example: > + :set mousescroll=ver:5,hor:2 +< Will make Nvim scroll 5 lines at a time when scrolling vertically, and + scroll 2 columns at a time when scrolling horizontally. + *'mouseshape'* *'mouses'* *E547* 'mouseshape' 'mouses' string (default "i:beam,r:beam,s:updown,sd:cross, m:no,ml:up-arrow,v:rightup-arrow") diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index a380088e98..6f16db5cc2 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -785,6 +785,7 @@ Short explanation of each option: *option-list* 'mousefocus' 'mousef' keyboard focus follows the mouse 'mousehide' 'mh' hide mouse pointer while typing 'mousemodel' 'mousem' changes meaning of mouse buttons +'mousescroll' amount to scroll by when scrolling with a mouse 'mouseshape' 'mouses' shape of the mouse pointer in different modes 'mousetime' 'mouset' max time between mouse double-click 'nrformats' 'nf' number formats recognized for CTRL-A command diff --git a/runtime/doc/scroll.txt b/runtime/doc/scroll.txt index 7d1da3b409..170c87a1a4 100644 --- a/runtime/doc/scroll.txt +++ b/runtime/doc/scroll.txt @@ -239,12 +239,16 @@ the "h" flag in 'guioptions' is set, the cursor moves to the longest visible line if the cursor line is about to be scrolled off the screen (similarly to how the horizontal scrollbar works). -You can modify the default behavior by mapping the keys. For example, to make -the scroll wheel move one line or half a page in Normal mode: > - :map <ScrollWheelUp> <C-Y> - :map <S-ScrollWheelUp> <C-U> - :map <ScrollWheelDown> <C-E> - :map <S-ScrollWheelDown> <C-D> -You can also use Alt and Ctrl modifiers. +You can control the number of lines / columns to scroll by using the +'mousescroll' option. You can also modify the default behavior by mapping +the keys. For example, to scroll a page at a time in normal mode: > + :map <ScrollWheelUp> <C-B> + :map <ScrollWheelDown> <C-F> +Scroll keys can also be combined with modifiers such as Shift, Ctrl, and Alt. + +When scrolling with a mouse, the window currently under the cursor is +scrolled. This allows you to scroll inactive windows. Note that when scroll +keys are remapped to keyboard keys, the active window is affected regardless +of the current cursor position. vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 4b26e5501c..8e853aaf9e 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -238,6 +238,7 @@ Options: 'inccommand' shows interactive results for |:substitute|-like commands and |:command-preview| commands 'laststatus' global statusline support + 'mousescroll' amount to scroll by when scrolling with a mouse 'pumblend' pseudo-transparent popupmenu 'scrollback' 'signcolumn' supports up to 9 dynamic/fixed columns diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 26422f06b1..de6f2c32de 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -7935,13 +7935,8 @@ static bool ins_esc(long *count, int cmdchar, bool nomove) * Don't do it for CTRL-O, unless past the end of the line. */ if (!nomove - && (curwin->w_cursor.col != 0 - || curwin->w_cursor.coladd > 0 - ) - && (restart_edit == NUL - || (gchar_cursor() == NUL - && !VIsual_active - )) + && (curwin->w_cursor.col != 0 || curwin->w_cursor.coladd > 0) + && (restart_edit == NUL || (gchar_cursor() == NUL && !VIsual_active)) && !revins_on) { if (curwin->w_cursor.coladd > 0 || get_ve_flags() == VE_ALL) { oneleft(); @@ -8557,14 +8552,12 @@ static void ins_mousescroll(int dir) } // Don't scroll the window in which completion is being done. - if (!pum_visible() - || curwin != old_curwin) { + if (!pum_visible() || curwin != old_curwin) { if (dir == MSCR_DOWN || dir == MSCR_UP) { if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { - scroll_redraw(dir, - (curwin->w_botline - curwin->w_topline)); + scroll_redraw(dir, (long)(curwin->w_botline - curwin->w_topline)); } else { - scroll_redraw(dir, 3L); + scroll_redraw(dir, p_mousescroll_vert); } } else { mouse_scroll_horiz(dir); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 43d57cb278..18490bd60c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -4345,13 +4345,12 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, int // used by itself: ":'M". MarkGet flag = to_other_file && cmd[1] == NUL ? kMarkAll : kMarkBufLocal; fmark_T *fm = mark_get(curbuf, curwin, NULL, flag, *cmd); - MarkMoveRes move_res = mark_move_to(fm, kMarkBeginLine); cmd++; - // Switched buffer - if (move_res & kMarkSwitchedBuf) { + if (fm != NULL && fm->fnum != curbuf->handle) { + // Jumped to another file. lnum = curwin->w_cursor.lnum; } else { - if (fm == NULL) { + if (!mark_check(fm)) { cmd = NULL; goto error; } diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 0bf5875269..66855c66b5 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -448,27 +448,21 @@ fmark_T *mark_get_local(buf_T *buf, win_T *win, int name) fmark_T *mark_get_motion(buf_T *buf, win_T *win, int name) { fmark_T *mark = NULL; - if (name == '{' || name == '}') { - // to previous/next paragraph + const pos_T pos = curwin->w_cursor; + const bool slcb = listcmd_busy; + listcmd_busy = true; // avoid that '' is changed + if (name == '{' || name == '}') { // to previous/next paragraph oparg_T oa; - bool slcb = listcmd_busy; - listcmd_busy = true; // avoid that '' is changed - - if (findpar(&oa.inclusive, - name == '}' ? FORWARD : BACKWARD, 1L, NUL, false)) { + if (findpar(&oa.inclusive, name == '}' ? FORWARD : BACKWARD, 1L, NUL, false)) { mark = pos_to_mark(buf, NULL, win->w_cursor); } - listcmd_busy = slcb; - // to previous/next sentence - } else if (name == '(' || name == ')') { - bool slcb = listcmd_busy; - listcmd_busy = true; // avoid that '' is changed - + } else if (name == '(' || name == ')') { // to previous/next sentence if (findsent(name == ')' ? FORWARD : BACKWARD, 1L)) { mark = pos_to_mark(buf, NULL, win->w_cursor); } - listcmd_busy = slcb; } + curwin->w_cursor = pos; + listcmd_busy = slcb; return mark; } diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index 3aee20dc7b..2ea108d3df 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -688,7 +688,7 @@ bool mouse_scroll_horiz(int dir) return false; } - int step = 6; + int step = (int)p_mousescroll_hor; if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { step = curwin->w_width_inner; } diff --git a/src/nvim/normal.c b/src/nvim/normal.c index ed624c0c9f..3a552ef053 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -559,6 +559,14 @@ static bool normal_need_additional_char(NormalState *s) static bool normal_need_redraw_mode_message(NormalState *s) { + // In Visual mode and with "^O" in Insert mode, a short message will be + // overwritten by the mode message. Wait a bit, until a key is hit. + // In Visual mode, it's more important to keep the Visual area updated + // than keeping a message (e.g. from a /pat search). + // Only do this if the command was typed, not from a mapping. + // Don't wait when emsg_silent is non-zero. + // Also wait a bit after an error message, e.g. for "^O:". + // Don't redraw the screen, it would remove the message. return ( // 'showmode' is set and messages can be printed ((p_smd && msg_silent == 0 @@ -892,14 +900,6 @@ static void normal_finish_command(NormalState *s) // Wait for a moment when a message is displayed that will be overwritten // by the mode message. - // In Visual mode and with "^O" in Insert mode, a short message will be - // overwritten by the mode message. Wait a bit, until a key is hit. - // In Visual mode, it's more important to keep the Visual area updated - // than keeping a message (e.g. from a /pat search). - // Only do this if the command was typed, not from a mapping. - // Don't wait when emsg_silent is non-zero. - // Also wait a bit after an error message, e.g. for "^O:". - // Don't redraw the screen, it would remove the message. if (normal_need_redraw_mode_message(s)) { normal_redraw_mode_message(s); } @@ -2078,8 +2078,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent) } else if ((mod_mask & MOD_MASK_SHIFT)) { // Shift-Mouse click searches for the next occurrence of the word under // the mouse pointer - if (State & MODE_INSERT - || (VIsual_active && VIsual_select)) { + if (State & MODE_INSERT || (VIsual_active && VIsual_select)) { stuffcharReadbuff(Ctrl_O); } if (which_button == MOUSE_LEFT) { @@ -2546,8 +2545,7 @@ static bool checkclearop(oparg_T *oap) /// @return true if operator or Visual was active. static bool checkclearopq(oparg_T *oap) { - if (oap->op_type == OP_NOP - && !VIsual_active) { + if (oap->op_type == OP_NOP && !VIsual_active) { return false; } clearopbeep(oap); @@ -3392,8 +3390,8 @@ static void nv_mousescroll(cmdarg_T *cap) if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { (void)onepage(cap->arg ? FORWARD : BACKWARD, 1L); } else { - cap->count1 = 3; - cap->count0 = 3; + cap->count1 = p_mousescroll_vert; + cap->count0 = p_mousescroll_vert; nv_scroll_line(cap); } } else { @@ -3474,6 +3472,108 @@ void scroll_redraw(int up, long count) redraw_later(curwin, VALID); } +/// Get the count specified after a 'z' command. +/// @return true to process the 'z' command and false to skip it. +static bool nv_z_get_count(cmdarg_T *cap, int *nchar_arg) +{ + int nchar = *nchar_arg; + + // "z123{nchar}": edit the count before obtaining {nchar} + if (checkclearop(cap->oap)) { + return false; + } + long n = nchar - '0'; + + for (;;) { + no_mapping++; + allow_keys++; // no mapping for nchar, but allow key codes + nchar = plain_vgetc(); + LANGMAP_ADJUST(nchar, true); + no_mapping--; + allow_keys--; + (void)add_to_showcmd(nchar); + if (nchar == K_DEL || nchar == K_KDEL) { + n /= 10; + } else if (ascii_isdigit(nchar)) { + n = n * 10 + (nchar - '0'); + } else if (nchar == CAR) { + win_setheight((int)n); + break; + } else if (nchar == 'l' + || nchar == 'h' + || nchar == K_LEFT + || nchar == K_RIGHT) { + cap->count1 = n ? n * cap->count1 : cap->count1; + *nchar_arg = nchar; + return true; + } else { + clearopbeep(cap->oap); + break; + } + } + cap->oap->op_type = OP_NOP; + return false; +} + +/// "zug" and "zuw": undo "zg" and "zw" +/// "zg": add good word to word list +/// "zw": add wrong word to word list +/// "zG": add good word to temp word list +/// "zW": add wrong word to temp word list +static int nv_zg_zw(cmdarg_T *cap, int nchar) +{ + bool undo = false; + + if (nchar == 'u') { + no_mapping++; + allow_keys++; // no mapping for nchar, but allow key codes + nchar = plain_vgetc(); + LANGMAP_ADJUST(nchar, true); + no_mapping--; + allow_keys--; + (void)add_to_showcmd(nchar); + if (vim_strchr("gGwW", nchar) == NULL) { + clearopbeep(cap->oap); + return OK; + } + undo = true; + } + + if (checkclearop(cap->oap)) { + return OK; + } + char_u *ptr = NULL; + size_t len; + if (VIsual_active && !get_visual_text(cap, &ptr, &len)) { + return FAIL; + } + if (ptr == NULL) { + pos_T pos = curwin->w_cursor; + + // Find bad word under the cursor. When 'spell' is + // off this fails and find_ident_under_cursor() is + // used below. + emsg_off++; + len = spell_move_to(curwin, FORWARD, true, true, NULL); + emsg_off--; + if (len != 0 && curwin->w_cursor.col <= pos.col) { + ptr = ml_get_pos(&curwin->w_cursor); + } + curwin->w_cursor = pos; + } + + if (ptr == NULL && (len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) { + return FAIL; + } + assert(len <= INT_MAX); + spell_add_word(ptr, (int)len, + nchar == 'w' || nchar == 'W' ? SPELL_ADD_BAD : SPELL_ADD_GOOD, + (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1, + undo); + + return OK; +} + /// Commands that start with "z". static void nv_zet(cmdarg_T *cap) { @@ -3482,47 +3582,13 @@ static void nv_zet(cmdarg_T *cap) int nchar = cap->nchar; long old_fdl = curwin->w_p_fdl; int old_fen = curwin->w_p_fen; - bool undo = false; int l_p_siso = (int)get_sidescrolloff_value(curwin); - if (ascii_isdigit(nchar)) { - // "z123{nchar}": edit the count before obtaining {nchar} - if (checkclearop(cap->oap)) { - return; - } - n = nchar - '0'; - for (;;) { - no_mapping++; - allow_keys++; // no mapping for nchar, but allow key codes - nchar = plain_vgetc(); - LANGMAP_ADJUST(nchar, true); - no_mapping--; - allow_keys--; - (void)add_to_showcmd(nchar); - if (nchar == K_DEL || nchar == K_KDEL) { - n /= 10; - } else if (ascii_isdigit(nchar)) { - n = n * 10 + (nchar - '0'); - } else if (nchar == CAR) { - win_setheight(n); - break; - } else if (nchar == 'l' - || nchar == 'h' - || nchar == K_LEFT - || nchar == K_RIGHT) { - cap->count1 = n ? n * cap->count1 : cap->count1; - goto dozet; - } else { - clearopbeep(cap->oap); - break; - } - } - cap->oap->op_type = OP_NOP; + if (ascii_isdigit(nchar) && !nv_z_get_count(cap, &nchar)) { return; } -dozet: // "zf" and "zF" are always an operator, "zd", "zo", "zO", "zc" // and "zC" only in Visual mode. "zj" and "zk" are motion // commands. @@ -3872,60 +3938,14 @@ dozet: break; case 'u': // "zug" and "zuw": undo "zg" and "zw" - no_mapping++; - allow_keys++; // no mapping for nchar, but allow key codes - nchar = plain_vgetc(); - LANGMAP_ADJUST(nchar, true); - no_mapping--; - allow_keys--; - (void)add_to_showcmd(nchar); - if (vim_strchr("gGwW", nchar) == NULL) { - clearopbeep(cap->oap); - break; - } - undo = true; - FALLTHROUGH; - case 'g': // "zg": add good word to word list case 'w': // "zw": add wrong word to word list case 'G': // "zG": add good word to temp word list case 'W': // "zW": add wrong word to temp word list - { - char_u *ptr = NULL; - size_t len; - - if (checkclearop(cap->oap)) { - break; - } - if (VIsual_active && !get_visual_text(cap, &ptr, &len)) { + if (nv_zg_zw(cap, nchar) == FAIL) { return; } - if (ptr == NULL) { - pos_T pos = curwin->w_cursor; - - // Find bad word under the cursor. When 'spell' is - // off this fails and find_ident_under_cursor() is - // used below. - emsg_off++; - len = spell_move_to(curwin, FORWARD, true, true, NULL); - emsg_off--; - if (len != 0 && curwin->w_cursor.col <= pos.col) { - ptr = ml_get_pos(&curwin->w_cursor); - } - curwin->w_cursor = pos; - } - - if (ptr == NULL && (len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) { - return; - } - assert(len <= INT_MAX); - spell_add_word(ptr, (int)len, - nchar == 'w' || nchar == 'W' - ? SPELL_ADD_BAD : SPELL_ADD_GOOD, - (nchar == 'G' || nchar == 'W') ? 0 : (int)cap->count1, - undo); - } - break; + break; case '=': // "z=": suggestions for a badly spelled word if (!checkclearop(cap->oap)) { @@ -4120,6 +4140,69 @@ void do_nv_ident(int c1, int c2) nv_ident(&ca); } +/// 'K' normal-mode command. Get the command to lookup the keyword under the +/// cursor. +static size_t nv_K_getcmd(cmdarg_T *cap, char_u *kp, bool kp_help, bool kp_ex, char_u **ptr_arg, + size_t n, char *buf, size_t buf_size) +{ + if (kp_help) { + // in the help buffer + STRCPY(buf, "he! "); + return n; + } + + if (kp_ex) { + // 'keywordprg' is an ex command + if (cap->count0 != 0) { // Send the count to the ex command. + snprintf(buf, buf_size, "%" PRId64, (int64_t)(cap->count0)); + } + STRCAT(buf, kp); + STRCAT(buf, " "); + return n; + } + + char_u *ptr = *ptr_arg; + + // An external command will probably use an argument starting + // with "-" as an option. To avoid trouble we skip the "-". + while (*ptr == '-' && n > 0) { + ptr++; + n--; + } + if (n == 0) { + // found dashes only + emsg(_(e_noident)); + xfree(buf); + *ptr_arg = ptr; + return 0; + } + + // When a count is given, turn it into a range. Is this + // really what we want? + bool isman = (STRCMP(kp, "man") == 0); + bool isman_s = (STRCMP(kp, "man -s") == 0); + if (cap->count0 != 0 && !(isman || isman_s)) { + snprintf(buf, buf_size, ".,.+%" PRId64, (int64_t)(cap->count0 - 1)); + } + + do_cmdline_cmd("tabnew"); + STRCAT(buf, "terminal "); + if (cap->count0 == 0 && isman_s) { + STRCAT(buf, "man"); + } else { + STRCAT(buf, kp); + } + STRCAT(buf, " "); + if (cap->count0 != 0 && (isman || isman_s)) { + snprintf(buf + STRLEN(buf), buf_size - STRLEN(buf), "%" PRId64, + (int64_t)cap->count0); + STRCAT(buf, " "); + } + + *ptr_arg = ptr; + return n; +} + /// Handle the commands that use the word under the cursor. /// [g] CTRL-] :ta to current identifier /// [g] 'K' run program for current identifier @@ -4199,48 +4282,9 @@ static void nv_ident(cmdarg_T *cap) break; case 'K': - if (kp_help) { - STRCPY(buf, "he! "); - } else if (kp_ex) { - if (cap->count0 != 0) { // Send the count to the ex command. - snprintf(buf, buf_size, "%" PRId64, (int64_t)(cap->count0)); - } - STRCAT(buf, kp); - STRCAT(buf, " "); - } else { - // An external command will probably use an argument starting - // with "-" as an option. To avoid trouble we skip the "-". - while (*ptr == '-' && n > 0) { - ptr++; - n--; - } - if (n == 0) { - emsg(_(e_noident)); // found dashes only - xfree(buf); - return; - } - - // When a count is given, turn it into a range. Is this - // really what we want? - bool isman = (STRCMP(kp, "man") == 0); - bool isman_s = (STRCMP(kp, "man -s") == 0); - if (cap->count0 != 0 && !(isman || isman_s)) { - snprintf(buf, buf_size, ".,.+%" PRId64, (int64_t)(cap->count0 - 1)); - } - - do_cmdline_cmd("tabnew"); - STRCAT(buf, "terminal "); - if (cap->count0 == 0 && isman_s) { - STRCAT(buf, "man"); - } else { - STRCAT(buf, kp); - } - STRCAT(buf, " "); - if (cap->count0 != 0 && (isman || isman_s)) { - snprintf(buf + STRLEN(buf), buf_size - STRLEN(buf), "%" PRId64, - (int64_t)cap->count0); - STRCAT(buf, " "); - } + n = nv_K_getcmd(cap, kp, kp_help, kp_ex, &ptr, n, buf, buf_size); + if (n == 0) { + return; } break; @@ -4472,7 +4516,6 @@ static void nv_scroll(cmdarg_T *cap) static void nv_right(cmdarg_T *cap) { long n; - int PAST_LINE; if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) { // <C-Right> and <S-Right> move a word or WORD right @@ -4485,20 +4528,19 @@ static void nv_right(cmdarg_T *cap) cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; - PAST_LINE = (VIsual_active && *p_sel != 'o'); + bool past_line = (VIsual_active && *p_sel != 'o'); - // In virtual mode, there's no such thing as "PAST_LINE", as lines are - // (theoretically) infinitely long. + // In virtual edit mode, there's no such thing as "past_line", as lines + // are (theoretically) infinitely long. if (virtual_active()) { - PAST_LINE = 0; + past_line = false; } for (n = cap->count1; n > 0; n--) { - if ((!PAST_LINE && oneright() == false) - || (PAST_LINE - && *get_cursor_pos_ptr() == NUL)) { - // <Space> wraps to next line if 'whichwrap' has 's'. - // 'l' wraps to next line if 'whichwrap' has 'l'. + if ((!past_line && oneright() == false) + || (past_line && *get_cursor_pos_ptr() == NUL)) { + // <Space> wraps to next line if 'whichwrap' has 's'. + // 'l' wraps to next line if 'whichwrap' has 'l'. // CURS_RIGHT wraps to next line if 'whichwrap' has '>'. if (((cap->cmdchar == ' ' && vim_strchr((char *)p_ww, 's') != NULL) || (cap->cmdchar == 'l' && vim_strchr((char *)p_ww, 'l') != NULL) @@ -4531,7 +4573,7 @@ static void nv_right(cmdarg_T *cap) } } break; - } else if (PAST_LINE) { + } else if (past_line) { curwin->w_set_curswant = true; if (virtual_active()) { oneright(); @@ -4847,18 +4889,131 @@ static void nv_csearch(cmdarg_T *cap) } } +/// "[{", "[(", "]}" or "])": go to Nth unclosed '{', '(', '}' or ')' +/// "[#", "]#": go to start/end of Nth innermost #if..#endif construct. +/// "[/", "[*", "]/", "]*": go to Nth comment start/end. +/// "[m" or "]m" search for prev/next start of (Java) method. +/// "[M" or "]M" search for prev/next end of (Java) method. +static void nv_bracket_block(cmdarg_T *cap, const pos_T *old_pos) +{ + pos_T new_pos = { 0, 0, 0 }; + pos_T *pos = NULL; // init for GCC + pos_T prev_pos; + long n; + int findc; + int c; + + if (cap->nchar == '*') { + cap->nchar = '/'; + } + prev_pos.lnum = 0; + if (cap->nchar == 'm' || cap->nchar == 'M') { + if (cap->cmdchar == '[') { + findc = '{'; + } else { + findc = '}'; + } + n = 9999; + } else { + findc = cap->nchar; + n = cap->count1; + } + for (; n > 0; n--) { + if ((pos = findmatchlimit(cap->oap, findc, + (cap->cmdchar == '[') ? FM_BACKWARD : FM_FORWARD, 0)) == NULL) { + if (new_pos.lnum == 0) { // nothing found + if (cap->nchar != 'm' && cap->nchar != 'M') { + clearopbeep(cap->oap); + } + } else { + pos = &new_pos; // use last one found + } + break; + } + prev_pos = new_pos; + curwin->w_cursor = *pos; + new_pos = *pos; + } + curwin->w_cursor = *old_pos; + + // Handle "[m", "]m", "[M" and "[M". The findmatchlimit() only + // brought us to the match for "[m" and "]M" when inside a method. + // Try finding the '{' or '}' we want to be at. + // Also repeat for the given count. + if (cap->nchar == 'm' || cap->nchar == 'M') { + // norm is true for "]M" and "[m" + int norm = ((findc == '{') == (cap->nchar == 'm')); + + n = cap->count1; + // found a match: we were inside a method + if (prev_pos.lnum != 0) { + pos = &prev_pos; + curwin->w_cursor = prev_pos; + if (norm) { + n--; + } + } else { + pos = NULL; + } + while (n > 0) { + for (;;) { + if ((findc == '{' ? dec_cursor() : inc_cursor()) < 0) { + // if not found anything, that's an error + if (pos == NULL) { + clearopbeep(cap->oap); + } + n = 0; + break; + } + c = gchar_cursor(); + if (c == '{' || c == '}') { + // Must have found end/start of class: use it. + // Or found the place to be at. + if ((c == findc && norm) || (n == 1 && !norm)) { + new_pos = curwin->w_cursor; + pos = &new_pos; + n = 0; + } else if (new_pos.lnum == 0) { + // if no match found at all, we started outside of the + // class and we're inside now. Just go on. + new_pos = curwin->w_cursor; + pos = &new_pos; + } else if ((pos = findmatchlimit(cap->oap, findc, + (cap->cmdchar == '[') ? FM_BACKWARD : FM_FORWARD, + 0)) == NULL) { + // found start/end of other method: go to match + n = 0; + } else { + curwin->w_cursor = *pos; + } + break; + } + } + n--; + } + curwin->w_cursor = *old_pos; + if (pos == NULL && new_pos.lnum != 0) { + clearopbeep(cap->oap); + } + } + if (pos != NULL) { + setpcmark(); + curwin->w_cursor = *pos; + curwin->w_set_curswant = true; + if ((fdo_flags & FDO_BLOCK) && KeyTyped + && cap->oap->op_type == OP_NOP) { + foldOpenCursor(); + } + } +} + /// "[" and "]" commands. /// cap->arg is BACKWARD for "[" and FORWARD for "]". static void nv_brackets(cmdarg_T *cap) { - pos_T new_pos = { 0, 0, 0 }; - pos_T prev_pos; - pos_T *pos = NULL; // init for GCC pos_T old_pos; // cursor position before command int flag; long n; - int findc; - int c; cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; @@ -4874,7 +5029,7 @@ static void nv_brackets(cmdarg_T *cap) // // search list jump // fwd bwd fwd bwd fwd bwd - // identifier "]i" "[i" "]I" "[I" "]^I" "[^I" + // identifier "]i" "[i" "]I" "[I" "]^I" "[^I" // define "]d" "[d" "]D" "[D" "]^D" "[^D" char_u *ptr; size_t len; @@ -4907,108 +5062,7 @@ static void nv_brackets(cmdarg_T *cap) // "[/", "[*", "]/", "]*": go to Nth comment start/end. // "[m" or "]m" search for prev/next start of (Java) method. // "[M" or "]M" search for prev/next end of (Java) method. - if (cap->nchar == '*') { - cap->nchar = '/'; - } - prev_pos.lnum = 0; - if (cap->nchar == 'm' || cap->nchar == 'M') { - if (cap->cmdchar == '[') { - findc = '{'; - } else { - findc = '}'; - } - n = 9999; - } else { - findc = cap->nchar; - n = cap->count1; - } - for (; n > 0; n--) { - if ((pos = findmatchlimit(cap->oap, findc, - (cap->cmdchar == '[') ? FM_BACKWARD : FM_FORWARD, 0)) == NULL) { - if (new_pos.lnum == 0) { // nothing found - if (cap->nchar != 'm' && cap->nchar != 'M') { - clearopbeep(cap->oap); - } - } else { - pos = &new_pos; // use last one found - } - break; - } - prev_pos = new_pos; - curwin->w_cursor = *pos; - new_pos = *pos; - } - curwin->w_cursor = old_pos; - - // Handle "[m", "]m", "[M" and "[M". The findmatchlimit() only - // brought us to the match for "[m" and "]M" when inside a method. - // Try finding the '{' or '}' we want to be at. - // Also repeat for the given count. - if (cap->nchar == 'm' || cap->nchar == 'M') { - // norm is true for "]M" and "[m" - int norm = ((findc == '{') == (cap->nchar == 'm')); - - n = cap->count1; - // found a match: we were inside a method - if (prev_pos.lnum != 0) { - pos = &prev_pos; - curwin->w_cursor = prev_pos; - if (norm) { - n--; - } - } else { - pos = NULL; - } - while (n > 0) { - for (;;) { - if ((findc == '{' ? dec_cursor() : inc_cursor()) < 0) { - // if not found anything, that's an error - if (pos == NULL) { - clearopbeep(cap->oap); - } - n = 0; - break; - } - c = gchar_cursor(); - if (c == '{' || c == '}') { - // Must have found end/start of class: use it. - // Or found the place to be at. - if ((c == findc && norm) || (n == 1 && !norm)) { - new_pos = curwin->w_cursor; - pos = &new_pos; - n = 0; - } else if (new_pos.lnum == 0) { - // if no match found at all, we started outside of the - // class and we're inside now. Just go on. - new_pos = curwin->w_cursor; - pos = &new_pos; - } else if ((pos = findmatchlimit(cap->oap, findc, - (cap->cmdchar == '[') ? FM_BACKWARD : FM_FORWARD, - 0)) == NULL) { - // found start/end of other method: go to match - n = 0; - } else { - curwin->w_cursor = *pos; - } - break; - } - } - n--; - } - curwin->w_cursor = old_pos; - if (pos == NULL && new_pos.lnum != 0) { - clearopbeep(cap->oap); - } - } - if (pos != NULL) { - setpcmark(); - curwin->w_cursor = *pos; - curwin->w_set_curswant = true; - if ((fdo_flags & FDO_BLOCK) && KeyTyped - && cap->oap->op_type == OP_NOP) { - foldOpenCursor(); - } - } + nv_bracket_block(cap, &old_pos); } else if (cap->nchar == '[' || cap->nchar == ']') { // "[[", "[]", "]]" and "][": move to start or end of function if (cap->nchar == cap->cmdchar) { // "]]" or "[[" @@ -5035,19 +5089,19 @@ static void nv_brackets(cmdarg_T *cap) nv_put_opt(cap, true); } else if (cap->nchar == '\'' || cap->nchar == '`') { // "['", "[`", "]'" and "]`": jump to next mark - fmark_T *fm = NULL; - pos = &curwin->w_cursor; + fmark_T *fm = pos_to_mark(curbuf, NULL, curwin->w_cursor); + fmark_T *prev_fm; for (n = cap->count1; n > 0; n--) { - prev_pos = *pos; - fm = getnextmark(pos, cap->cmdchar == '[' ? BACKWARD : FORWARD, + prev_fm = fm; + fm = getnextmark(&fm->mark, cap->cmdchar == '[' ? BACKWARD : FORWARD, cap->nchar == '\''); - if (pos == NULL) { + if (fm == NULL) { break; - } else { - pos = fm != NULL ? &fm->mark : NULL; // Adjust for the next iteration. } } - fm = fm == NULL ? pos_to_mark(curbuf, NULL, curwin->w_cursor) : fm; + if (fm == NULL) { + fm = prev_fm; + } MarkMove flags = kMarkContext; flags |= cap->nchar == '\'' ? kMarkBeginLine: 0; nv_mark_move_to(cap, flags, fm); @@ -5883,13 +5937,218 @@ static void nv_suspend(cmdarg_T *cap) do_cmdline_cmd("st"); } +/// "gv": Reselect the previous Visual area. If Visual already active, +/// exchange previous and current Visual area. +static void nv_gv_cmd(cmdarg_T *cap) +{ + if (checkclearop(cap->oap)) { + return; + } + + if (curbuf->b_visual.vi_start.lnum == 0 + || curbuf->b_visual.vi_start.lnum > curbuf->b_ml.ml_line_count + || curbuf->b_visual.vi_end.lnum == 0) { + beep_flush(); + return; + } + + pos_T tpos; + // set w_cursor to the start of the Visual area, tpos to the end + if (VIsual_active) { + int i = VIsual_mode; + VIsual_mode = curbuf->b_visual.vi_mode; + curbuf->b_visual.vi_mode = i; + curbuf->b_visual_mode_eval = i; + i = curwin->w_curswant; + curwin->w_curswant = curbuf->b_visual.vi_curswant; + curbuf->b_visual.vi_curswant = i; + + tpos = curbuf->b_visual.vi_end; + curbuf->b_visual.vi_end = curwin->w_cursor; + curwin->w_cursor = curbuf->b_visual.vi_start; + curbuf->b_visual.vi_start = VIsual; + } else { + VIsual_mode = curbuf->b_visual.vi_mode; + curwin->w_curswant = curbuf->b_visual.vi_curswant; + tpos = curbuf->b_visual.vi_end; + curwin->w_cursor = curbuf->b_visual.vi_start; + } + + VIsual_active = true; + VIsual_reselect = true; + + // Set Visual to the start and w_cursor to the end of the Visual + // area. Make sure they are on an existing character. + check_cursor(); + VIsual = curwin->w_cursor; + curwin->w_cursor = tpos; + check_cursor(); + update_topline(curwin); + + // When called from normal "g" command: start Select mode when + // 'selectmode' contains "cmd". When called for K_SELECT, always + // start Select mode. + if (cap->arg) { + VIsual_select = true; + VIsual_select_reg = 0; + } else { + may_start_select('c'); + } + setmouse(); + redraw_curbuf_later(INVERTED); + showmode(); +} + +/// "g0", "g^" : Like "0" and "^" but for screen lines. +/// "gm": middle of "g0" and "g$". +static void nv_g_home_m_cmd(cmdarg_T *cap) +{ + int i; + const bool flag = cap->nchar == '^'; + + cap->oap->motion_type = kMTCharWise; + cap->oap->inclusive = false; + if (curwin->w_p_wrap + && curwin->w_width_inner != 0) { + int width1 = curwin->w_width_inner - curwin_col_off(); + int width2 = width1 + curwin_col_off2(); + + validate_virtcol(); + i = 0; + if (curwin->w_virtcol >= (colnr_T)width1 && width2 > 0) { + i = (curwin->w_virtcol - width1) / width2 * width2 + width1; + } + } else { + i = curwin->w_leftcol; + } + // Go to the middle of the screen line. When 'number' or + // 'relativenumber' is on and lines are wrapping the middle can be more + // to the left. + if (cap->nchar == 'm') { + i += (curwin->w_width_inner - curwin_col_off() + + ((curwin->w_p_wrap && i > 0) ? curwin_col_off2() : 0)) / 2; + } + coladvance((colnr_T)i); + if (flag) { + do { + i = gchar_cursor(); + } while (ascii_iswhite(i) && oneright()); + curwin->w_valid &= ~VALID_WCOL; + } + curwin->w_set_curswant = true; +} + +/// "g_": to the last non-blank character in the line or <count> lines downward. +static void nv_g_underscore_cmd(cmdarg_T *cap) +{ + cap->oap->motion_type = kMTCharWise; + cap->oap->inclusive = true; + curwin->w_curswant = MAXCOL; + if (cursor_down(cap->count1 - 1, cap->oap->op_type == OP_NOP) == false) { + clearopbeep(cap->oap); + return; + } + + char_u *ptr = get_cursor_line_ptr(); + + // In Visual mode we may end up after the line. + if (curwin->w_cursor.col > 0 && ptr[curwin->w_cursor.col] == NUL) { + curwin->w_cursor.col--; + } + + // Decrease the cursor column until it's on a non-blank. + while (curwin->w_cursor.col > 0 && ascii_iswhite(ptr[curwin->w_cursor.col])) { + curwin->w_cursor.col--; + } + curwin->w_set_curswant = true; + adjust_for_sel(cap); +} + +/// "g$" : Like "$" but for screen lines. +static void nv_g_dollar_cmd(cmdarg_T *cap) +{ + oparg_T *oap = cap->oap; + int i; + int col_off = curwin_col_off(); + + oap->motion_type = kMTCharWise; + oap->inclusive = true; + if (curwin->w_p_wrap && curwin->w_width_inner != 0) { + curwin->w_curswant = MAXCOL; // so we stay at the end + if (cap->count1 == 1) { + int width1 = curwin->w_width_inner - col_off; + int width2 = width1 + curwin_col_off2(); + + validate_virtcol(); + i = width1 - 1; + if (curwin->w_virtcol >= (colnr_T)width1) { + i += ((curwin->w_virtcol - width1) / width2 + 1) * width2; + } + coladvance((colnr_T)i); + + // Make sure we stick in this column. + validate_virtcol(); + curwin->w_curswant = curwin->w_virtcol; + curwin->w_set_curswant = false; + if (curwin->w_cursor.col > 0 && curwin->w_p_wrap) { + // Check for landing on a character that got split at + // the end of the line. We do not want to advance to + // the next screen line. + if (curwin->w_virtcol > (colnr_T)i) { + curwin->w_cursor.col--; + } + } + } else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false) { + clearopbeep(oap); + } + } else { + if (cap->count1 > 1) { + // if it fails, let the cursor still move to the last char + (void)cursor_down(cap->count1 - 1, false); + } + i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1; + coladvance((colnr_T)i); + + // if the character doesn't fit move one back + if (curwin->w_cursor.col > 0 && utf_ptr2cells((const char *)get_cursor_pos_ptr()) > 1) { + colnr_T vcol; + + getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); + if (vcol >= curwin->w_leftcol + curwin->w_width - col_off) { + curwin->w_cursor.col--; + } + } + + // Make sure we stick in this column. + validate_virtcol(); + curwin->w_curswant = curwin->w_virtcol; + curwin->w_set_curswant = false; + } +} + +/// "gi": start Insert at the last position. +static void nv_gi_cmd(cmdarg_T *cap) +{ + if (curbuf->b_last_insert.mark.lnum != 0) { + curwin->w_cursor = curbuf->b_last_insert.mark; + check_cursor_lnum(); + int i = (int)STRLEN(get_cursor_line_ptr()); + if (curwin->w_cursor.col > (colnr_T)i) { + if (virtual_active()) { + curwin->w_cursor.coladd += curwin->w_cursor.col - i; + } + curwin->w_cursor.col = i; + } + } + cap->cmdchar = 'i'; + nv_edit(cap); +} + /// Commands starting with "g". static void nv_g_cmd(cmdarg_T *cap) { oparg_T *oap = cap->oap; - pos_T tpos; int i; - bool flag = false; switch (cap->nchar) { // "g^A/g^X": Sequentially increment visually selected region. @@ -5920,61 +6179,9 @@ static void nv_g_cmd(cmdarg_T *cap) break; // "gv": Reselect the previous Visual area. If Visual already active, - // exchange previous and current Visual area. + // exchange previous and current Visual area. case 'v': - if (checkclearop(oap)) { - break; - } - - if (curbuf->b_visual.vi_start.lnum == 0 - || curbuf->b_visual.vi_start.lnum > curbuf->b_ml.ml_line_count - || curbuf->b_visual.vi_end.lnum == 0) { - beep_flush(); - } else { - // set w_cursor to the start of the Visual area, tpos to the end - if (VIsual_active) { - i = VIsual_mode; - VIsual_mode = curbuf->b_visual.vi_mode; - curbuf->b_visual.vi_mode = i; - curbuf->b_visual_mode_eval = i; - i = curwin->w_curswant; - curwin->w_curswant = curbuf->b_visual.vi_curswant; - curbuf->b_visual.vi_curswant = i; - - tpos = curbuf->b_visual.vi_end; - curbuf->b_visual.vi_end = curwin->w_cursor; - curwin->w_cursor = curbuf->b_visual.vi_start; - curbuf->b_visual.vi_start = VIsual; - } else { - VIsual_mode = curbuf->b_visual.vi_mode; - curwin->w_curswant = curbuf->b_visual.vi_curswant; - tpos = curbuf->b_visual.vi_end; - curwin->w_cursor = curbuf->b_visual.vi_start; - } - - VIsual_active = true; - VIsual_reselect = true; - - // Set Visual to the start and w_cursor to the end of the Visual - // area. Make sure they are on an existing character. - check_cursor(); - VIsual = curwin->w_cursor; - curwin->w_cursor = tpos; - check_cursor(); - update_topline(curwin); - // When called from normal "g" command: start Select mode when - // 'selectmode' contains "cmd". When called for K_SELECT, always - // start Select mode. - if (cap->arg) { - VIsual_select = true; - VIsual_select_reg = 0; - } else { - may_start_select('c'); - } - setmouse(); - redraw_curbuf_later(INVERTED); - showmode(); - } + nv_gv_cmd(cap); break; // "gV": Don't reselect the previous Visual area after a Select mode mapping of menu. case 'V': @@ -6040,47 +6247,14 @@ static void nv_g_cmd(cmdarg_T *cap) nv_join(cap); break; - // "g0", "g^" and "g$": Like "0", "^" and "$" but for screen lines. + // "g0", "g^" : Like "0" and "^" but for screen lines. // "gm": middle of "g0" and "g$". case '^': - flag = true; - FALLTHROUGH; - case '0': case 'm': case K_HOME: case K_KHOME: - oap->motion_type = kMTCharWise; - oap->inclusive = false; - if (curwin->w_p_wrap - && curwin->w_width_inner != 0) { - int width1 = curwin->w_width_inner - curwin_col_off(); - int width2 = width1 + curwin_col_off2(); - - validate_virtcol(); - i = 0; - if (curwin->w_virtcol >= (colnr_T)width1 && width2 > 0) { - i = (curwin->w_virtcol - width1) / width2 * width2 + width1; - } - } else { - i = curwin->w_leftcol; - } - // Go to the middle of the screen line. When 'number' or - // 'relativenumber' is on and lines are wrapping the middle can be more - // to the left. - if (cap->nchar == 'm') { - i += (curwin->w_width_inner - curwin_col_off() - + ((curwin->w_p_wrap && i > 0) - ? curwin_col_off2() : 0)) / 2; - } - coladvance((colnr_T)i); - if (flag) { - do { - i = gchar_cursor(); - } while (ascii_iswhite(i) && oneright()); - curwin->w_valid &= ~VALID_WCOL; - } - curwin->w_set_curswant = true; + nv_g_home_m_cmd(cap); break; case 'M': @@ -6095,94 +6269,17 @@ static void nv_g_cmd(cmdarg_T *cap) curwin->w_set_curswant = true; break; + // "g_": to the last non-blank character in the line or <count> lines downward. case '_': - // "g_": to the last non-blank character in the line or <count> lines downward. - cap->oap->motion_type = kMTCharWise; - cap->oap->inclusive = true; - curwin->w_curswant = MAXCOL; - if (cursor_down(cap->count1 - 1, - cap->oap->op_type == OP_NOP) == false) { - clearopbeep(cap->oap); - } else { - char_u *ptr = get_cursor_line_ptr(); - - // In Visual mode we may end up after the line. - if (curwin->w_cursor.col > 0 && ptr[curwin->w_cursor.col] == NUL) { - curwin->w_cursor.col--; - } - - // Decrease the cursor column until it's on a non-blank. - while (curwin->w_cursor.col > 0 - && ascii_iswhite(ptr[curwin->w_cursor.col])) { - curwin->w_cursor.col--; - } - curwin->w_set_curswant = true; - adjust_for_sel(cap); - } + nv_g_underscore_cmd(cap); break; + // "g$" : Like "$" but for screen lines. case '$': case K_END: - case K_KEND: { - int col_off = curwin_col_off(); - - oap->motion_type = kMTCharWise; - oap->inclusive = true; - if (curwin->w_p_wrap - && curwin->w_width_inner != 0) { - curwin->w_curswant = MAXCOL; // so we stay at the end - if (cap->count1 == 1) { - int width1 = curwin->w_width_inner - col_off; - int width2 = width1 + curwin_col_off2(); - - validate_virtcol(); - i = width1 - 1; - if (curwin->w_virtcol >= (colnr_T)width1) { - i += ((curwin->w_virtcol - width1) / width2 + 1) - * width2; - } - coladvance((colnr_T)i); - - // Make sure we stick in this column. - validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; - curwin->w_set_curswant = false; - if (curwin->w_cursor.col > 0 && curwin->w_p_wrap) { - // Check for landing on a character that got split at - // the end of the line. We do not want to advance to - // the next screen line. - if (curwin->w_virtcol > (colnr_T)i) { - curwin->w_cursor.col--; - } - } - } else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false) { - clearopbeep(oap); - } - } else { - if (cap->count1 > 1) { - // if it fails, let the cursor still move to the last char - (void)cursor_down(cap->count1 - 1, false); - } - i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1; - coladvance((colnr_T)i); - - // if the character doesn't fit move one back - if (curwin->w_cursor.col > 0 && utf_ptr2cells((const char *)get_cursor_pos_ptr()) > 1) { - colnr_T vcol; - - getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol); - if (vcol >= curwin->w_leftcol + curwin->w_width - col_off) { - curwin->w_cursor.col--; - } - } - - // Make sure we stick in this column. - validate_virtcol(); - curwin->w_curswant = curwin->w_virtcol; - curwin->w_set_curswant = false; - } - } - break; + case K_KEND: + nv_g_dollar_cmd(cap); + break; // "g*" and "g#", like "*" and "#" but without using "\<" and "\>" case '*': @@ -6213,19 +6310,7 @@ static void nv_g_cmd(cmdarg_T *cap) // "gi": start Insert at the last position. case 'i': - if (curbuf->b_last_insert.mark.lnum != 0) { - curwin->w_cursor = curbuf->b_last_insert.mark; - check_cursor_lnum(); - i = (int)STRLEN(get_cursor_line_ptr()); - if (curwin->w_cursor.col > (colnr_T)i) { - if (virtual_active()) { - curwin->w_cursor.coladd += curwin->w_cursor.col - i; - } - curwin->w_cursor.col = i; - } - } - cap->cmdchar = 'i'; - nv_edit(cap); + nv_gi_cmd(cap); break; // "gI": Start insert in column 1. @@ -6284,14 +6369,14 @@ static void nv_g_cmd(cmdarg_T *cap) nv_goto(cap); break; - // Two-character operators: - // "gq" Format text - // "gw" Format text and keep cursor position - // "g~" Toggle the case of the text. - // "gu" Change text to lower case. - // "gU" Change text to upper case. - // "g?" rot13 encoding - // "g@" call 'operatorfunc' + // Two-character operators: + // "gq" Format text + // "gw" Format text and keep cursor position + // "g~" Toggle the case of the text. + // "gu" Change text to lower case. + // "gU" Change text to upper case. + // "g?" rot13 encoding + // "g@" call 'operatorfunc' case 'q': case 'w': oap->cursor_start = curwin->w_cursor; @@ -6472,8 +6557,7 @@ static void nv_redo_or_register(cmdarg_T *cap) static void nv_Undo(cmdarg_T *cap) { // In Visual mode and typing "gUU" triggers an operator - if (cap->oap->op_type == OP_UPPER - || VIsual_active) { + if (cap->oap->op_type == OP_UPPER || VIsual_active) { // translate "gUU" to "gUgU" cap->cmdchar = 'g'; cap->nchar = 'U'; @@ -6488,9 +6572,7 @@ static void nv_Undo(cmdarg_T *cap) /// single character. static void nv_tilde(cmdarg_T *cap) { - if (!p_to - && !VIsual_active - && cap->oap->op_type != OP_TILDE) { + if (!p_to && !VIsual_active && cap->oap->op_type != OP_TILDE) { if (bt_prompt(curbuf) && !prompt_curpos_editable()) { clearopbeep(cap->oap); return; diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 2f29e94ff1..21ab26898e 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -6627,9 +6627,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) switch (oap->op_type) { case OP_LSHIFT: case OP_RSHIFT: - op_shift(oap, true, - oap->is_VIsual ? (int)cap->count1 : - 1); + op_shift(oap, true, oap->is_VIsual ? (int)cap->count1 : 1); auto_format(false, true); break; diff --git a/src/nvim/option.c b/src/nvim/option.c index bcaa4bb9b8..a3bd960fa5 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2366,6 +2366,69 @@ static bool valid_spellfile(const char_u *val) return true; } +/// Handle setting 'mousescroll'. +/// @return error message, NULL if it's OK. +static char *check_mousescroll(char *string) +{ + long vertical = -1; + long horizontal = -1; + + for (;;) { + char *end = vim_strchr(string, ','); + size_t length = end ? (size_t)(end - string) : STRLEN(string); + + // Both "ver:" and "hor:" are 4 bytes long. + // They should be followed by at least one digit. + if (length <= 4) { + return e_invarg; + } + + long *direction; + + if (memcmp(string, "ver:", 4) == 0) { + direction = &vertical; + } else if (memcmp(string, "hor:", 4) == 0) { + direction = &horizontal; + } else { + return e_invarg; + } + + // If the direction has already been set, this is a duplicate. + if (*direction != -1) { + return e_invarg; + } + + // Verify that only digits follow the colon. + for (size_t i = 4; i < length; i++) { + if (!ascii_isdigit(string[i])) { + return N_("E548: digit expected"); + } + } + + string += 4; + *direction = getdigits_int(&string, false, -1); + + // Num options are generally kept within the signed int range. + // We know this number won't be negative because we've already checked for + // a minus sign. We'll allow 0 as a means of disabling mouse scrolling. + if (*direction == -1) { + return e_invarg; + } + + if (!end) { + break; + } + + string = end + 1; + } + + // If a direction wasn't set, fallback to the default value. + p_mousescroll_vert = (vertical == -1) ? MOUSESCROLL_VERT_DFLT : vertical; + p_mousescroll_hor = (horizontal == -1) ? MOUSESCROLL_HOR_DFLT : horizontal; + + return NULL; +} + /// Handle string options that need some action to perform when changed. /// Returns NULL for success, or an error message for an error. /// @@ -2859,6 +2922,8 @@ ambw_end: if (check_opt_strings(p_mousem, p_mousem_values, false) != OK) { errmsg = e_invarg; } + } else if (varp == &p_mousescroll) { // 'mousescroll' + errmsg = check_mousescroll((char *)p_mousescroll); } else if (varp == &p_swb) { // 'switchbuf' if (opt_strings_flags(p_swb, p_swb_values, &swb_flags, true) != OK) { errmsg = e_invarg; diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index 531527ea3c..237288fbad 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -153,6 +153,11 @@ #define MOUSE_NONE ' ' // don't use Visual selection #define MOUSE_NONEF 'x' // forced modeless selection +// default vertical and horizontal mouse scroll values. +// Note: This should be in sync with the default mousescroll option. +#define MOUSESCROLL_VERT_DFLT 3 +#define MOUSESCROLL_HOR_DFLT 6 + #define COCU_ALL "nvic" // flags for 'concealcursor' /// characters for p_shm option: @@ -528,6 +533,9 @@ EXTERN long p_mls; // 'modelines' EXTERN char_u *p_mouse; // 'mouse' EXTERN char_u *p_mousem; // 'mousemodel' EXTERN int p_mousef; // 'mousefocus' +EXTERN char_u *p_mousescroll; // 'mousescroll' +EXTERN long p_mousescroll_vert INIT(= MOUSESCROLL_VERT_DFLT); +EXTERN long p_mousescroll_hor INIT(= MOUSESCROLL_HOR_DFLT); EXTERN long p_mouset; // 'mousetime' EXTERN int p_more; // 'more' EXTERN char_u *p_opfunc; // 'operatorfunc' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index a0fbf8d9f0..addc08d560 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1622,6 +1622,14 @@ return { defaults={if_true="extend"} }, { + full_name='mousescroll', + short_desc=N_("amount to scroll by when scrolling with a mouse"), + type='string', list='comma', scope={'global'}, + vi_def=true, + varname='p_mousescroll', + defaults={if_true="ver:3,hor:6"} + }, + { full_name='mouseshape', abbreviation='mouses', short_desc=N_("shape of the mouse pointer in different modes"), type='string', list='onecomma', scope={'global'}, diff --git a/src/nvim/search.c b/src/nvim/search.c index 15e8bd9e13..a915594e26 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -514,7 +514,7 @@ void last_pat_prog(regmmatch_T *regmatch) --emsg_off; } -/// lowest level search function. +/// Lowest level search function. /// Search for 'count'th occurrence of pattern "pat" in direction "dir". /// Start at position "pos" and return the found position in "pos". /// @@ -3991,8 +3991,7 @@ static int find_prev_quote(char_u *line, int col_start, int quotechar, char_u *e } if (n & 1) { col_start -= n; // uneven number of escape chars, skip it - } else if (line[col_start] == - quotechar) { + } else if (line[col_start] == quotechar) { break; } } @@ -4115,8 +4114,7 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) col_end = curwin->w_cursor.col; } } - } else if (line[col_start] == quotechar - || !vis_empty) { + } else if (line[col_start] == quotechar || !vis_empty) { int first_col = col_start; if (!vis_empty) { @@ -4185,9 +4183,8 @@ bool current_quote(oparg_T *oap, long count, bool include, int quotechar) // Set start position. After vi" another i" must include the ". // For v2i" include the quotes. - if (!include && count < 2 - && (vis_empty || !inside_quotes)) { - ++col_start; + if (!include && count < 2 && (vis_empty || !inside_quotes)) { + col_start++; } curwin->w_cursor.col = col_start; if (VIsual_active) { diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 82ba2cfd48..f146726b7b 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1422,9 +1422,16 @@ func Test_normal27_bracket() call assert_equal(5, line('.')) call assert_equal(3, col('.')) - " No mark after line 21, cursor moves to first non blank on current line + " No mark before line 1, cursor moves to first non-blank on current line + 1 + norm! 5|[' + call assert_equal(' 1 b', getline('.')) + call assert_equal(1, line('.')) + call assert_equal(3, col('.')) + + " No mark after line 21, cursor moves to first non-blank on current line 21 - norm! $]' + norm! 5|]' call assert_equal(' 21 b', getline('.')) call assert_equal(21, line('.')) call assert_equal(3, col('.')) @@ -1441,12 +1448,40 @@ func Test_normal27_bracket() call assert_equal(20, line('.')) call assert_equal(8, col('.')) + " No mark before line 1, cursor does not move + 1 + norm! 5|[` + call assert_equal(' 1 b', getline('.')) + call assert_equal(1, line('.')) + call assert_equal(5, col('.')) + + " No mark after line 21, cursor does not move + 21 + norm! 5|]` + call assert_equal(' 21 b', getline('.')) + call assert_equal(21, line('.')) + call assert_equal(5, col('.')) + + " Count too large for [` + " cursor moves to first lowercase mark + norm! 99[` + call assert_equal(' 1 b', getline('.')) + call assert_equal(1, line('.')) + call assert_equal(7, col('.')) + + " Count too large for ]` + " cursor moves to last lowercase mark + norm! 99]` + call assert_equal(' 20 b', getline('.')) + call assert_equal(20, line('.')) + call assert_equal(8, col('.')) + " clean up bw! endfunc +" Test for ( and ) sentence movements func Test_normal28_parenthesis() - " basic testing for ( and ) new call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here']) diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua index 8b469286ec..1eb76aa628 100644 --- a/test/functional/editor/mark_spec.lua +++ b/test/functional/editor/mark_spec.lua @@ -279,6 +279,27 @@ describe('named marks', function() -- should still be folded eq(2, funcs.foldclosed('.')) end) + + it("getting '{ '} '( ') does not move cursor", function() + meths.buf_set_lines(0, 0, 0, true, {'aaaaa', 'bbbbb', 'ccccc', 'ddddd', 'eeeee'}) + meths.win_set_cursor(0, {2, 0}) + funcs.getpos("'{") + eq({2, 0}, meths.win_get_cursor(0)) + funcs.getpos("'}") + eq({2, 0}, meths.win_get_cursor(0)) + funcs.getpos("'(") + eq({2, 0}, meths.win_get_cursor(0)) + funcs.getpos("')") + eq({2, 0}, meths.win_get_cursor(0)) + end) + + it('in command range does not move cursor #19248', function() + meths.create_user_command('Test', ':', {range = true}) + meths.buf_set_lines(0, 0, 0, true, {'aaaaa', 'bbbbb', 'ccccc', 'ddddd', 'eeeee'}) + meths.win_set_cursor(0, {2, 0}) + command([['{,'}Test]]) + eq({2, 0}, meths.win_get_cursor(0)) + end) end) describe('named marks view', function() diff --git a/test/functional/options/mousescroll_spec.lua b/test/functional/options/mousescroll_spec.lua new file mode 100644 index 0000000000..2c9b2d175e --- /dev/null +++ b/test/functional/options/mousescroll_spec.lua @@ -0,0 +1,151 @@ +local helpers = require('test.functional.helpers')(after_each) +local command = helpers.command +local clear = helpers.clear +local eval = helpers.eval +local eq = helpers.eq +local exc_exec = helpers.exc_exec +local feed = helpers.feed + +local scroll = function(direction) + return helpers.request('nvim_input_mouse', 'wheel', direction, '', 0, 2, 2) +end + +local screenrow = function() + return helpers.call('screenrow') +end + +local screencol = function() + return helpers.call('screencol') +end + +describe("'mousescroll'", function() + local invalid_arg = 'Vim(set):E474: Invalid argument: mousescroll=' + local digit_expected = 'Vim(set):E548: digit expected: mousescroll=' + + local function should_fail(val, errorstr) + eq(errorstr..val, exc_exec('set mousescroll='..val)) + end + + local function should_succeed(val) + eq(0, exc_exec('set mousescroll='..val)) + end + + before_each(function() + clear() + command('set nowrap lines=20 columns=20 virtualedit=all') + feed('100o<Esc>50G10|') + end) + + it('handles invalid values', function() + should_fail('', invalid_arg) -- empty string + should_fail('foo:123', invalid_arg) -- unknown direction + should_fail('hor:1,hor:2', invalid_arg) -- duplicate direction + should_fail('ver:99999999999999999999', invalid_arg) -- integer overflow + should_fail('ver:bar', digit_expected) -- expected digit + should_fail('ver:-1', digit_expected) -- negative count + end) + + it('handles valid values', function() + should_succeed('hor:1,ver:1') -- both directions set + should_succeed('hor:1') -- only horizontal + should_succeed('ver:1') -- only vertical + should_succeed('hor:0,ver:0') -- zero + should_succeed('hor:2147483647') -- large count + end) + + it('default set correctly', function() + eq('ver:3,hor:6', eval('&mousescroll')) + + eq(10, screenrow()) + scroll('up') + eq(13, screenrow()) + scroll('down') + eq(10, screenrow()) + + eq(10, screencol()) + scroll('right') + eq(4, screencol()) + scroll('left') + eq(10, screencol()) + end) + + it('vertical scrolling falls back to default value', function() + command('set mousescroll=hor:1') + eq(10, screenrow()) + scroll('up') + eq(13, screenrow()) + end) + + it('horizontal scrolling falls back to default value', function() + command('set mousescroll=ver:1') + eq(10, screencol()) + scroll('right') + eq(4, screencol()) + end) + + it('count of zero disables mouse scrolling', function() + command('set mousescroll=hor:0,ver:0') + + eq(10, screenrow()) + scroll('up') + eq(10, screenrow()) + scroll('down') + eq(10, screenrow()) + + eq(10, screencol()) + scroll('right') + eq(10, screencol()) + scroll('left') + eq(10, screencol()) + end) + + local test_vertical_scrolling = function() + eq(10, screenrow()) + + command('set mousescroll=ver:1') + scroll('up') + eq(11, screenrow()) + + command('set mousescroll=ver:2') + scroll('down') + eq(9, screenrow()) + + command('set mousescroll=ver:5') + scroll('up') + eq(14, screenrow()) + end + + it('controls vertical scrolling in normal mode', function() + test_vertical_scrolling() + end) + + it('controls vertical scrolling in insert mode', function() + feed('i') + test_vertical_scrolling() + end) + + local test_horizontal_scrolling = function() + eq(10, screencol()) + + command('set mousescroll=hor:1') + scroll('right') + eq(9, screencol()) + + command('set mousescroll=hor:3') + scroll('right') + eq(6, screencol()) + + command('set mousescroll=hor:2') + scroll('left') + eq(8, screencol()) + end + + it('controls horizontal scrolling in normal mode', function() + test_horizontal_scrolling() + end) + + it('controls horizontal scrolling in insert mode', function() + feed('i') + test_horizontal_scrolling() + end) +end) |