diff options
61 files changed, 1978 insertions, 714 deletions
diff --git a/runtime/doc/insert.txt b/runtime/doc/insert.txt index 3c5d246a49..a16d88b4e9 100644 --- a/runtime/doc/insert.txt +++ b/runtime/doc/insert.txt @@ -654,8 +654,10 @@ Note: The keys that are valid in CTRL-X mode are not mapped. This allows for ends CTRL-X mode (any key that is not a valid CTRL-X mode command) is mapped. Also, when doing completion with 'complete' mappings apply as usual. -Note: While completion is active Insert mode can't be used recursively. -Mappings that somehow invoke ":normal i.." will generate an E523 error. + *E565* +Note: While completion is active Insert mode can't be used recursively and +buffer text cannot be changed. Mappings that somehow invoke ":normal i.." +will generate an E565 error. The following mappings are suggested to make typing the completion commands a bit easier (although they will hide other commands): > diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt index 487ccdb0c5..a7be9ff98f 100644 --- a/runtime/doc/nvim_terminal_emulator.txt +++ b/runtime/doc/nvim_terminal_emulator.txt @@ -472,6 +472,20 @@ any window, using the TermDebugSendCommand() function. Example: > The argument is the gdb command. +Popup menu ~ + *termdebug_popup* + +By default the Termdebug plugin sets 'mousemodel' to "popup_setpos" and adds +these entries to the popup menu: + Set breakpoint `:Break` + Clear breakpoint `:Clear` + Evaluate `:Evaluate` +If you don't want this then disable it with: > + let g:termdebug_config['popup'] = 0 +or if there is no g:termdebug_config: > + let g:termdebug_popup = 0 + + Vim window width ~ *termdebug_wide* 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/runtime/filetype.lua b/runtime/filetype.lua index 35bb31edce..f1885a8b95 100644 --- a/runtime/filetype.lua +++ b/runtime/filetype.lua @@ -12,8 +12,14 @@ vim.api.nvim_create_augroup('filetypedetect', { clear = false }) vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, { group = 'filetypedetect', callback = function(args) - local ft, on_detect = vim.filetype.match({ buf = args.buf }) - if ft then + local ft, on_detect = vim.filetype.match({ filename = args.match, buf = args.buf }) + if not ft then + -- Generic configuration file used as fallback + ft = require('vim.filetype.detect').conf(args.file, args.buf) + if ft then + vim.api.nvim_cmd({ cmd = 'setf', args = { 'FALLBACK', ft } }, {}) + end + else vim.api.nvim_buf_set_option(args.buf, 'filetype', ft) if on_detect then on_detect(args.buf) diff --git a/runtime/filetype.vim b/runtime/filetype.vim index 0f67f45262..ba39bc3ae5 100644 --- a/runtime/filetype.vim +++ b/runtime/filetype.vim @@ -2388,7 +2388,7 @@ au BufNewFile,BufRead *fvwm2rc* au BufNewFile,BufRead */tmp/lltmp* call s:StarSetf('gedcom') " Git -au BufNewFile,BufRead */.gitconfig.d/*,/etc/gitconfig.d/* call s:StarSetf('gitconfig') +au BufNewFile,BufRead */.gitconfig.d/*,*/etc/gitconfig.d/* call s:StarSetf('gitconfig') " Gitolite au BufNewFile,BufRead */gitolite-admin/conf/* call s:StarSetf('gitolite') @@ -2453,7 +2453,7 @@ au BufNewFile,BufRead neomuttrc*,Neomuttrc* call s:StarSetf('neomuttrc') au BufNewFile,BufRead tmac.* call s:StarSetf('nroff') " OpenBSD hostname.if -au BufNewFile,BufRead /etc/hostname.* call s:StarSetf('config') +au BufNewFile,BufRead */etc/hostname.* call s:StarSetf('config') " Pam conf au BufNewFile,BufRead */etc/pam.d/* call s:StarSetf('pamconf') diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 2874ea45e7..df21fd46a1 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -17,7 +17,7 @@ local function starsetf(ft, opts) end end, { - -- Starset matches should always have lowest priority + -- Starset matches should have lowest priority by default priority = (opts and opts.priority) or -math.huge, }, } @@ -310,7 +310,7 @@ local extension = { end, ecd = 'ecd', edf = 'edif', - edfi = 'edif', + edif = 'edif', edo = 'edif', edn = function(path, bufnr) return require('vim.filetype.detect').edn(bufnr) @@ -425,7 +425,6 @@ local extension = { gleam = 'gleam', glsl = 'glsl', gpi = 'gnuplot', - gnuplot = 'gnuplot', go = 'go', gp = 'gp', gs = 'grads', @@ -1175,9 +1174,8 @@ local extension = { return require('vim.filetype.detect').sgml(bufnr) end, t = function(path, bufnr) - if not require('vim.filetype.detect').nroff(bufnr) and not require('vim.filetype.detect').perl(path, bufnr) then - return 'tads' - end + local nroff = require('vim.filetype.detect').nroff(bufnr) + return nroff or require('vim.filetype.detect').perl(path, bufnr) or 'tads' end, -- Ignored extensions bak = function(path, bufnr) @@ -1253,13 +1251,6 @@ local filename = { ['.arch-inventory'] = 'arch', ['GNUmakefile.am'] = 'automake', ['named.root'] = 'bindzone', - ['.*/bind/db%..*'] = starsetf('bindzone'), - ['.*/named/db%..*'] = starsetf('bindzone'), - ['cabal%.project%..*'] = starsetf('cabalproject'), - ['sgml%.catalog.*'] = starsetf('catalog'), - ['/etc/hostname%..*'] = starsetf('config'), - ['.*/etc/cron%.d/.*'] = starsetf('crontab'), - ['crontab%..*'] = starsetf('crontab'), WORKSPACE = 'bzl', BUILD = 'bzl', ['cabal.project'] = 'cabalproject', @@ -1305,9 +1296,6 @@ local filename = { ['/debian/copyright'] = 'debcopyright', ['/etc/apt/sources.list'] = 'debsources', ['denyhosts.conf'] = 'denyhosts', - ['.*/debian/patches/.*'] = function(path, bufnr) - return require('vim.filetype.detect').dep3patch(path, bufnr) - end, ['dict.conf'] = 'dictconf', ['.dictrc'] = 'dictconf', ['/etc/DIR_COLORS'] = 'dircolors', @@ -1366,6 +1354,7 @@ local filename = { ['.gnashpluginrc'] = 'gnash', gnashpluginrc = 'gnash', gnashrc = 'gnash', + ['.gnuplot'] = 'gnuplot', ['go.work'] = 'gowork', ['.gprc'] = 'gp', ['/.gnupg/gpg.conf'] = 'gpg', @@ -1514,7 +1503,6 @@ local filename = { ['.screenrc'] = 'screen', ['/etc/sensors3.conf'] = 'sensors', ['/etc/sensors.conf'] = 'sensors', - ['.*/etc/sensors%.d/[^.].*'] = starsetf('sensors'), ['/etc/services'] = 'services', ['/etc/serial.conf'] = 'setserial', ['/etc/udev/cdsymlinks.conf'] = 'sh', @@ -1560,13 +1548,15 @@ local filename = { ['.slrnrc'] = 'slrnrc', ['sendmail.cf'] = 'sm', ['squid.conf'] = 'squid', - ['/.ssh/config'] = 'sshconfig', ['ssh_config'] = 'sshconfig', ['sshd_config'] = 'sshdconfig', ['/etc/sudoers'] = 'sudoers', ['sudoers.tmp'] = 'sudoers', ['/etc/sysctl.conf'] = 'sysctl', tags = 'tags', + ['pending.data'] = 'taskdata', + ['completed.data'] = 'taskdata', + ['undo.data'] = 'taskdata', ['.tclshrc'] = 'tcl', ['.wishrc'] = 'tcl', ['tclsh.rc'] = 'tcl', @@ -1667,12 +1657,16 @@ local pattern = { return require('vim.filetype.detect').asm(bufnr) end, ['[mM]akefile%.am'] = 'automake', + ['.*/bind/db%..*'] = starsetf('bindzone'), + ['.*/named/db%..*'] = starsetf('bindzone'), ['.*bsd'] = 'bsdl', ['bzr_log%..*'] = 'bzr', ['.*enlightenment/.*%.cfg'] = 'c', + ['cabal%.project%..*'] = starsetf('cabalproject'), ['.*/%.calendar/.*'] = starsetf('calendar'), ['.*/share/calendar/.*/calendar%..*'] = starsetf('calendar'), ['.*/share/calendar/calendar%..*'] = starsetf('calendar'), + ['sgml%.catalog.*'] = starsetf('catalog'), ['.*/etc/defaults/cdrdao'] = 'cdrdaoconf', ['.*/etc/cdrdao%.conf'] = 'cdrdaoconf', ['.*/etc/default/cdrdao'] = 'cdrdaoconf', @@ -1681,10 +1675,13 @@ local pattern = { function(path, bufnr) return require('vim.filetype.detect').cfg(bufnr) end, - -- Decrease the priority to avoid conflicts with more specific patterns + -- Decrease priority to avoid conflicts with more specific patterns -- such as '.*/etc/a2ps/.*%.cfg', '.*enlightenment/.*%.cfg', etc. { priority = -1 }, }, + ['[cC]hange[lL]og.*'] = starsetf(function(path, bufnr) + require('vim.filetype.detect').changelog(bufnr) + end), ['.*%.%.ch'] = 'chill', ['.*%.cmake%.in'] = 'cmake', -- */cmus/rc and */.cmus/rc @@ -1693,6 +1690,9 @@ local pattern = { ['.*/%.?cmus/.*%.theme'] = 'cmusrc', ['.*/%.cmus/autosave'] = 'cmusrc', ['.*/%.cmus/command%-history'] = 'cmusrc', + ['.*/etc/hostname%..*'] = starsetf('config'), + ['crontab%..*'] = starsetf('crontab'), + ['.*/etc/cron%.d/.*'] = starsetf('crontab'), ['%.cshrc.*'] = function(path, bufnr) return require('vim.filetype.detect').csh(path, bufnr) end, @@ -1703,9 +1703,9 @@ local pattern = { ['.*%.[Dd][Aa][Tt]'] = function(path, bufnr) return require('vim.filetype.detect').dat(path, bufnr) end, - ['[cC]hange[lL]og.*'] = starsetf(function(path, bufnr) - require('vim.filetype.detect').changelog(bufnr) - end), + ['.*/debian/patches/.*'] = function(path, bufnr) + return require('vim.filetype.detect').dep3patch(path, bufnr) + end, ['.*/etc/dnsmasq%.d/.*'] = starsetf('dnsmasq'), ['Containerfile%..*'] = starsetf('dockerfile'), ['Dockerfile%..*'] = starsetf('dockerfile'), @@ -1716,6 +1716,8 @@ local pattern = { ['.*/debian/copyright'] = 'debcopyright', ['.*/etc/apt/sources%.list%.d/.*%.list'] = 'debsources', ['.*/etc/apt/sources%.list'] = 'debsources', + ['.*%.directory'] = 'desktop', + ['.*%.desktop'] = 'desktop', ['dictd.*%.conf'] = 'dictdconf', ['.*/etc/DIR_COLORS'] = 'dircolors', ['.*/etc/dnsmasq%.conf'] = 'dnsmasq', @@ -1766,14 +1768,19 @@ local pattern = { return require('vim.filetype.detect').fvwm(path) end), ['.*/tmp/lltmp.*'] = starsetf('gedcom'), - ['/etc/gitconfig%.d/.*'] = starsetf('gitconfig'), + ['.*/etc/gitconfig%.d/.*'] = starsetf('gitconfig'), ['.*/gitolite%-admin/conf/.*'] = starsetf('gitolite'), ['tmac%..*'] = starsetf('nroff'), ['.*/%.gitconfig%.d/.*'] = starsetf('gitconfig'), - ['.*%.git/.*'] = function(path, bufnr) - require('vim.filetype.detect').git(bufnr) - end, + ['.*%.git/.*'] = { + function(path, bufnr) + return require('vim.filetype.detect').git(bufnr) + end, + -- Decrease priority to run after simple pattern checks + { priority = -1 }, + }, ['.*%.git/modules/.*/config'] = 'gitconfig', + ['.*%.git/modules/config'] = 'gitconfig', ['.*%.git/config'] = 'gitconfig', ['.*/etc/gitconfig'] = 'gitconfig', ['.*/%.config/git/config'] = 'gitconfig', @@ -1859,6 +1866,83 @@ local pattern = { ['.*[mM]akefile'] = 'make', ['[mM]akefile.*'] = starsetf('make'), ['.*/etc/man%.conf'] = 'manconf', + ['.*/log/auth'] = 'messages', + ['.*/log/cron'] = 'messages', + ['.*/log/daemon'] = 'messages', + ['.*/log/debug'] = 'messages', + ['.*/log/kern'] = 'messages', + ['.*/log/lpr'] = 'messages', + ['.*/log/mail'] = 'messages', + ['.*/log/messages'] = 'messages', + ['.*/log/news/news'] = 'messages', + ['.*/log/syslog'] = 'messages', + ['.*/log/user'] = 'messages', + ['.*/log/auth%.log'] = 'messages', + ['.*/log/cron%.log'] = 'messages', + ['.*/log/daemon%.log'] = 'messages', + ['.*/log/debug%.log'] = 'messages', + ['.*/log/kern%.log'] = 'messages', + ['.*/log/lpr%.log'] = 'messages', + ['.*/log/mail%.log'] = 'messages', + ['.*/log/messages%.log'] = 'messages', + ['.*/log/news/news%.log'] = 'messages', + ['.*/log/syslog%.log'] = 'messages', + ['.*/log/user%.log'] = 'messages', + ['.*/log/auth%.err'] = 'messages', + ['.*/log/cron%.err'] = 'messages', + ['.*/log/daemon%.err'] = 'messages', + ['.*/log/debug%.err'] = 'messages', + ['.*/log/kern%.err'] = 'messages', + ['.*/log/lpr%.err'] = 'messages', + ['.*/log/mail%.err'] = 'messages', + ['.*/log/messages%.err'] = 'messages', + ['.*/log/news/news%.err'] = 'messages', + ['.*/log/syslog%.err'] = 'messages', + ['.*/log/user%.err'] = 'messages', + ['.*/log/auth%.info'] = 'messages', + ['.*/log/cron%.info'] = 'messages', + ['.*/log/daemon%.info'] = 'messages', + ['.*/log/debug%.info'] = 'messages', + ['.*/log/kern%.info'] = 'messages', + ['.*/log/lpr%.info'] = 'messages', + ['.*/log/mail%.info'] = 'messages', + ['.*/log/messages%.info'] = 'messages', + ['.*/log/news/news%.info'] = 'messages', + ['.*/log/syslog%.info'] = 'messages', + ['.*/log/user%.info'] = 'messages', + ['.*/log/auth%.warn'] = 'messages', + ['.*/log/cron%.warn'] = 'messages', + ['.*/log/daemon%.warn'] = 'messages', + ['.*/log/debug%.warn'] = 'messages', + ['.*/log/kern%.warn'] = 'messages', + ['.*/log/lpr%.warn'] = 'messages', + ['.*/log/mail%.warn'] = 'messages', + ['.*/log/messages%.warn'] = 'messages', + ['.*/log/news/news%.warn'] = 'messages', + ['.*/log/syslog%.warn'] = 'messages', + ['.*/log/user%.warn'] = 'messages', + ['.*/log/auth%.crit'] = 'messages', + ['.*/log/cron%.crit'] = 'messages', + ['.*/log/daemon%.crit'] = 'messages', + ['.*/log/debug%.crit'] = 'messages', + ['.*/log/kern%.crit'] = 'messages', + ['.*/log/lpr%.crit'] = 'messages', + ['.*/log/mail%.crit'] = 'messages', + ['.*/log/messages%.crit'] = 'messages', + ['.*/log/news/news%.crit'] = 'messages', + ['.*/log/syslog%.crit'] = 'messages', + ['.*/log/user%.crit'] = 'messages', + ['.*/log/auth%.notice'] = 'messages', + ['.*/log/cron%.notice'] = 'messages', + ['.*/log/daemon%.notice'] = 'messages', + ['.*/log/debug%.notice'] = 'messages', + ['.*/log/kern%.notice'] = 'messages', + ['.*/log/lpr%.notice'] = 'messages', + ['.*/log/mail%.notice'] = 'messages', + ['.*/log/messages%.notice'] = 'messages', + ['.*/log/news/news%.notice'] = 'messages', + ['.*/log/syslog%.notice'] = 'messages', + ['.*/log/user%.notice'] = 'messages', ['.*%.[Mm][Oo][Dd]'] = function(path, bufnr) return require('vim.filetype.detect').mod(path, bufnr) end, @@ -1937,6 +2021,7 @@ local pattern = { ['[rR]akefile.*'] = starsetf('ruby'), ['[rR]antfile'] = 'ruby', ['[rR]akefile'] = 'ruby', + ['.*/etc/sensors%.d/[^.].*'] = starsetf('sensors'), ['.*/etc/sensors%.conf'] = 'sensors', ['.*/etc/sensors3%.conf'] = 'sensors', ['.*/etc/services'] = 'services', @@ -1975,6 +2060,7 @@ local pattern = { ['.*/etc/slp%.spi'] = 'slpspi', ['.*/etc/ssh/ssh_config%.d/.*%.conf'] = 'sshconfig', ['.*/%.ssh/config'] = 'sshconfig', + ['.*/%.ssh/.*%.conf'] = 'sshconfig', ['.*/etc/ssh/sshd_config%.d/.*%.conf'] = 'sshdconfig', ['.*%.[Ss][Rr][Cc]'] = function(path, bufnr) return require('vim.filetype.detect').src(bufnr) @@ -2322,7 +2408,7 @@ function M.match(args) local ft, on_detect -- First check for the simple case where the full path exists as a key - local path = vim.fn.resolve(vim.fn.fnamemodify(name, ':p')) + local path = vim.fn.fnamemodify(name, ':p') ft, on_detect = dispatch(filename[path], path, bufnr) if ft then return ft, on_detect diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 37922b4ebf..fb36a502b0 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -187,6 +187,17 @@ function M.cls(bufnr) end end +function M.conf(path, bufnr) + if vim.fn.did_filetype() ~= 0 or path:find(vim.g.ft_ignore_pat) then + return + end + for _, line in ipairs(getlines(bufnr, 1, 5)) do + if line:find('^#') then + return 'conf' + end + end +end + -- Debian Control function M.control(bufnr) if getlines(bufnr, 1):find('^Source:') then @@ -256,8 +267,9 @@ local function cvs_diff(path, contents) end function M.dat(path, bufnr) + local file_name = vim.fn.fnamemodify(path, ':t'):lower() -- Innovation data processing - if findany(path:lower(), { '^upstream%.dat$', '^upstream%..*%.dat$', '^.*%.upstream%.dat$' }) then + if findany(file_name, { '^upstream%.dat$', '^upstream%..*%.dat$', '^.*%.upstream%.dat$' }) then return 'upstreamdat' end if vim.g.filetype_dat then @@ -458,7 +470,7 @@ end function M.git(bufnr) local line = getlines(bufnr, 1) - if line:find('^' .. string.rep('%x', 40) .. '+ ') or line:sub(1, 5) == 'ref: ' then + if matchregex(line, [[^\x\{40,\}\>\|^ref: ]]) then return 'git' end end @@ -568,7 +580,7 @@ function M.log(path) return 'upstreaminstalllog' elseif findany(path, { 'usserver%.log', 'usserver%..*%.log', '.*%.usserver%.log' }) then return 'usserverlog' - elseif findany(path, { 'usw2kagt%.log', 'usws2kagt%..*%.log', '.*%.usws2kagt%.log' }) then + elseif findany(path, { 'usw2kagt%.log', 'usw2kagt%..*%.log', '.*%.usw2kagt%.log' }) then return 'usw2kagtlog' end end @@ -759,8 +771,8 @@ end -- If the first line starts with '#' and contains 'perl' it's probably a Perl file. -- (Slow test) If a file contains a 'use' statement then it is almost certainly a Perl file. function M.perl(path, bufnr) - local dirname = vim.fn.expand(path, '%:p:h:t') - if vim.fn.expand(dirname, '%:e') == 't' and (dirname == 't' or dirname == 'xt') then + local dir_name = vim.fs.dirname(path) + if vim.fn.expand(path, '%:e') == 't' and (dir_name == 't' or dir_name == 'xt') then return 'perl' end local first_line = getlines(bufnr, 1) @@ -1323,6 +1335,7 @@ local patterns_hashbang = { ['fish\\>'] = { 'fish', { vim_regex = true } }, ['gforth\\>'] = { 'forth', { vim_regex = true } }, ['icon\\>'] = { 'icon', { vim_regex = true } }, + guile = 'scheme', } ---@private diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim index c4e90eb373..802ebd42b5 100644 --- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim +++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim @@ -37,7 +37,7 @@ " " For neovim compatibility, the vim specific calls were replaced with neovim " specific calls: -" term_start -> term_open +" term_start -> termopen " term_sendkeys -> chansend " term_getline -> getbufline " job_info && term_getjob -> using linux command ps to get the tty @@ -902,6 +902,26 @@ func s:InstallCommands() nnoremap K :Evaluate<CR> endif + if has('menu') && &mouse != '' + call s:InstallWinbar() + + let popup = 1 + if exists('g:termdebug_config') + let popup = get(g:termdebug_config, 'popup', 1) + elseif exists('g:termdebug_popup') + let popup = g:termdebug_popup + endif + if popup + let s:saved_mousemodel = &mousemodel + let &mousemodel = 'popup_setpos' + an 1.200 PopUp.-SEP3- <Nop> + an 1.210 PopUp.Set\ breakpoint :Break<CR> + an 1.220 PopUp.Clear\ breakpoint :Clear<CR> + an 1.230 PopUp.Run\ until :Until<CR> + an 1.240 PopUp.Evaluate :Evaluate<CR> + endif + endif + let &cpo = save_cpo endfunc @@ -956,6 +976,33 @@ func s:DeleteCommands() unlet s:k_map_saved endif + if has('menu') + " Remove the WinBar entries from all windows where it was added. + " let curwinid = win_getid(winnr()) + " for winid in s:winbar_winids + " if win_gotoid(winid) + " aunmenu WinBar.Step + " aunmenu WinBar.Next + " aunmenu WinBar.Finish + " aunmenu WinBar.Cont + " aunmenu WinBar.Stop + " aunmenu WinBar.Eval + " endif + " endfor + " call win_gotoid(curwinid) + " let s:winbar_winids = [] + + if exists('s:saved_mousemodel') + let &mousemodel = s:saved_mousemodel + unlet s:saved_mousemodel + aunmenu PopUp.-SEP3- + aunmenu PopUp.Set\ breakpoint + aunmenu PopUp.Clear\ breakpoint + aunmenu PopUp.Run\ until + aunmenu PopUp.Evaluate + endif + endif + call sign_unplace('TermDebug') unlet s:breakpoints unlet s:breakpoint_locations diff --git a/runtime/scripts.vim b/runtime/scripts.vim index 49e9e259a2..a129c3467e 100644 --- a/runtime/scripts.vim +++ b/runtime/scripts.vim @@ -206,6 +206,10 @@ if s:line1 =~# "^#!" elseif s:name =~# 'icon\>' set ft=icon + " Guile + elseif s:name =~# 'guile' + set ft=scheme + endif unlet s:name diff --git a/src/clint.py b/src/clint.py index 944946bd16..28f6031a57 100755 --- a/src/clint.py +++ b/src/clint.py @@ -651,6 +651,9 @@ def Error(filename, linenum, category, confidence, message): elif _cpplint_state.output_format == 'eclipse': sys.stdout.write('%s:%s: warning: %s [%s] [%d]\n' % ( filename, linenum, message, category, confidence)) + elif _cpplint_state.output_format == 'gh_action': + sys.stdout.write('::error file=%s,line=%s::%s [%s] [%d]\n' % ( + filename, linenum, message, category, confidence)) else: sys.stdout.write('%s:%s: %s [%s] [%d]\n' % ( filename, linenum, message, category, confidence)) @@ -3053,7 +3056,7 @@ def ParseArguments(args): if opt == '--help': PrintUsage(None) elif opt == '--output': - if val not in ('emacs', 'vs7', 'eclipse'): + if val not in ('emacs', 'vs7', 'eclipse', 'gh_action'): PrintUsage('The only allowed output formats are emacs,' ' vs7 and eclipse.') output_format = val diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index a0b8f16a39..de4af8e77b 100755 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -778,6 +778,12 @@ add_custom_command( add_download(${LINT_SUPPRESS_FILE} ${LINT_SUPPRESS_URL} off) +if(CI_BUILD) + set(LINT_OUTPUT_FORMAT gh_action) +else() + set(LINT_OUTPUT_FORMAT vs7) +endif() + set(LINT_NVIM_REL_SOURCES) foreach(sfile ${LINT_NVIM_SOURCES}) get_test_target("" "${sfile}" r suffix) @@ -787,7 +793,7 @@ foreach(sfile ${LINT_NVIM_SOURCES}) set(touch_file "${TOUCHES_DIR}/ran-clint-${suffix}") add_custom_command( OUTPUT ${touch_file} - COMMAND ${LINT_PRG} --suppress-errors=${suppress_file} ${rsfile} + COMMAND ${LINT_PRG} --suppress-errors=${suppress_file} --output=${LINT_OUTPUT_FORMAT} ${rsfile} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} COMMAND ${CMAKE_COMMAND} -E touch ${touch_file} DEPENDS ${LINT_PRG} ${sfile} ${LINT_SUPPRESSES_TOUCH_FILE} @@ -807,7 +813,7 @@ add_glob_targets( add_custom_target( lintcfull COMMAND - ${LINT_PRG} --suppress-errors=${LINT_SUPPRESS_FILE} ${LINT_NVIM_REL_SOURCES} + ${LINT_PRG} --suppress-errors=${LINT_SUPPRESS_FILE} --output=${LINT_OUTPUT_FORMAT} ${LINT_NVIM_REL_SOURCES} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} DEPENDS ${LINT_PRG} ${LINT_NVIM_SOURCES} ${LINT_SUPPRESS_FILE} lintuncrustify ) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 7bdb905dfa..7e1eae9632 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -89,6 +89,7 @@ static char *msg_loclist = N_("[Location List]"); static char *msg_qflist = N_("[Quickfix List]"); static char *e_auabort = N_("E855: Autocommands caused command to abort"); +static char *e_buflocked = N_("E937: Attempt to delete a buffer that is in use"); // Number of times free_buffer() was called. static int buf_free_count = 0; @@ -438,7 +439,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i // Disallow deleting the buffer when it is locked (already being closed or // halfway a command that relies on it). Unloading is allowed. if (buf->b_locked > 0 && (del_buf || wipe_buf)) { - emsg(_("E937: Attempt to delete a buffer that is in use")); + emsg(_(e_buflocked)); return false; } @@ -529,7 +530,9 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i } if (buf->terminal) { + buf->b_locked++; terminal_close(&buf->terminal, -1); + buf->b_locked--; } // Always remove the buffer when there is no file name. @@ -1182,6 +1185,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit) if (unload) { int forward; bufref_T bufref; + if (buf->b_locked) { + emsg(_(e_buflocked)); + return FAIL; + } set_bufref(&bufref, buf); // When unloading or deleting a buffer that's already unloaded and @@ -1956,11 +1963,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) return OK; } - if (text_locked()) { - text_locked_msg(); - return FAIL; - } - if (curbuf_locked()) { + if (text_or_buf_locked()) { return FAIL; } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index d21583e951..2abe9068eb 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -1409,14 +1409,9 @@ bool edit(int cmdchar, bool startln, long count) // Don't allow changes in the buffer while editing the cmdline. The // caller of getcmdline() may get confused. - if (textlock != 0) { - emsg(_(e_secure)); - return false; - } - // Don't allow recursive insert mode when busy with completion. - if (compl_started || compl_busy || pum_visible()) { - emsg(_(e_secure)); + if (textlock != 0 || compl_started || compl_busy || pum_visible()) { + emsg(_(e_textlock)); return false; } @@ -3966,6 +3961,8 @@ static void expand_by_function(int type, char_u *base) pos = curwin->w_cursor; curwin_save = curwin; curbuf_save = curbuf; + // Lock the text to avoid weird things from happening. + textlock++; // Call a function, which returns a list or dict. if (call_vim_function((char *)funcname, 2, args, &rettv) == OK) { @@ -3984,6 +3981,7 @@ static void expand_by_function(int type, char_u *base) break; } } + textlock--; if (curwin_save != curwin || curbuf_save != curbuf) { emsg(_(e_complwin)); @@ -7330,7 +7328,7 @@ static void replace_do_bs(int limit_col) } /// Check that C-indenting is on. -static bool cindent_on(void) +bool cindent_on(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return !p_paste && (curbuf->b_p_cin || *curbuf->b_p_inde != NUL); @@ -7935,13 +7933,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(); @@ -8373,7 +8366,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) } // delete characters until we are at or before want_vcol - while (vcol > want_vcol + while (vcol > want_vcol && curwin->w_cursor.col > 0 && (cc = *(get_cursor_pos_ptr() - 1), ascii_iswhite(cc))) { ins_bs_one(&vcol); } @@ -8557,14 +8550,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/eval/funcs.c b/src/nvim/eval/funcs.c index 8d267f6a9e..2775fd4778 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1062,6 +1062,11 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + const int save_textlock = textlock; + // "textlock" is set when evaluating 'completefunc' but we can change text + // here. + textlock = 0; + // Check for undo allowed here, because if something was already inserted // the line was already saved for undo and this check isn't done. if (!undo_allowed(curbuf)) { @@ -1070,15 +1075,13 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_LIST) { emsg(_(e_invarg)); - return; - } - - const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); - if (startcol <= 0) { - return; + } else { + const colnr_T startcol = tv_get_number_chk(&argvars[0], NULL); + if (startcol > 0) { + set_completion(startcol - 1, argvars[1].vval.v_list); + } } - - set_completion(startcol - 1, argvars[1].vval.v_list); + textlock = save_textlock; } /// "complete_add()" function diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 43d57cb278..d4fe55392e 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -2904,9 +2904,16 @@ int checkforcmd(char **pp, char *cmd, int len) /// invisible otherwise. static void append_command(char *cmd) { + size_t len = STRLEN(IObuff); char *s = cmd; char *d; + if (len > IOSIZE - 100) { + // Not enough space, truncate and put in "...". + d = (char *)IObuff + IOSIZE - 100; + d -= utf_head_off(IObuff, (const char_u *)d); + STRCPY(d, "..."); + } STRCAT(IObuff, ": "); d = (char *)IObuff + STRLEN(IObuff); while (*s != NUL && (char_u *)d - IObuff + 5 < IOSIZE) { @@ -4345,13 +4352,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/ex_getln.c b/src/nvim/ex_getln.c index f8c9e1f3ea..7e22ed55cb 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -2722,10 +2722,21 @@ char *get_text_locked_msg(void) if (cmdwin_type != 0) { return e_cmdwin; } else { - return e_secure; + return e_textlock; } } +/// Check for text, window or buffer locked. +/// Give an error message and return true if something is locked. +bool text_or_buf_locked(void) +{ + if (text_locked()) { + text_locked_msg(); + return true; + } + return curbuf_locked(); +} + /// Check if "curbuf->b_ro_locked" or "allbuf_lock" is set and /// return true when it is and give an error message. bool curbuf_locked(void) @@ -6600,9 +6611,9 @@ static int open_cmdwin(void) bool save_exmode = exmode_active; int save_cmdmsg_rl = cmdmsg_rl; + // Can't do this when text or buffer is locked. // Can't do this recursively. Can't do it when typing a password. - if (cmdwin_type != 0 - || cmdline_star > 0) { + if (text_or_buf_locked() || cmdwin_type != 0 || cmdline_star > 0) { beep_flush(); return K_IGNORE; } diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index 0f7052d351..4cf282770d 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -292,7 +292,7 @@ for i = 1, #functions do if fn.check_textlock then output:write('\n if (textlock != 0) {') - output:write('\n api_set_error(error, kErrorTypeException, "%s", e_secure);') + output:write('\n api_set_error(error, kErrorTypeException, "%s", e_textlock);') output:write('\n goto cleanup;') output:write('\n }\n') end @@ -435,7 +435,7 @@ local function process_function(fn) if fn.check_textlock then write_shifted_output(output, [[ if (textlock != 0) { - api_set_error(&err, kErrorTypeException, "%s", e_secure); + api_set_error(&err, kErrorTypeException, "%s", e_textlock); goto exit_0; } ]]) diff --git a/src/nvim/globals.h b/src/nvim/globals.h index f3f60ebfb7..0281eebcee 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -509,7 +509,7 @@ EXTERN int full_screen INIT(= false); /// .vimrc in current directory. EXTERN int secure INIT(= 0); -/// Non-zero when changing text and jumping to another window/buffer is not +/// Non-zero when changing text and jumping to another window or editing another buffer is not /// allowed. EXTERN int textlock INIT(= 0); @@ -955,6 +955,7 @@ EXTERN char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, EXTERN char e_readerrf[] INIT(= N_("E47: Error while reading errorfile")); EXTERN char e_sandbox[] INIT(= N_("E48: Not allowed in sandbox")); EXTERN char e_secure[] INIT(= N_("E523: Not allowed here")); +EXTERN char e_textlock[] INIT(= N_("E565: Not allowed to change text or change window")); EXTERN char e_screenmode[] INIT(= N_("E359: Screen mode setting not supported")); EXTERN char e_scroll[] INIT(= N_("E49: Invalid scroll size")); EXTERN char e_shellempty[] INIT(= N_("E91: 'shell' option is empty")); diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 9d60cf9dfe..271498d41a 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -656,6 +656,9 @@ int get_lisp_indent(void) } } } + if (*that == NUL) { + break; + } } if ((*that == '(') || (*that == '[')) { parencount++; diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 2402ea3035..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; } @@ -607,6 +601,8 @@ void mark_view_restore(fmark_T *fm) { if (fm != NULL && fm->view.topline_offset >= 0) { linenr_T topline = fm->mark.lnum - fm->view.topline_offset; + // If the mark does not have a view, topline_offset is MAXLNUM, + // and this check can prevent restoring mark view in that case. if (topline >= 1) { set_topline(curwin, topline); } diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h index 16d85a6e51..a78056c5f9 100644 --- a/src/nvim/mark_defs.h +++ b/src/nvim/mark_defs.h @@ -64,9 +64,10 @@ typedef enum { /// Represents view in which the mark was created typedef struct fmarkv { linenr_T topline_offset; ///< Amount of lines from the mark lnum to the top of the window. + ///< Use MAXLNUM to indicate that the mark does not have a view. } fmarkv_T; -#define INIT_FMARKV { 0 } +#define INIT_FMARKV { MAXLNUM } /// Structure defining single local mark typedef struct filemark { diff --git a/src/nvim/match.c b/src/nvim/match.c index 54bd3066ab..d5e3d8cddc 100644 --- a/src/nvim/match.c +++ b/src/nvim/match.c @@ -494,10 +494,10 @@ static void next_search_hl(win_T *win, match_T *search_hl, match_T *shl, linenr_ shl->lnum += shl->rm.startpos[0].lnum; break; // useful match found } - - // Restore called_emsg for assert_fails(). - called_emsg = save_called_emsg; } + + // Restore called_emsg for assert_fails(). + called_emsg = save_called_emsg; } /// Advance to the match in window "wp" line "lnum" or past it. 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 c7f7b569e7..9696130070 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -432,6 +432,18 @@ static int find_command(int cmdchar) return idx; } +/// If currently editing a cmdline or text is locked: beep and give an error +/// message, return true. +static bool check_text_locked(oparg_T *oap) +{ + if (text_locked()) { + clearopbeep(oap); + text_locked_msg(); + return true; + } + return false; +} + /// Normal state entry point. This is called on: /// /// - Startup, In this case the function never returns. @@ -559,6 +571,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 +912,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); } @@ -1079,15 +1091,9 @@ static int normal_execute(VimState *state, int key) goto finish; } - if (text_locked() && (nv_cmds[s->idx].cmd_flags & NV_NCW)) { - // This command is not allowed while editing a cmdline: beep. - clearopbeep(&s->oa); - text_locked_msg(); - s->command_finished = true; - goto finish; - } - - if ((nv_cmds[s->idx].cmd_flags & NV_NCW) && curbuf_locked()) { + if ((nv_cmds[s->idx].cmd_flags & NV_NCW) + && (check_text_locked(&s->oa) || curbuf_locked())) { + // this command is not allowed now s->command_finished = true; goto finish; } @@ -2078,8 +2084,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) { @@ -2494,21 +2499,30 @@ static void prep_redo_cmd(cmdarg_T *cap) /// Note that only the last argument can be a multi-byte char. void prep_redo(int regname, long num, int cmd1, int cmd2, int cmd3, int cmd4, int cmd5) { + prep_redo_num2(regname, num, cmd1, cmd2, 0L, cmd3, cmd4, cmd5); +} + +/// Prepare for redo of any command with extra count after "cmd2". +void prep_redo_num2(int regname, long num1, int cmd1, int cmd2, long num2, int cmd3, int cmd4, + int cmd5) +{ ResetRedobuff(); if (regname != 0) { // yank from specified buffer AppendCharToRedobuff('"'); AppendCharToRedobuff(regname); } - if (num) { - AppendNumberToRedobuff(num); + if (num1 != 0) { + AppendNumberToRedobuff(num1); } - if (cmd1 != NUL) { AppendCharToRedobuff(cmd1); } if (cmd2 != NUL) { AppendCharToRedobuff(cmd2); } + if (num2 != 0) { + AppendNumberToRedobuff(num2); + } if (cmd3 != NUL) { AppendCharToRedobuff(cmd3); } @@ -2537,8 +2551,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); @@ -3383,8 +3396,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 { @@ -3465,6 +3478,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) { @@ -3473,47 +3588,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. @@ -3863,60 +3944,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)) { @@ -4111,6 +4146,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 @@ -4190,48 +4288,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; @@ -4463,7 +4522,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 @@ -4476,20 +4534,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) @@ -4522,7 +4579,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(); @@ -4653,9 +4710,7 @@ static void nv_gotofile(cmdarg_T *cap) char_u *ptr; linenr_T lnum = -1; - if (text_locked()) { - clearopbeep(cap->oap); - text_locked_msg(); + if (check_text_locked(cap->oap)) { return; } if (curbuf_locked()) { @@ -4838,18 +4893,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; @@ -4865,7 +5033,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; @@ -4898,108 +5066,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 "[[" @@ -5026,19 +5093,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); @@ -5874,13 +5941,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. @@ -5911,61 +6183,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': @@ -6031,47 +6251,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': @@ -6086,84 +6273,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); - - // 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 '*': @@ -6194,19 +6314,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. @@ -6265,14 +6373,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; @@ -6329,13 +6437,7 @@ static void nv_g_cmd(cmdarg_T *cap) // "gQ": improved Ex mode case 'Q': - if (text_locked()) { - clearopbeep(cap->oap); - text_locked_msg(); - break; - } - - if (!checkclearopq(oap)) { + if (!check_text_locked(cap->oap) && !checkclearopq(oap)) { do_exmode(); } break; @@ -6453,8 +6555,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'; @@ -6469,9 +6570,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 a8198cfce9..21ab26898e 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -4421,6 +4421,7 @@ void format_lines(linenr_T line_count, int avoid_fex) int smd_save; long count; bool need_set_indent = true; // set indent of next paragraph + linenr_T first_line = curwin->w_cursor.lnum; bool force_format = false; const int old_State = State; @@ -4547,9 +4548,24 @@ void format_lines(linenr_T line_count, int avoid_fex) */ if (is_end_par || force_format) { if (need_set_indent) { - // replace indent in first line with minimal number of - // tabs and spaces, according to current options - (void)set_indent(get_indent(), SIN_CHANGED); + int indent = 0; // amount of indent needed + + // Replace indent in first line of a paragraph with minimal + // number of tabs and spaces, according to current options. + // For the very first formatted line keep the current + // indent. + if (curwin->w_cursor.lnum == first_line) { + indent = get_indent(); + } else if (curbuf->b_p_lisp) { + indent = get_lisp_indent(); + } else { + if (cindent_on()) { + indent = *curbuf->b_p_inde != NUL ? get_expr_indent() : get_c_indent(); + } else { + indent = get_indent(); + } + } + (void)set_indent(indent, SIN_CHANGED); } // put cursor on last non-space @@ -6218,6 +6234,15 @@ static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial) oap->start = curwin->w_cursor; } +/// Information for redoing the previous Visual selection. +typedef struct { + int rv_mode; ///< 'v', 'V', or Ctrl-V + linenr_T rv_line_count; ///< number of lines + colnr_T rv_vcol; ///< number of cols or end column + long rv_count; ///< count for Visual operator + int rv_arg; ///< extra argument +} redo_VIsual_T; + /// Handle an operator after Visual mode or when the movement is finished. /// "gui_yank" is true when yanking text for the clipboard. void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) @@ -6229,11 +6254,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) int lbr_saved = curwin->w_p_lbr; // The visual area is remembered for redo - static int redo_VIsual_mode = NUL; // 'v', 'V', or Ctrl-V - static linenr_T redo_VIsual_line_count; // number of lines - static colnr_T redo_VIsual_vcol; // number of cols or end column - static long redo_VIsual_count; // count for Visual operator - static int redo_VIsual_arg; // extra argument + static redo_VIsual_T redo_VIsual = { NUL, 0, 0, 0, 0 }; + bool include_line_break = false; old_cursor = curwin->w_cursor; @@ -6316,28 +6338,27 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) if (redo_VIsual_busy) { // Redo of an operation on a Visual area. Use the same size from - // redo_VIsual_line_count and redo_VIsual_vcol. + // redo_VIsual.rv_line_count and redo_VIsual.rv_vcol. oap->start = curwin->w_cursor; - curwin->w_cursor.lnum += redo_VIsual_line_count - 1; + curwin->w_cursor.lnum += redo_VIsual.rv_line_count - 1; if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; } - VIsual_mode = redo_VIsual_mode; - if (redo_VIsual_vcol == MAXCOL || VIsual_mode == 'v') { + VIsual_mode = redo_VIsual.rv_mode; + if (redo_VIsual.rv_vcol == MAXCOL || VIsual_mode == 'v') { if (VIsual_mode == 'v') { - if (redo_VIsual_line_count <= 1) { + if (redo_VIsual.rv_line_count <= 1) { validate_virtcol(); - curwin->w_curswant = - curwin->w_virtcol + redo_VIsual_vcol - 1; + curwin->w_curswant = curwin->w_virtcol + redo_VIsual.rv_vcol - 1; } else { - curwin->w_curswant = redo_VIsual_vcol; + curwin->w_curswant = redo_VIsual.rv_vcol; } } else { curwin->w_curswant = MAXCOL; } coladvance(curwin->w_curswant); } - cap->count0 = redo_VIsual_count; + cap->count0 = redo_VIsual.rv_count; cap->count1 = (cap->count0 == 0 ? 1 : cap->count0); } else if (VIsual_active) { if (!gui_yank) { @@ -6424,7 +6445,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) virtual_op = virtual_active(); if (VIsual_active || redo_VIsual_busy) { - get_op_vcol(oap, redo_VIsual_vcol, true); + get_op_vcol(oap, redo_VIsual.rv_vcol, true); if (!redo_VIsual_busy && !gui_yank) { // Prepare to reselect and redo Visual: this is based on the @@ -6469,6 +6490,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) get_op_char(oap->op_type), get_extra_op_char(oap->op_type), oap->motion_force, cap->cmdchar, cap->nchar); } else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND) { + int opchar = get_op_char(oap->op_type); + int extra_opchar = get_extra_op_char(oap->op_type); int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL; // reverse what nv_replace() did @@ -6477,15 +6500,20 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else if (nchar == REPLACE_NL_NCHAR) { nchar = NL; } - prep_redo(oap->regname, 0L, NUL, 'v', get_op_char(oap->op_type), - get_extra_op_char(oap->op_type), nchar); + + if (opchar == 'g' && extra_opchar == '@') { + // also repeat the count for 'operatorfunc' + prep_redo_num2(oap->regname, 0L, NUL, 'v', cap->count0, opchar, extra_opchar, nchar); + } else { + prep_redo(oap->regname, 0L, NUL, 'v', opchar, extra_opchar, nchar); + } } if (!redo_VIsual_busy) { - redo_VIsual_mode = resel_VIsual_mode; - redo_VIsual_vcol = resel_VIsual_vcol; - redo_VIsual_line_count = resel_VIsual_line_count; - redo_VIsual_count = cap->count0; - redo_VIsual_arg = cap->arg; + redo_VIsual.rv_mode = resel_VIsual_mode; + redo_VIsual.rv_vcol = resel_VIsual_vcol; + redo_VIsual.rv_line_count = resel_VIsual_line_count; + redo_VIsual.rv_count = cap->count0; + redo_VIsual.rv_arg = cap->arg; } } @@ -6599,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; @@ -6735,12 +6761,20 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) op_format(oap, true); // use internal function break; - case OP_FUNCTION: + case OP_FUNCTION: { + redo_VIsual_T save_redo_VIsual = redo_VIsual; + // Restore linebreak, so that when the user edits it looks as // before. curwin->w_p_lbr = lbr_saved; - op_function(oap); // call 'operatorfunc' + // call 'operatorfunc' + op_function(oap); + + // Restore the info for redoing Visual mode, the function may + // invoke another operator and unintentionally change it. + redo_VIsual = save_redo_VIsual; break; + } case OP_INSERT: case OP_APPEND: @@ -6821,7 +6855,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else { VIsual_active = true; curwin->w_p_lbr = lbr_saved; - op_addsub(oap, (linenr_T)cap->count1, redo_VIsual_arg); + op_addsub(oap, (linenr_T)cap->count1, redo_VIsual.rv_arg); VIsual_active = false; } check_cursor_col(); 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/spell.c b/src/nvim/spell.c index 0fedd27037..2aadc2258e 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -7023,8 +7023,9 @@ void spell_dump_compl(char_u *pat, int ic, Direction *dir, int dumpflags_arg) n = arridx[depth] + curi[depth]; ++curi[depth]; c = byts[n]; - if (c == 0) { - // End of word, deal with the word. + if (c == 0 || depth >= MAXWLEN - 1) { + // End of word or reached maximum length, deal with the + // word. // Don't use keep-case words in the fold-case tree, // they will appear in the keep-case tree. // Only use the word when the region matches. diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index f1092af358..dfcde37f62 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -30,6 +30,10 @@ set switchbuf= mapclear mapclear! +" Make "Q" switch to Ex mode. +" This does not work for all tests. +nnoremap Q gQ + " Prevent Nvim log from writing to stderr. let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 660801d575..fcef57e47a 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -169,7 +169,9 @@ func Test_autocmd_bufunload_avoiding_SEGV_01() exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!' augroup END - call assert_fails('edit bb.txt', 'E937:') + " Todo: check for E937 generated first + " call assert_fails('edit bb.txt', 'E937:') + call assert_fails('edit bb.txt', 'E517:') autocmd! test_autocmd_bufunload augroup! test_autocmd_bufunload @@ -540,7 +542,7 @@ func Test_three_windows() e Xtestje2 sp Xtestje1 call assert_fails('e', 'E937:') - call assert_equal('Xtestje2', expand('%')) + call assert_equal('Xtestje1', expand('%')) " Test changing buffers in a BufWipeout autocommand. If this goes wrong " there are ml_line errors and/or a Crash. @@ -563,7 +565,6 @@ func Test_three_windows() au! enew - bwipe! Xtestje1 call delete('Xtestje1') call delete('Xtestje2') call delete('Xtestje3') diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index 7ba8ef3397..ccc8168c09 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -5306,11 +5306,20 @@ func Test_cindent_case() set cindent norm! f:a: call assert_equal('case x:: // x', getline(1)) - set cindent& bwipe! endfunc +" Test for changing multiple lines (using c) with cindent +func Test_cindent_change_multline() + new + setlocal cindent + call setline(1, ['if (a)', '{', ' i = 1;', '}']) + normal! jc3jm = 2; + call assert_equal("\tm = 2;", getline(2)) + close! +endfunc + " This was reading past the end of the line func Test_cindent_check_funcdecl() new diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index aedcad5296..b00764e0dd 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -1226,6 +1226,16 @@ func Test_cmdline_expand_home() call delete('Xdir', 'rf') endfunc +" Test for normal mode commands not supported in the cmd window +func Test_cmdwin_blocked_commands() + call assert_fails('call feedkeys("q:\<C-T>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-]>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<C-^>\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:Q\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:Z\<CR>", "xt")', 'E11:') + call assert_fails('call feedkeys("q:\<F1>\<CR>", "xt")', 'E11:') +endfunc + " test that ";" works to find a match at the start of the first line func Test_zero_line_search() new @@ -1351,4 +1361,9 @@ func Test_recursive_register() call assert_equal('yes', caught) endfunc +func Test_long_error_message() + " the error should be truncated, not overrun IObuff + silent! norm Q00000000000000 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 275c8f7a15..19b088959b 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -270,6 +270,10 @@ func Test_edit_10() call cursor(1, 4) call feedkeys("A\<s-home>start\<esc>", 'txin') call assert_equal(['startdef', 'ghi'], getline(1, '$')) + " start select mode again with gv + set selectmode=cmd + call feedkeys('gvabc', 'xt') + call assert_equal('abctdef', getline(1)) set selectmode= keymodel= bw! endfunc @@ -981,6 +985,22 @@ func Test_edit_CTRL_U() bw! endfunc +func Test_edit_completefunc_delete() + func CompleteFunc(findstart, base) + if a:findstart == 1 + return col('.') - 1 + endif + normal dd + return ['a', 'b'] + endfunc + new + set completefunc=CompleteFunc + call setline(1, ['', 'abcd', '']) + 2d + call assert_fails("normal 2G$a\<C-X>\<C-U>", 'E565:') + bwipe! +endfunc + func Test_edit_CTRL_Z() " Ctrl-Z when insertmode is not set inserts it literally new @@ -1327,7 +1347,7 @@ func Test_edit_forbidden() try call feedkeys("ix\<esc>", 'tnix') call assert_fails(1, 'textlock') - catch /^Vim\%((\a\+)\)\=:E523/ " catch E523: not allowed here + catch /^Vim\%((\a\+)\)\=:E565/ " catch E565: not allowed here endtry " TODO: Might be a bug: should x really be inserted here call assert_equal(['xa'], getline(1, '$')) @@ -1352,7 +1372,7 @@ func Test_edit_forbidden() try call feedkeys("i\<c-x>\<c-u>\<esc>", 'tnix') call assert_fails(1, 'change in complete function') - catch /^Vim\%((\a\+)\)\=:E523/ " catch E523 + catch /^Vim\%((\a\+)\)\=:E565/ " catch E565 endtry delfu Complete set completefunc= diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 0ce333fa40..c3f7fb45f4 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -67,13 +67,13 @@ endfunc func Test_ex_mode_errors() " Not allowed to enter ex mode when text is locked au InsertCharPre <buffer> normal! gQ<CR> - let caught_e523 = 0 + let caught_e565 = 0 try call feedkeys("ix\<esc>", 'xt') - catch /^Vim\%((\a\+)\)\=:E523/ " catch E523 - let caught_e523 = 1 + catch /^Vim\%((\a\+)\)\=:E565/ " catch E565 + let caught_e565 = 1 endtry - call assert_equal(1, caught_e523) + call assert_equal(1, caught_e565) au! InsertCharPre new diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim index 7dde8a0439..b3052abb24 100644 --- a/src/nvim/testdir/test_excmd.vim +++ b/src/nvim/testdir/test_excmd.vim @@ -401,6 +401,30 @@ func Test_winsize_cmd() " Actually changing the window size would be flaky. endfunc +" Test for running Ex commands when text is locked. +" <C-\>e in the command line is used to lock the text +func Test_run_excmd_with_text_locked() + " :quit + let cmd = ":\<C-\>eexecute('quit')\<CR>\<C-C>" + call assert_fails("call feedkeys(cmd, 'xt')", 'E565:') + + " :qall + let cmd = ":\<C-\>eexecute('qall')\<CR>\<C-C>" + call assert_fails("call feedkeys(cmd, 'xt')", 'E565:') + + " :exit + let cmd = ":\<C-\>eexecute('exit')\<CR>\<C-C>" + call assert_fails("call feedkeys(cmd, 'xt')", 'E565:') + + " :close - should be ignored + new + let cmd = ":\<C-\>eexecute('close')\<CR>\<C-C>" + call assert_equal(2, winnr('$')) + close + + call assert_fails("call feedkeys(\":\<C-R>=execute('bnext')\<CR>\", 'xt')", 'E565:') +endfunc + func Test_not_break_expression_register() call setreg('=', '1+1') if 0 diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 8055c30cfc..5b3ed74d29 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -115,7 +115,7 @@ let s:filename_checks = { \ 'coco': ['file.atg'], \ 'conaryrecipe': ['file.recipe'], \ 'conf': ['auto.master'], - \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file'], + \ 'config': ['configure.in', 'configure.ac', '/etc/hostname.file', 'any/etc/hostname.file'], \ 'confini': ['/etc/pacman.conf', 'any/etc/pacman.conf', 'mpv.conf'], \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'], \ 'cook': ['file.cook'], @@ -208,7 +208,7 @@ let s:filename_checks = { \ 'gemtext': ['file.gmi', 'file.gemini'], \ 'gift': ['file.gift'], \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'], - \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'], + \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'], \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'], \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], @@ -705,7 +705,8 @@ let s:script_checks = { \ 'awk': [['#!/path/awk'], \ ['#!/path/gawk']], \ 'wml': [['#!/path/wml']], - \ 'scheme': [['#!/path/scheme']], + \ 'scheme': [['#!/path/scheme'], + \ ['#!/path/guile']], \ 'cfengine': [['#!/path/cfengine']], \ 'erlang': [['#!/path/escript']], \ 'haskell': [['#!/path/haskell']], diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim index 589899f532..7cb9daf59a 100644 --- a/src/nvim/testdir/test_gf.vim +++ b/src/nvim/testdir/test_gf.vim @@ -147,5 +147,20 @@ func Test_gf_error() call setline(1, '/doesnotexist') call assert_fails('normal gf', 'E447:') call assert_fails('normal gF', 'E447:') + call assert_fails('normal [f', 'E447:') + + " gf is not allowed when text is locked + au InsertCharPre <buffer> normal! gF<CR> + let caught_e565 = 0 + try + call feedkeys("ix\<esc>", 'xt') + catch /^Vim\%((\a\+)\)\=:E565/ " catch E565 + let caught_e565 = 1 + endtry + call assert_equal(1, caught_e565) + au! InsertCharPre + bwipe! endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_indent.vim b/src/nvim/testdir/test_indent.vim index 516b3fbdd1..3b5b643177 100644 --- a/src/nvim/testdir/test_indent.vim +++ b/src/nvim/testdir/test_indent.vim @@ -121,4 +121,159 @@ func Test_copyindent() close! endfunc +" Test for changing multiple lines with lisp indent +func Test_lisp_indent_change_multiline() + new + setlocal lisp autoindent + call setline(1, ['(if a', ' (if b', ' (return 5)))']) + normal! jc2j(return 4)) + call assert_equal(' (return 4))', getline(2)) + close! +endfunc + +func Test_lisp_indent() + new + setlocal lisp autoindent + call setline(1, ['(if a', ' ;; comment', ' \ abc', '', ' " str1\', ' " st\b', ' (return 5)']) + normal! jo;; comment + normal! jo\ abc + normal! jo;; ret + normal! jostr1" + normal! jostr2" + call assert_equal([' ;; comment', ' ;; comment', ' \ abc', ' \ abc', '', ' ;; ret', ' " str1\', ' str1"', ' " st\b', ' str2"'], getline(2, 11)) + close! +endfunc + +func Test_lisp_indent_quoted() + " This was going past the end of the line + new + setlocal lisp autoindent + call setline(1, ['"[', '=']) + normal Gvk= + + bwipe! +endfunc + +" Test for setting the 'indentexpr' from a modeline +func Test_modeline_indent_expr() + let modeline = &modeline + set modeline + func GetIndent() + return line('.') * 2 + endfunc + call writefile(['# vim: indentexpr=GetIndent()'], 'Xfile.txt') + set modelineexpr + new Xfile.txt + call assert_equal('GetIndent()', &indentexpr) + exe "normal Oa\nb\n" + call assert_equal([' a', ' b'], getline(1, 2)) + + set modelineexpr& + delfunc GetIndent + let &modeline = modeline + close! + call delete('Xfile.txt') +endfunc + +func Test_indent_func_with_gq() + + function GetTeXIndent() + " Sample indent expression for TeX files + let lnum = prevnonblank(v:lnum - 1) + " At the start of the file use zero indent. + if lnum == 0 + return 0 + endif + let line = getline(lnum) + let ind = indent(lnum) + " Add a 'shiftwidth' after beginning of environments. + if line =~ '\\begin{center}' + let ind = ind + shiftwidth() + endif + return ind + endfunction + + new + setl et sw=2 sts=2 ts=2 tw=50 indentexpr=GetTeXIndent() + put =[ '\documentclass{article}', '', '\begin{document}', '', + \ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce ut enim non', + \ 'libero efficitur aliquet. Maecenas metus justo, facilisis convallis blandit', + \ 'non, semper eu urna. Suspendisse diam diam, iaculis faucibus lorem eu,', + \ 'fringilla condimentum lectus. Quisque euismod diam at convallis vulputate.', + \ 'Pellentesque laoreet tortor sit amet mauris euismod ornare. Sed varius', + \ 'bibendum orci vel vehicula. Pellentesque tempor, ipsum et auctor accumsan,', + \ 'metus lectus ultrices odio, sed elementum mi ante at arcu.', '', '\begin{center}', '', + \ 'Proin nec risus consequat nunc dapibus consectetur. Mauris lacinia est a augue', + \ 'tristique accumsan. Morbi pretium, felis molestie eleifend condimentum, arcu', + \ 'ipsum congue nisl, quis euismod purus libero in ante.', '', + \ 'Donec id semper purus.', + \ 'Suspendisse eget aliquam nunc. Maecenas fringilla mauris vitae maximus', + \ 'condimentum. Cras a quam in mi dictum eleifend at a lorem. Sed convallis', + \ 'ante a commodo facilisis. Nam suscipit vulputate odio, vel dapibus nisl', + \ 'dignissim facilisis. Vestibulum ante ipsum primis in faucibus orci luctus et', + \ 'ultrices posuere cubilia curae;', '', ''] + 1d_ + call cursor(5, 1) + ka + call cursor(14, 1) + kb + norm! 'agqap + norm! 'bgqG + let expected = [ '\documentclass{article}', '', '\begin{document}', '', + \ 'Lorem ipsum dolor sit amet, consectetur adipiscing', + \ 'elit. Fusce ut enim non libero efficitur aliquet.', + \ 'Maecenas metus justo, facilisis convallis blandit', + \ 'non, semper eu urna. Suspendisse diam diam,', + \ 'iaculis faucibus lorem eu, fringilla condimentum', + \ 'lectus. Quisque euismod diam at convallis', + \ 'vulputate. Pellentesque laoreet tortor sit amet', + \ 'mauris euismod ornare. Sed varius bibendum orci', + \ 'vel vehicula. Pellentesque tempor, ipsum et auctor', + \ 'accumsan, metus lectus ultrices odio, sed', + \ 'elementum mi ante at arcu.', '', '\begin{center}', '', + \ ' Proin nec risus consequat nunc dapibus', + \ ' consectetur. Mauris lacinia est a augue', + \ ' tristique accumsan. Morbi pretium, felis', + \ ' molestie eleifend condimentum, arcu ipsum congue', + \ ' nisl, quis euismod purus libero in ante.', + \ '', + \ ' Donec id semper purus. Suspendisse eget aliquam', + \ ' nunc. Maecenas fringilla mauris vitae maximus', + \ ' condimentum. Cras a quam in mi dictum eleifend', + \ ' at a lorem. Sed convallis ante a commodo', + \ ' facilisis. Nam suscipit vulputate odio, vel', + \ ' dapibus nisl dignissim facilisis. Vestibulum', + \ ' ante ipsum primis in faucibus orci luctus et', + \ ' ultrices posuere cubilia curae;', '', ''] + call assert_equal(expected, getline(1, '$')) + + bwipe! + delmark ab + delfunction GetTeXIndent +endfu + +func Test_formatting_keeps_first_line_indent() + let lines =<< trim END + foo() + { + int x; // manually positioned + // more text that will be formatted + // but not reindented + END + new + call setline(1, lines) + setlocal sw=4 cindent tw=45 et + normal! 4Ggqj + let expected =<< trim END + foo() + { + int x; // manually positioned + // more text that will be + // formatted but not + // reindented + END + call assert_equal(expected, getline(1, '$')) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index b44d65c2a4..f146726b7b 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -115,8 +115,8 @@ func Test_normal01_keymodel() bw! endfunc +" Test for select mode func Test_normal02_selectmode() - " some basic select mode tests call Setup_NewWindow() 50 norm! gHy @@ -345,7 +345,7 @@ func Test_normal08_fold() bw! endfunc -func Test_normal09_operatorfunc() +func Test_normal09a_operatorfunc() " Test operatorfunc call Setup_NewWindow() " Add some spaces for counting @@ -375,7 +375,7 @@ func Test_normal09_operatorfunc() bw! endfunc -func Test_normal09a_operatorfunc() +func Test_normal09b_operatorfunc() " Test operatorfunc call Setup_NewWindow() " Add some spaces for counting @@ -397,10 +397,45 @@ func Test_normal09a_operatorfunc() " clean up unmap <buffer> ,, set opfunc= + call assert_fails('normal Vg@', 'E774:') bw! unlet! g:opt endfunc +func OperatorfuncRedo(_) + let g:opfunc_count = v:count +endfunc + +func Underscorize(_) + normal! '[V']r_ +endfunc + +func Test_normal09c_operatorfunc() + " Test redoing operatorfunc + new + call setline(1, 'some text') + set operatorfunc=OperatorfuncRedo + normal v3g@ + call assert_equal(3, g:opfunc_count) + let g:opfunc_count = 0 + normal . + call assert_equal(3, g:opfunc_count) + + bw! + unlet g:opfunc_count + + " Test redoing Visual mode + set operatorfunc=Underscorize + new + call setline(1, ['first', 'first', 'third', 'third', 'second']) + normal! 1GVjg@ + normal! 5G. + normal! 3G. + call assert_equal(['_____', '_____', '_____', '_____', '______'], getline(1, '$')) + bwipe! + set operatorfunc= +endfunc + func Test_normal10_expand() " Test for expand() 10new @@ -456,8 +491,12 @@ func Test_normal12_nv_error() 10new call setline(1, range(1,5)) " should not do anything, just beep - exe "norm! <c-k>" + call assert_beeps('exe "norm! <c-k>"') call assert_equal(map(range(1,5), 'string(v:val)'), getline(1,'$')) + call assert_beeps('normal! G2dd') + call assert_beeps("normal! g\<C-A>") + call assert_beeps("normal! g\<C-X>") + call assert_beeps("normal! g\<C-B>") bw! endfunc @@ -1383,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('.')) @@ -1402,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']) @@ -1654,6 +1728,14 @@ fun! Test_normal30_changecase() throw 'Skipped: Turkish locale not available' endtry + call setline(1, ['aaaaaa', 'aaaaaa']) + normal! gg10~ + call assert_equal(['AAAAAA', 'aaaaaa'], getline(1, 2)) + set whichwrap+=~ + normal! gg10~ + call assert_equal(['aaaaaa', 'AAAAaa'], getline(1, 2)) + set whichwrap& + " clean up bw! endfunc @@ -1683,8 +1765,8 @@ fun! Test_normal31_r_cmd() bw! endfunc +" Test for g*, g# func Test_normal32_g_cmd1() - " Test for g*, g# new call append(0, ['abc.x_foo', 'x_foobar.abc']) 1 @@ -1699,11 +1781,12 @@ func Test_normal32_g_cmd1() bw! endfunc +" Test for g`, g;, g,, g&, gv, gk, gj, gJ, g0, g^, g_, gm, g$, gM, g CTRL-G, +" gi and gI commands fun! Test_normal33_g_cmd2() if !has("jumplist") return endif - " Tests for g cmds call Setup_NewWindow() " Test for g` clearjumps @@ -1715,6 +1798,10 @@ fun! Test_normal33_g_cmd2() call assert_equal('>', a[-1:]) call assert_equal(1, line('.')) call assert_equal('1', getline('.')) + call cursor(10, 1) + norm! g'a + call assert_equal('>', a[-1:]) + call assert_equal(1, line('.')) " Test for g; and g, norm! g; @@ -1756,14 +1843,20 @@ fun! Test_normal33_g_cmd2() exe "norm! G0\<c-v>4k4ly" exe "norm! gvood" call assert_equal(['', 'abfgh', 'abfgh', 'abfgh', 'fgh', 'fgh', 'fgh', 'fgh', 'fgh'], getline(1,'$')) + " gv cannot be used in operator pending mode + call assert_beeps('normal! cgv') + " gv should beep without a previously selected visual area + new + call assert_beeps('normal! gv') + close " Test for gk/gj %d 15vsp set wrap listchars= sbr= - let lineA='abcdefghijklmnopqrstuvwxyz' - let lineB='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' - let lineC='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + let lineA = 'abcdefghijklmnopqrstuvwxyz' + let lineB = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + let lineC = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' $put =lineA $put =lineB @@ -1796,8 +1889,39 @@ fun! Test_normal33_g_cmd2() norm! g^yl call assert_equal(15, col('.')) call assert_equal('l', getreg(0)) + call assert_beeps('normal 5g$') + + " Test for g$ with double-width character half displayed + vsplit + 9wincmd | + setlocal nowrap nonumber + call setline(2, 'asdfasdfヨ') + 2 + normal 0g$ + call assert_equal(8, col('.')) + 10wincmd | + normal 0g$ + call assert_equal(9, col('.')) + + setlocal signcolumn=yes + 11wincmd | + normal 0g$ + call assert_equal(8, col('.')) + 12wincmd | + normal 0g$ + call assert_equal(9, col('.')) + + close - norm! 2ggdd + " Test for g_ + call assert_beeps('normal! 100g_') + call setline(2, [' foo ', ' foobar ']) + normal! 2ggg_ + call assert_equal(5, col('.')) + normal! 2g_ + call assert_equal(8, col('.')) + + norm! 2ggdG $put =lineC " Test for gM @@ -1839,6 +1963,17 @@ fun! Test_normal33_g_cmd2() $put ='third line' norm! gi another word call assert_equal(['foobar next word another word', 'new line', 'third line'], getline(1,'$')) + call setline(1, 'foobar') + normal! Ggifirst line + call assert_equal('foobarfirst line', getline(1)) + " Test gi in 'virtualedit' mode with cursor after the end of the line + set virtualedit=all + call setline(1, 'foo') + exe "normal! Abar\<Right>\<Right>\<Right>\<Right>" + call setline(1, 'foo') + normal! Ggifirst line + call assert_equal('foo first line', getline(1)) + set virtualedit& " clean up bw! @@ -1927,8 +2062,8 @@ func Test_g_ctrl_g() bwipe! endfunc +" Test for g8 fun! Test_normal34_g_cmd3() - " Test for g8 new let a=execute(':norm! 1G0g8') call assert_equal("\nNUL", a) @@ -1945,11 +2080,10 @@ fun! Test_normal34_g_cmd3() bw! endfunc +" Test 8g8 which finds invalid utf8 at or after the cursor. func Test_normal_8g8() new - " Test 8g8 which finds invalid utf8 at or after the cursor. - " With invalid byte. call setline(1, "___\xff___") norm! 1G08g8g @@ -1978,8 +2112,8 @@ func Test_normal_8g8() bw! endfunc +" Test for g< fun! Test_normal35_g_cmd4() - " Test for g< " Cannot capture its output, " probably a bug, therefore, test disabled: throw "Skipped: output of g< can't be tested currently" @@ -1988,6 +2122,7 @@ fun! Test_normal35_g_cmd4() call assert_true(!empty(b), 'failed `execute(g<)`') endfunc +" Test for gp gP go fun! Test_normal36_g_cmd5() new call append(0, 'abcdefghijklmnopqrstuvwxyz') @@ -2026,8 +2161,8 @@ fun! Test_normal36_g_cmd5() bw! endfunc +" Test for gt and gT fun! Test_normal37_g_cmd6() - " basic test for gt and gT tabnew 1.txt tabnew 2.txt tabnew 3.txt @@ -2050,11 +2185,11 @@ fun! Test_normal37_g_cmd6() tabclose endfor " clean up - call assert_fails(':tabclose', 'E784') + call assert_fails(':tabclose', 'E784:') endfunc +" Test for <Home> and <C-Home> key fun! Test_normal38_nvhome() - " Test for <Home> and <C-Home> key new call setline(1, range(10)) $ @@ -2069,11 +2204,14 @@ fun! Test_normal38_nvhome() call assert_equal([0, 5, 1, 0, 1], getcurpos()) exe "norm! \<c-home>" call assert_equal([0, 1, 1, 0, 1], getcurpos()) + exe "norm! G\<c-kHome>" + call assert_equal([0, 1, 1, 0, 1], getcurpos()) " clean up bw! endfunc +" Test for cw cW ce fun! Test_normal39_cw() " Test for cw and cW on whitespace " and cpo+=w setting @@ -2095,12 +2233,27 @@ fun! Test_normal39_cw() norm! 2gg0cwfoo call assert_equal('foo', getline('.')) + call setline(1, 'one; two') + call cursor(1, 1) + call feedkeys('cwvim', 'xt') + call assert_equal('vim; two', getline(1)) + call feedkeys('0cWone', 'xt') + call assert_equal('one two', getline(1)) + "When cursor is at the end of a word 'ce' will change until the end of the + "next word, but 'cw' will change only one character + call setline(1, 'one two') + call feedkeys('0ecwce', 'xt') + call assert_equal('once two', getline(1)) + call setline(1, 'one two') + call feedkeys('0ecely', 'xt') + call assert_equal('only', getline(1)) + " clean up bw! endfunc +" Test for CTRL-\ commands fun! Test_normal40_ctrl_bsl() - " Basic test for CTRL-\ commands new call append(0, 'here are some words') exe "norm! 1gg0a\<C-\>\<C-N>" @@ -2124,9 +2277,8 @@ fun! Test_normal40_ctrl_bsl() bw! endfunc +" Test for <c-r>=, <c-r><c-r>= and <c-r><c-o>= in insert mode fun! Test_normal41_insert_reg() - " Test for <c-r>=, <c-r><c-r>= and <c-r><c-o>= - " in insert mode new set sts=2 sw=2 ts=8 tw=0 call append(0, ["aaa\tbbb\tccc", '', '', '']) @@ -2144,8 +2296,8 @@ fun! Test_normal41_insert_reg() bw! endfunc +" Test for Ctrl-D and Ctrl-U func Test_normal42_halfpage() - " basic test for Ctrl-D and Ctrl-U call Setup_NewWindow() call assert_equal(5, &scroll) exe "norm! \<c-d>" @@ -2181,8 +2333,8 @@ func Test_normal42_halfpage() bw! endfunc +" Tests for text object aw fun! Test_normal43_textobject1() - " basic tests for text object aw new call append(0, ['foobar,eins,foobar', 'foo,zwei,foo ']) " diw @@ -2212,8 +2364,8 @@ fun! Test_normal43_textobject1() bw! endfunc +" Test for is and as text objects func Test_normal44_textobjects2() - " basic testing for is and as text objects new call append(0, ['This is a test. With some sentences!', '', 'Even with a question? And one more. And no sentence here']) " Test for dis - does not remove trailing whitespace @@ -2504,6 +2656,18 @@ func Test_gr_command() normal 4gro call assert_equal('ooooecond line', getline(2)) let &cpo = save_cpo + normal! ggvegrx + call assert_equal('xxxxx line', getline(1)) + exe "normal! gggr\<C-V>122" + call assert_equal('zxxxx line', getline(1)) + set virtualedit=all + normal! 15|grl + call assert_equal('zxxxx line l', getline(1)) + set virtualedit& + set nomodifiable + call assert_fails('normal! grx', 'E21:') + call assert_fails('normal! gRx', 'E21:') + set modifiable& enew! endfunc @@ -2701,10 +2865,11 @@ fun! Test_normal_gdollar_cmd() bw! endfunc -func Test_normal_gk() +func Test_normal_gk_gj() " needs 80 column new window new vert 80new + call assert_beeps('normal gk') put =[repeat('x',90)..' {{{1', 'x {{{1'] norm! gk " In a 80 column wide terminal the window will be only 78 char @@ -2719,12 +2884,12 @@ func Test_normal_gk() norm! gk call assert_equal(95, col('.')) call assert_equal(95, virtcol('.')) - bw! - bw! + %bw! " needs 80 column new window new vert 80new + call assert_beeps('normal gj') set number set numberwidth=10 set cpoptions+=n @@ -2743,9 +2908,14 @@ func Test_normal_gk() call assert_equal(1, col('.')) norm! gj call assert_equal(76, col('.')) - bw! - bw! - set cpoptions& number& numberwidth& + " When 'nowrap' is set, gk and gj behave like k and j + set nowrap + normal! gk + call assert_equal([2, 76], [line('.'), col('.')]) + normal! gj + call assert_equal([3, 76], [line('.'), col('.')]) + %bw! + set cpoptions& number& numberwidth& wrap& endfunc " Test for cursor movement with '-' in 'cpoptions' @@ -2765,6 +2935,54 @@ func Test_normal_cpo_minus() close! endfunc +" Test for using : to run a multi-line Ex command in operator pending mode +func Test_normal_yank_with_excmd() + new + call setline(1, ['foo', 'bar', 'baz']) + let @a = '' + call feedkeys("\"ay:if v:true\<CR>normal l\<CR>endif\<CR>", 'xt') + call assert_equal('f', @a) + close! +endfunc + +" Test for supplying a count to a normal-mode command across a cursorhold call +func Test_normal_cursorhold_with_count() + throw 'Skipped: Nvim removed <CursorHold> key' + func s:cHold() + let g:cHold_Called += 1 + endfunc + new + augroup normalcHoldTest + au! + au CursorHold <buffer> call s:cHold() + augroup END + let g:cHold_Called = 0 + call feedkeys("3\<CursorHold>2ix", 'xt') + call assert_equal(1, g:cHold_Called) + call assert_equal(repeat('x', 32), getline(1)) + augroup normalcHoldTest + au! + augroup END + au! normalcHoldTest + close! + delfunc s:cHold +endfunc + +" Test for using a count and a command with CTRL-W +func Test_wincmd_with_count() + call feedkeys("\<C-W>12n", 'xt') + call assert_equal(12, winheight(0)) +endfunc + +" Test for 'b', 'B' 'ge' and 'gE' commands +func Test_backward_motion() + normal! gg + call assert_beeps('normal! b') + call assert_beeps('normal! B') + call assert_beeps('normal! gE') + call assert_beeps('normal! ge') +endfunc + " Some commands like yy, cc, dd, >>, << and !! accept a count after " typing the first letter of the command. func Test_normal_count_after_operator() diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 48358f71b8..7afa31a6d1 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -349,19 +349,17 @@ func DummyCompleteOne(findstart, base) endif endfunc -" Test that nothing happens if the 'completefunc' opens -" a new window (no completion, no crash) +" Test that nothing happens if the 'completefunc' tries to open +" a new window (fails to open window, continues) func Test_completefunc_opens_new_window_one() new let winid = win_getid() setlocal completefunc=DummyCompleteOne call setline(1, 'one') /^one - call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E839:') - call assert_notequal(winid, win_getid()) - q! + call assert_fails('call feedkeys("A\<C-X>\<C-U>\<C-N>\<Esc>", "x")', 'E565:') call assert_equal(winid, win_getid()) - call assert_equal('', getline(1)) + call assert_equal('oneDEF', getline(1)) q! endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index 1db0a01bd8..5099818384 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -287,6 +287,18 @@ func Test_spellreall() bwipe! endfunc +func Test_spell_dump_word_length() + " this was running over MAXWLEN + new + noremap 0 0a0zW0000000 + sil! norm 0z=0 + sil norm 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 + sil! norm 0z=0 + + bwipe! + nunmap 0 +endfunc + " Test spellsuggest({word} [, {max} [, {capital}]]) func Test_spellsuggest() " Verify suggestions are given even when spell checking is not enabled. diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index 8483435062..619b63202a 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -1,5 +1,7 @@ " Tests for the substitute (:s) command +source shared.vim + func Test_multiline_subst() enew! call append(0, ["1 aa", @@ -140,7 +142,7 @@ func Test_substitute_repeat() " This caused an invalid memory access. split Xfile s/^/x - call feedkeys("gQsc\<CR>y", 'tx') + call feedkeys("Qsc\<CR>y", 'tx') bwipe! endfunc @@ -851,6 +853,54 @@ func Test_sub_change_window() delfunc Repl endfunc +" This was undoign a change in between computing the length and using it. +func Do_Test_sub_undo_change() + new + norm o0000000000000000000000000000000000000000000000000000 + silent! s/\%')/\=Repl() + bwipe! +endfunc + +func Test_sub_undo_change() + func Repl() + silent! norm g- + endfunc + call Do_Test_sub_undo_change() + + func! Repl() + silent earlier + endfunc + call Do_Test_sub_undo_change() + + delfunc Repl +endfunc + +" This was opening a command line window from the expression +func Test_sub_open_cmdline_win() + " the error only happens in a very specific setup, run a new Vim instance to + " get a clean starting point. + let lines =<< trim [SCRIPT] + set vb t_vb= + norm o0000000000000000000000000000000000000000000000000000 + func Replace() + norm q/ + endfunc + s/\%')/\=Replace() + redir >Xresult + messages + redir END + qall! + [SCRIPT] + call writefile(lines, 'Xscript') + if RunVim([], [], '-u NONE -S Xscript') + call assert_match('E565: Not allowed to change text or change window', + \ readfile('Xresult')->join('XX')) + endif + + call delete('Xscript') + call delete('Xresult') +endfunc + " Test for the 2-letter and 3-letter :substitute commands func Test_substitute_short_cmd() new diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index 09eed4e10d..5c67efb831 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -279,7 +279,7 @@ func Test_ex_mode() endfunc let timer = timer_start(40, function('g:Foo'), {'repeat':-1}) " This used to throw error E749. - exe "normal gQsleep 100m\rvi\r" + exe "normal Qsleep 100m\rvi\r" call timer_stop(timer) endfunc diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index 250b896532..522ca17675 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -301,6 +301,9 @@ func Test_replace_on_tab() call append(0, "'r'\t") normal gg^5lrxAy call assert_equal("'r' x y", getline(1)) + call setline(1, 'aaaaaaaaaaaa') + exe "normal! gg2lgR\<Tab>" + call assert_equal("aa\taaaa", getline(1)) bwipe! set virtualedit= endfunc diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 492750fa66..349c4fde50 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -700,7 +700,6 @@ func Test_linewise_select_mode() exe "normal GkkgH\<Del>" call assert_equal(['', 'b', 'c'], getline(1, '$')) - " linewise select mode: delete middle two lines call deletebufline('', 1, '$') call append('$', ['a', 'b', 'c']) @@ -722,6 +721,15 @@ func Test_linewise_select_mode() bwipe! endfunc +" Test for blockwise select mode (g CTRL-H) +func Test_blockwise_select_mode() + new + call setline(1, ['foo', 'bar']) + call feedkeys("g\<BS>\<Right>\<Down>mm", 'xt') + call assert_equal(['mmo', 'mmr'], getline(1, '$')) + close! +endfunc + func Test_visual_mode_put() new @@ -1105,6 +1113,73 @@ func Test_block_insert_replace_tabs() bwipe! endfunc +" Test for * register in : +func Test_star_register() + call assert_fails('*bfirst', 'E16:') + new + call setline(1, ['foo', 'bar', 'baz', 'qux']) + exe "normal jVj\<ESC>" + *yank r + call assert_equal("bar\nbaz\n", @r) + + delmarks < > + call assert_fails('*yank', 'E20:') + close! +endfunc + +" Test for using visual mode maps in select mode +func Test_select_mode_map() + new + vmap <buffer> <F2> 3l + call setline(1, 'Test line') + call feedkeys("gh\<F2>map", 'xt') + call assert_equal('map line', getline(1)) + + vmap <buffer> <F2> ygV + call feedkeys("0gh\<Right>\<Right>\<F2>cwabc", 'xt') + call assert_equal('abc line', getline(1)) + + vmap <buffer> <F2> :<C-U>let v=100<CR> + call feedkeys("gggh\<Right>\<Right>\<F2>foo", 'xt') + call assert_equal('foo line', getline(1)) + + " reselect the select mode using gv from a visual mode map + vmap <buffer> <F2> gv + set selectmode=cmd + call feedkeys("0gh\<F2>map", 'xt') + call assert_equal('map line', getline(1)) + set selectmode& + + close! +endfunc + +" Test for changing text in visual mode with 'exclusive' selection +func Test_exclusive_selection() + new + call setline(1, ['one', 'two']) + set selection=exclusive + call feedkeys("vwcabc", 'xt') + call assert_equal('abctwo', getline(1)) + call setline(1, ["\tone"]) + set virtualedit=all + call feedkeys('0v2lcl', 'xt') + call assert_equal('l one', getline(1)) + set virtualedit& + set selection& + close! +endfunc + +" Test for starting visual mode with a count +" This test should be run withou any previous visual modes. So this should be +" run as a first test. +func Test_AAA_start_visual_mode_with_count() + new + call setline(1, ['aaaaaaa', 'aaaaaaa', 'aaaaaaa', 'aaaaaaa']) + normal! gg2Vy + call assert_equal("aaaaaaa\naaaaaaa\n", @") + close! +endfunc + func Test_visual_put_in_block() new call setline(1, ['xxxx', 'y∞yy', 'zzzz']) diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index bd66260457..68df5819e2 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -137,8 +137,7 @@ struct TUIData { char *space_buf; }; -static bool got_winch = false; -static bool did_user_set_dimensions = false; +static int got_winch = 0; static bool cursor_style_enabled = false; #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -535,7 +534,7 @@ static void sigcont_cb(SignalWatcher *watcher, int signum, void *data) static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) { - got_winch = true; + got_winch++; UI *ui = data; if (tui_is_stopped(ui)) { return; @@ -987,7 +986,7 @@ static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) r->right = MIN(r->right, grid->width); } - if (!got_winch && (!data->is_starting || did_user_set_dimensions)) { + if (!got_winch && !data->is_starting) { // Resize the _host_ terminal. UNIBI_SET_NUM_VAR(data->params[0], (int)height); UNIBI_SET_NUM_VAR(data->params[1], (int)width); @@ -997,7 +996,7 @@ static void tui_grid_resize(UI *ui, Integer g, Integer width, Integer height) reset_scroll_region(ui, ui->width == grid->width); } } else { // Already handled the SIGWINCH signal; avoid double-resize. - got_winch = false; + got_winch = got_winch > 0 ? got_winch - 1 : 0; grid->row = -1; } } @@ -1504,23 +1503,13 @@ static void tui_guess_size(UI *ui) TUIData *data = ui->data; int width = 0, height = 0; - // 1 - look for non-default 'columns' and 'lines' options during startup - if (data->is_starting && (Columns != DFLT_COLS || Rows != DFLT_ROWS)) { - did_user_set_dimensions = true; - assert(Columns >= 0); - assert(Rows >= 0); - width = Columns; - height = Rows; - goto end; - } - - // 2 - try from a system call(ioctl/TIOCGWINSZ on unix) + // 1 - try from a system call(ioctl/TIOCGWINSZ on unix) if (data->out_isatty && !uv_tty_get_winsize(&data->output_handle.tty, &width, &height)) { goto end; } - // 3 - use $LINES/$COLUMNS if available + // 2 - use $LINES/$COLUMNS if available const char *val; int advance; if ((val = os_getenv("LINES")) @@ -1530,7 +1519,7 @@ static void tui_guess_size(UI *ui) goto end; } - // 4 - read from terminfo if available + // 3 - read from terminfo if available height = unibi_get_num(data->ut, unibi_lines); width = unibi_get_num(data->ut, unibi_columns); diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 135c46203f..8324db37c6 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -89,6 +89,7 @@ #include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/edit.h" +#include "nvim/ex_getln.h" #include "nvim/extmark.h" #include "nvim/fileio.h" #include "nvim/fold.h" @@ -297,7 +298,7 @@ bool undo_allowed(buf_T *buf) // Don't allow changes in the buffer while editing the cmdline. The // caller of getcmdline() may get confused. if (textlock != 0) { - emsg(_(e_secure)); + emsg(_(e_textlock)); return false; } @@ -1954,6 +1955,11 @@ void undo_time(long step, bool sec, bool file, bool absolute) bool above = false; bool did_undo = true; + if (text_locked()) { + text_locked_msg(); + return; + } + // First make sure the current undoable change is synced. if (curbuf->b_u_synced == false) { u_sync(true); diff --git a/src/nvim/window.c b/src/nvim/window.c index ee0d19b1fe..771a85479d 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -4571,12 +4571,8 @@ void win_goto(win_T *wp) { win_T *owp = curwin; - if (text_locked()) { + if (text_or_buf_locked()) { beep_flush(); - text_locked_msg(); - return; - } - if (curbuf_locked()) { return; } diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index c418a12faf..859c2ebf44 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -7,6 +7,8 @@ local eval, eq, neq, retry = helpers.eval, helpers.eq, helpers.neq, helpers.retry local ok = helpers.ok local feed = helpers.feed +local pcall_err = helpers.pcall_err +local assert_alive = helpers.assert_alive local iswin = helpers.iswin describe('autocmd TermClose', function() @@ -16,6 +18,24 @@ describe('autocmd TermClose', function() command('set shellcmdflag=EXE shellredir= shellpipe= shellquote= shellxquote=') end) + + local function test_termclose_delete_own_buf() + command('autocmd TermClose * bdelete!') + command('terminal') + eq('Vim(bdelete):E937: Attempt to delete a buffer that is in use', pcall_err(command, 'bdelete!')) + assert_alive() + end + + -- TODO: fixed after merging patches for `can_unload_buffer`? + pending('TermClose deleting its own buffer, altbuf = buffer 1 #10386', function() + test_termclose_delete_own_buf() + end) + + it('TermClose deleting its own buffer, altbuf NOT buffer 1 #10386', function() + command('edit foo1') + test_termclose_delete_own_buf() + end) + it('triggers when fast-exiting terminal job stops', function() command('autocmd TermClose * let g:test_termclose = 23') command('terminal') diff --git a/test/functional/editor/jump_spec.lua b/test/functional/editor/jump_spec.lua index d3d3d7f79d..63f522fe6e 100644 --- a/test/functional/editor/jump_spec.lua +++ b/test/functional/editor/jump_spec.lua @@ -251,4 +251,32 @@ describe("jumpoptions=view", function() | ]]) end) + + it('falls back to standard behavior for a mark without a view', function() + local screen = Screen.new(5, 8) + screen:attach() + command('edit ' .. file1) + feed('10ggzzvwy') + screen:expect([[ + 7 line | + 8 line | + 9 line | + ^10 line | + 11 line | + 12 line | + 13 line | + | + ]]) + feed('`]') + screen:expect([[ + 7 line | + 8 line | + 9 line | + 10 ^line | + 11 line | + 12 line | + 13 line | + | + ]]) + end) end) 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) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 1a11f6fc60..dd88379344 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -86,6 +86,24 @@ describe('TUI', function() assert_alive() end) + it('resize at startup', function() + -- Issues: #17285 #15044 #11330 + screen:try_resize(50, 10) + feed_command([[call termopen([v:progpath, '--clean', '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile']) | sleep 500m | vs new]]) + screen:expect([[ + {1: } │ | + {4:~ }│{4:~ }| + {4:~ }│{4:~ }| + {4:~ }│{4:~ }| + {4:~ }│{4:~ }| + {4:~ }│{5:[No Name] 0,0-1 All}| + {4:~ }│ | + {5:new }{MATCH:<.*[/\]nvim }| + | + {3:-- TERMINAL --} | + ]]) + end) + it('accepts resize while pager is active', function() child_session:request("nvim_command", [[ set more diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index 4286446af7..d8dd546a8d 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -1778,11 +1778,11 @@ describe("'inccommand' and :cnoremap", function() feed_command("cnoremap <expr> x execute('bwipeout!')[-1].'x'") feed(":%s/tw/tox<enter>") - screen:expect{any=[[{14:^E523:]]} + screen:expect{any=[[{14:^E565:]]} feed('<c-c>') -- error thrown b/c of the mapping - neq(nil, eval('v:errmsg'):find('^E523:')) + neq(nil, eval('v:errmsg'):find('^E565:')) expect([[ Inc substitution on toxo lines diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index e628fcea01..ea98705394 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -383,7 +383,7 @@ function Screen:expect(expected, attr_ids, ...) for i, row in ipairs(expected_rows) do msg_expected_rows[i] = row local m = (row ~= actual_rows[i] and row:match('{MATCH:(.*)}') or nil) - if row ~= actual_rows[i] and (not m or not actual_rows[i]:match(m)) then + if row ~= actual_rows[i] and (not m or not (actual_rows[i] and actual_rows[i]:match(m))) then msg_expected_rows[i] = '*' .. msg_expected_rows[i] if i <= #actual_rows then actual_rows[i] = '*' .. actual_rows[i] diff --git a/test/functional/vimscript/api_functions_spec.lua b/test/functional/vimscript/api_functions_spec.lua index d07e74d40e..8ca245f61a 100644 --- a/test/functional/vimscript/api_functions_spec.lua +++ b/test/functional/vimscript/api_functions_spec.lua @@ -49,7 +49,8 @@ describe('eval-API', function() it('cannot change texts if textlocked', function() command("autocmd TextYankPost <buffer> ++once call nvim_buf_set_lines(0, 0, -1, v:false, [])") - eq('Vim(call):E5555: API call: E523: Not allowed here', pcall_err(command, "normal! yy")) + eq('Vim(call):E5555: API call: E565: Not allowed to change text or change window', + pcall_err(command, "normal! yy")) end) it("use buffer numbers and windows ids as handles", function() |