diff options
34 files changed, 748 insertions, 189 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 078377dcc2..e26d0d63c5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,7 +92,7 @@ the VCS/git logs more valuable. The general structure of a commit message is: ``` - Prefix the commit subject with one of these [_types_](https://github.com/commitizen/conventional-commit-types/blob/master/index.json): - - `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `test`, `vim-patch`, `chore` + - `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `test`, `vim-patch`, `dist` - You can **ignore this for "fixup" commits** or any commits you expect to be squashed. - Append optional scope to _type_ such as `(lsp)`, `(treesitter)`, `(float)`, … - _Description_ shouldn't start with a capital letter or end in a period. diff --git a/runtime/autoload/health/nvim.vim b/runtime/autoload/health/nvim.vim index f3732e012f..9b387095ee 100644 --- a/runtime/autoload/health/nvim.vim +++ b/runtime/autoload/health/nvim.vim @@ -45,7 +45,7 @@ function! s:check_config() abort let shadafile = empty(&shada) ? &shada : substitute(matchstr( \ split(&shada, ',')[-1], '^n.\+'), '^n', '', '') let shadafile = empty(&shadafile) ? empty(shadafile) ? - \ stdpath('data').'/shada/main.shada' : expand(shadafile) + \ stdpath('state').'/shada/main.shada' : expand(shadafile) \ : &shadafile ==# 'NONE' ? '' : &shadafile if !empty(shadafile) && empty(glob(shadafile)) " Since this may be the first time neovim has been run, we will try to diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 820eaad99a..86ec05417c 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1728,8 +1728,7 @@ Vimscript Functions *api-vimscript* nvim_call_dict_function({dict}, {fn}, {args}) Calls a VimL |Dictionary-function| with the given arguments. - On execution error: fails with VimL error, does not update - v:errmsg. + On execution error: fails with VimL error, updates v:errmsg. Parameters: ~ {dict} Dictionary, or String evaluating to a VimL |self| @@ -1743,8 +1742,7 @@ nvim_call_dict_function({dict}, {fn}, {args}) nvim_call_function({fn}, {args}) *nvim_call_function()* Calls a VimL function with the given arguments. - On execution error: fails with VimL error, does not update - v:errmsg. + On execution error: fails with VimL error, updates v:errmsg. Parameters: ~ {fn} Function to call @@ -1763,6 +1761,8 @@ nvim_cmd({*cmd}, {*opts}) *nvim_cmd()* argument, expanding filenames in a command that otherwise doesn't expand filenames, etc. + On execution error: fails with VimL error, updates v:errmsg. + Parameters: ~ {cmd} Command to execute. Must be a Dictionary that can contain the same values as the return value of @@ -1784,8 +1784,7 @@ nvim_cmd({*cmd}, {*opts}) *nvim_cmd()* nvim_command({command}) *nvim_command()* Executes an Ex command. - On execution error: fails with VimL error, does not update - v:errmsg. + On execution error: fails with VimL error, updates v:errmsg. Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script or an Ex command @@ -1801,8 +1800,7 @@ nvim_eval({expr}) *nvim_eval()* Evaluates a VimL |expression|. Dictionaries and Lists are recursively expanded. - On execution error: fails with VimL error, does not update - v:errmsg. + On execution error: fails with VimL error, updates v:errmsg. Parameters: ~ {expr} VimL expression string @@ -1817,8 +1815,7 @@ nvim_exec({src}, {output}) *nvim_exec()* Unlike |nvim_command()| this function supports heredocs, script-scope (s:), etc. - On execution error: fails with VimL error, does not update - v:errmsg. + On execution error: fails with VimL error, updates v:errmsg. Parameters: ~ {src} Vimscript code diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 0b6b8d05ed..b551552c03 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -690,8 +690,9 @@ matching within a single line. vim.regex({re}) *vim.regex()* Parse the Vim regex {re} and return a regex object. Regexes are - "magic" and case-insensitive by default, regardless of 'magic' and - 'ignorecase'. They can be controlled with flags, see |/magic|. + "magic" and case-sensitive by default, regardless of 'magic' and + 'ignorecase'. They can be controlled with flags, see |/magic| and + |/ignorecase|. Methods on the regex object: diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index dc25d68f61..e6ab48f30d 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -406,7 +406,6 @@ function vim.defer_fn(fn, timeout) timeout, 0, vim.schedule_wrap(function() - timer:stop() timer:close() fn() diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index fed0231ae9..8321e0b11e 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -810,11 +810,11 @@ local extension = { R = function(path, bufnr) require('vim.filetype.detect').r(bufnr) end, - asm = function() - vim.fn['dist#ft#FTasm']() + asm = function(path, bufnr) + require('vim.filetype.detect').asm(bufnr) end, - bas = function() - vim.fn['dist#ft#FTbas']() + bas = function(path, bufnr) + require('vim.filetype.detect').bas(bufnr) end, bi = function() vim.fn['dist#ft#FTbas']() @@ -828,11 +828,11 @@ local extension = { btm = function(path, bufnr) return require('vim.filetype.detect').btm(bufnr) end, - c = function() - vim.fn['dist#ft#FTlpc']() + c = function(path, bufnr) + return require('vim.filetype.detect').lpc(bufnr) end, - ch = function() - vim.fn['dist#ft#FTchange']() + ch = function(path, bufnr) + return require('vim.filetype.detect').change(bufnr) end, com = function() vim.fn['dist#ft#BindzoneCheck']('dcl') @@ -843,8 +843,8 @@ local extension = { csh = function() vim.fn['dist#ft#CSH']() end, - d = function() - vim.fn['dist#ft#DtraceCheck']() + d = function(path, bufnr) + return require('vim.filetype.detect').dtrace(bufnr) end, db = function() vim.fn['dist#ft#BindzoneCheck']('') @@ -862,30 +862,30 @@ local extension = { vim.fn['dist#ft#SetFileTypeSH']('bash') end, ent = function(path, bufnr) - return require('vim.filetype.detect').ent(bufnr) + require('vim.filetype.detect').ent(bufnr) end, env = function() vim.fn['dist#ft#SetFileTypeSH'](vim.fn.getline(1)) end, eu = function(path, bufnr) - return require('vim.filetype.detect').euphoria(bufnr) + require('vim.filetype.detect').euphoria(bufnr) end, ew = function(path, bufnr) - return require('vim.filetype.detect').euphoria(bufnr) + require('vim.filetype.detect').euphoria(bufnr) end, ex = function(path, bufnr) - return require('vim.filetype.detect').ex(bufnr) + require('vim.filetype.detect').ex(bufnr) end, exu = function(path, bufnr) - return require('vim.filetype.detect').euphoria(bufnr) + require('vim.filetype.detect').euphoria(bufnr) end, exw = function(path, bufnr) - return require('vim.filetype.detect').euphoria(bufnr) + require('vim.filetype.detect').euphoria(bufnr) end, frm = function(path, bufnr) require('vim.filetype.detect').frm(bufnr) end, - fs = function() + fs = function(path, bufnr) vim.fn['dist#ft#FTfs']() end, h = function(path, bufnr) @@ -897,8 +897,8 @@ local extension = { html = function() vim.fn['dist#ft#FThtml']() end, - i = function() - vim.fn['dist#ft#FTprogress_asm']() + i = function(path, bufnr) + require('vim.filetype.detect').progress_asm(bufnr) end, idl = function(path, bufnr) require('vim.filetype.detect').idl(bufnr) @@ -912,26 +912,26 @@ local extension = { ksh = function() vim.fn['dist#ft#SetFileTypeSH']('ksh') end, - lst = function() - vim.fn['dist#ft#FTasm']() + lst = function(path, bufnr) + require('vim.filetype.detect').asm(bufnr) end, - m = function() - vim.fn['dist#ft#FTm']() + m = function(path, bufnr) + require('vim.filetype.detect').m(bufnr) end, - mac = function() - vim.fn['dist#ft#FTasm']() + mac = function(path, bufnr) + require('vim.filetype.detect').asm(bufnr) end, mc = function(path, bufnr) require('vim.filetype.detect').mc(bufnr) end, - mm = function() - vim.fn['dist#ft#FTmm']() + mm = function(path, bufnr) + require('vim.filetype.detect').mm(bufnr) end, mms = function(path, bufnr) require('vim.filetype.detect').mms(bufnr) end, - p = function() - vim.fn['dist#ft#FTprogress_pascal']() + p = function(path, bufnr) + require('vim.filetype.detect').progress_pascal(bufnr) end, patch = function(path, bufnr) local firstline = getline(bufnr, 1) @@ -959,8 +959,8 @@ local extension = { rdf = function(path, bufnr) require('vim.filetype.detect').redif(bufnr) end, - rules = function() - vim.fn['dist#ft#FTRules']() + rules = function(path, bufnr) + require('vim.filetype.detect').rules(path, bufnr) end, sc = function(path, bufnr) require('vim.filetype.detect').sc(bufnr) @@ -1559,13 +1559,13 @@ local pattern = { ['%.zlog.*'] = starsetf('zsh'), ['%.zsh.*'] = starsetf('zsh'), ['.*%.[1-9]'] = function(path, bufnr) - return require('vim.filetype.detect').nroff(bufnr) + require('vim.filetype.detect').nroff(bufnr) end, - ['.*%.[aA]'] = function() - vim.fn['dist#ft#FTasm']() + ['.*%.[aA]'] = function(path, bufnr) + require('vim.filetype.detect').asm(bufnr) end, - ['.*%.[sS]'] = function() - vim.fn['dist#ft#FTasm']() + ['.*%.[sS]'] = function(path, bufnr) + require('vim.filetype.detect').asm(bufnr) end, ['.*%.properties_.._.._.*'] = starsetf('jproperties'), ['.*%.vhdl_[0-9].*'] = starsetf('vhdl'), @@ -1575,8 +1575,8 @@ local pattern = { ['.*/Xresources/.*'] = starsetf('xdefaults'), ['.*/app%-defaults/.*'] = starsetf('xdefaults'), ['.*/bind/db%..*'] = starsetf('bindzone'), - ['.*/debian/patches/.*'] = function() - vim.fn['dist#ft#Dep3patch']() + ['.*/debian/patches/.*'] = function(path, bufnr) + require('vim.filetype.detect').dep3patch(path, bufnr) end, ['.*/etc/Muttrc%.d/.*'] = starsetf('muttrc'), ['.*/etc/apache2/.*%.conf.*'] = starsetf('apache'), @@ -1680,24 +1680,24 @@ local pattern = { return 'git' end end, - ['.*%.[Cc][Ff][Gg]'] = function() - vim.fn['dist#ft#FTcfg']() + ['.*%.[Cc][Ff][Gg]'] = function(path, bufnr) + require('vim.filetype.detect').cfg(bufnr) end, - ['.*%.[Dd][Aa][Tt]'] = function() - vim.fn['dist#ft#FTdat']() + ['.*%.[Dd][Aa][Tt]'] = function(path, bufnr) + require('vim.filetype.detect').dat(bufnr) end, - ['.*%.[Mm][Oo][Dd]'] = function() - vim.fn['dist#ft#FTmod']() + ['.*%.[Mm][Oo][Dd]'] = function(path, bufnr) + require('vim.filetype.detect').mod(path, bufnr) end, - ['.*%.[Ss][Rr][Cc]'] = function() - vim.fn['dist#ft#FTsrc']() + ['.*%.[Ss][Rr][Cc]'] = function(path, bufnr) + require('vim.filetype.detect').src(bufnr) end, ['.*%.[Ss][Uu][Bb]'] = 'krl', - ['.*%.[Pp][Rr][Gg]'] = function() - vim.fn['dist#ft#FTprg']() + ['.*%.[Pp][Rr][Gg]'] = function(path, bufnr) + require('vim.filetype.detect').prg(bufnr) end, - ['.*%.[Ss][Yy][Ss]'] = function() - vim.fn['dist#ft#FTsys']() + ['.*%.[Ss][Yy][Ss]'] = function(path, bufnr) + require('vim.filetype.detect').sys(bufnr) end, -- Neovim only ['.*/queries/.*%.scm'] = 'query', -- tree-sitter queries diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 4c363e7403..da59625353 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -1,15 +1,28 @@ +-- Contains filetype detection functions converted to Lua from Vim's autoload/runtime/dist/ft.vim file. + +-- Here are a few guidelines to follow when porting a new function: +-- * Sort the function alphabetically and omit 'ft' or 'check' from the new function name. +-- * Use ':find' instead of ':match' / ':sub' if possible. +-- * When '=~' is used to match a pattern, there are two possibilities: +-- - If the pattern only contains lowercase characters, treat the comparison as case-insensitive. +-- - Otherwise, treat it as case-sensitive. +-- (Basically, we apply 'smartcase': if upper case characters are used in the original pattern, then +-- it's likely that case does matter). +-- * When '\k', '\<' or '\>' is used in a pattern, use the 'matchregex' function. +-- Note that vim.regex is case-sensitive by default, so add the '\c' flag if only lowercase letters +-- are present in the pattern: +-- Example: +-- `if line =~ '^\s*unwind_protect\>'` => `if matchregex(line, [[\c^\s*unwind_protect\>]])` + local M = {} ---@private -local function getlines(bufnr, start_lnum, end_lnum, opts) +local function getlines(bufnr, start_lnum, end_lnum) if not end_lnum then -- Return a single line as a string return vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, start_lnum, false)[1] end - - local lines = vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false) - opts = opts or {} - return opts.concat and (table.concat(lines) or '') or lines + return vim.api.nvim_buf_get_lines(bufnr, start_lnum - 1, end_lnum, false) end ---@private @@ -22,16 +35,103 @@ local function findany(s, patterns) return false end +---@private +local function nextnonblank(bufnr, start_lnum) + for _, line in ipairs(getlines(bufnr, start_lnum, -1)) do + if not line:find('^%s*$') then + return line + end + end + return nil +end + +---@private +local matchregex = (function() + local cache = {} + return function(line, pattern) + if line == nil then + return nil + end + if not cache[pattern] then + cache[pattern] = vim.regex(pattern) + end + return cache[pattern]:match_str(line) + end +end)() + -- luacheck: push no unused args -- luacheck: push ignore 122 -function M.asm(path, bufnr) end +-- This function checks for the kind of assembly that is wanted by the user, or +-- can be detected from the first five lines of the file. +function M.asm(bufnr) + -- Make sure b:asmsyntax exists + if not vim.b[bufnr].asmsyntax then + vim.b[bufnr].asmsyntax = '' + end -function M.asm_syntax(path, bufnr) end + if vim.b[bufnr].asmsyntax == '' then + M.asm_syntax(bufnr) + end -function M.bas(path, bufnr) end + -- If b:asmsyntax still isn't set, default to asmsyntax or GNU + if vim.b[bufnr].asmsyntax == '' then + if vim.g.asmsyntax and vim.g.asmsyntax ~= 0 then + vim.b[bufnr].asmsyntax = vim.g.asmsyntax + else + vim.b[bufnr].asmsyntax = 'asm' + end + end + vim.bo[bufnr].filetype = vim.fn.fnameescape(vim.b[bufnr].asmsyntax) +end + +-- Checks the first 5 lines for a asmsyntax=foo override. +-- Only whitespace characters can be present immediately before or after this statement. +function M.asm_syntax(bufnr) + local lines = table.concat(getlines(bufnr, 1, 5), ' '):lower() + local match = lines:match('%sasmsyntax=([a-zA-Z0-9]+)%s') + if match then + vim.b['asmsyntax'] = match + elseif findany(lines, { '%.title', '%.ident', '%.macro', '%.subtitle', '%.library' }) then + vim.b['asmsyntax'] = 'vmasm' + end +end -function M.bindzone(path, bufnr) end +local visual_basic_content = { 'vb_name', 'begin vb%.form', 'begin vb%.mdiform', 'begin vb%.usercontrol' } + +-- See frm() for Visual Basic form file detection +function M.bas(bufnr) + if vim.g.filetype_bas then + vim.bo[bufnr].filetype = vim.g.filetype_bas + return + end + + -- Most frequent FreeBASIC-specific keywords in distro files + local fb_keywords = + [[\c^\s*\%(extern\|var\|enum\|private\|scope\|union\|byref\|operator\|constructor\|delete\|namespace\|public\|property\|with\|destructor\|using\)\>\%(\s*[:=(]\)\@!]] + local fb_preproc = + [[\c^\s*\%(#\a\+\|option\s\+\%(byval\|dynamic\|escape\|\%(no\)\=gosub\|nokeyword\|private\|static\)\>\)]] + + local fb_comment = "^%s*/'" + -- OPTION EXPLICIT, without the leading underscore, is common to many dialects + local qb64_preproc = [[\c^\s*\%($\a\+\|option\s\+\%(_explicit\|_\=explicitarray\)\>\)]] + + for _, line in ipairs(getlines(bufnr, 1, 100)) do + if line:find(fb_comment) or matchregex(line, fb_preproc) or matchregex(line, fb_keywords) then + vim.bo[bufnr].filetype = 'freebasic' + return + elseif matchregex(line, qb64_preproc) then + vim.bo[bufnr].filetype = 'qb64' + return + elseif findany(line:lower(), visual_basic_content) then + vim.bo[bufnr].filetype = 'vb' + return + end + end + vim.bo[bufnr].filetype = 'basic' +end + +function M.bindzone(bufnr, default_ft) end function M.btm(bufnr) if vim.g.dosbatch_syntax_for_btm and vim.g.dosbatch_syntax_for_btm ~= 0 then @@ -47,14 +147,11 @@ local function is_rapid(bufnr, extension) local line = getlines(bufnr, 1):lower() return findany(line, { 'eio:cfg', 'mmc:cfg', 'moc:cfg', 'proc:cfg', 'sio:cfg', 'sys:cfg' }) end - local first = '^%s*module%s+%S+%s*' - -- Called from mod, prg or sys functions - for _, line in ipairs(getlines(bufnr, 1, -1)) do - if not line:find('^%s*$') then - return findany(line:lower(), { '^%s*%%%%%%', first .. '(', first .. '$' }) - end + local line = nextnonblank(bufnr, 1) + if line then + -- Called from mod, prg or sys functions + return matchregex(line:lower(), [[\c\v^\s*%(\%{3}|module\s+\k+\s*%(\(|$))]]) end - -- Only found blank lines return false end @@ -68,17 +165,110 @@ function M.cfg(bufnr) end end -function M.change(path, bufnr) end +-- This function checks if one of the first ten lines start with a '@'. In +-- that case it is probably a change file. +-- If the first line starts with # or ! it's probably a ch file. +-- If a line has "main", "include", "//" or "/*" it's probably ch. +-- Otherwise CHILL is assumed. +function M.change(bufnr) + local first_line = getlines(bufnr, 1) + if findany(first_line, { '^#', '^!' }) then + vim.bo[bufnr].filetype = 'ch' + return + end + for _, line in ipairs(getlines(bufnr, 1, 10)) do + if line:find('^@') then + vim.bo[bufnr].filetype = 'change' + return + end + if line:find('MODULE') then + vim.bo[bufnr].filetype = 'chill' + return + elseif findany(line:lower(), { 'main%s*%(', '#%s*include', '//' }) then + vim.bo[bufnr].filetype = 'ch' + return + end + end + vim.bo[bufnr].filetype = 'chill' +end function M.csh(path, bufnr) end -function M.dat(path, bufnr) end +-- Determine if a *.dat file is Kuka Robot Language +-- TODO: this one fails for some reason, so I omitted it. #18219 should be merged first. +function M.dat(bufnr) + -- if vim.g.filetype_dat then + -- vim.bo[bufnr].filetype = vim.g.filetype_dat + -- return + -- end + -- local line = nextnonblank(bufnr, 1):lower() + -- if findany(line, { "^%s*&%w+", "^%s*defdat" }) then + -- vim.bo[bufnr].filetype = "krl" + -- end +end -function M.dep3patch(path, bufnr) end +-- This function is called for all files under */debian/patches/*, make sure not +-- to non-dep3patch files, such as README and other text files. +function M.dep3patch(path, bufnr) + local file_name = vim.fn.fnamemodify(path, ':t') + if file_name == 'series' then + return + end -function M.dtrace(path, bufnr) end + for _, line in ipairs(getlines(bufnr, 1, 100)) do + if + findany(line, { + '^Description:', + '^Subject:', + '^Origin:', + '^Bug:', + '^Forwarded:', + '^Author:', + '^From:', + '^Reviewed%-by:', + '^Acked%-by:', + '^Last%-Updated:', + '^Applied%-Upstream:', + }) + then + vim.bo[bufnr].filetype = 'dep3patch' + return + elseif line:find('^%-%-%-') then + -- End of headers found. stop processing + return + end + end +end -function M.e(path, bufnr) end +function M.dtrace(bufnr) + local did_filetype = vim.fn.did_filetype() + if did_filetype and did_filetype ~= 0 then + -- Filetype was already detected + return + end + for _, line in ipairs(getlines(bufnr, 1, 100)) do + if matchregex(line, [[\c^module\>\|^import\>]]) then + -- D files often start with a module and/or import statement. + vim.bo[bufnr].filetype = 'd' + return + elseif findany(line, { '^#!%S+dtrace', '#pragma%s+D%s+option', ':%S-:%S-:' }) then + vim.bo[bufnr].filetype = 'dtrace' + return + end + end + vim.bo[bufnr].filetype = 'd' +end + +function M.e(path, bufnr) + if vim.g.filetype_euphoria then + vim.bo[bufnr].filetype = vim.g.filetype_euphoria + return + end + -- TODO: WIP + -- for _, line in ipairs(getlines(bufnr, 1, 100)) do + -- if line:find("^$") + -- end +end -- This function checks for valid cl syntax in the first five lines. -- Look for either an opening comment, '#', or a block start, '{'. @@ -110,8 +300,7 @@ function M.ex(bufnr) vim.bo[bufnr].filetype = vim.g.filetype_euphoria else for _, line in ipairs(getlines(bufnr, 1, 100)) do - -- TODO: in the Vim regex, \> is used to match the end of the word, can this be omitted? - if findany(line, { '^%-%-', '^ifdef', '^include' }) then + if matchregex(line, [[\c^--\|^ifdef\>\|^include\>]]) then vim.bo[bufnr].filetype = 'euphoria3' return end @@ -138,22 +327,34 @@ end function M.frm(bufnr) if vim.g.filetype_frm then vim.bo[bufnr].filetype = vim.g.filetype_frm + return + end + local lines = table.concat(getlines(bufnr, 1, 5)):lower() + if findany(lines, visual_basic_content) then + vim.bo[bufnr].filetype = 'vb' else - -- Always ignore case - local lines = getlines(bufnr, 1, 5, { concat = true }):lower() - if findany(lines, { 'vb_name', 'begin vb%.form', 'begin vb%.mdiform' }) then - vim.bo[bufnr].filetype = 'vb' - else - vim.bo[bufnr].filetype = 'form' - end + vim.bo[bufnr].filetype = 'form' end end -function M.fs(path, bufnr) end +-- Distinguish between Forth and F#. +function M.fs(bufnr) + -- TODO: WIP + -- if vim.g.filetype_fs then + -- vim.bo[bufnr].filetype = vim.g.filetype_fs + -- return + -- end + -- local line = nextnonblank(bufnr, 1) + -- if findany(line, { '^%s*.?%( ', '^%s*\\G? ', '^\\$', '^%s*: %S' }) then + -- vim.bo[bufnr].filetype = 'forth' + -- else + -- vim.bo[bufnr].filetype = 'fsharp' + -- end +end function M.header(bufnr) for _, line in ipairs(getlines(bufnr, 1, 200)) do - if findany(line, { '^@interface', '^@end', '^@class' }) then + if findany(line:lower(), { '^@interface', '^@end', '^@class' }) then if vim.g.c_syntax_for_h then vim.bo[bufnr].filetype = 'objc' else @@ -171,11 +372,22 @@ function M.header(bufnr) end end +function M.html(bufnr) + for _, line in ipairs(getlines(bufnr, 1, 10)) do + if matchregex(line, [[\<DTD\s\+XHTML\s]]) then + vim.bo[bufnr].filetype = 'xhtml' + return + elseif matchregex(line, [[\c{%\s*\(extends\|block\|load\)\>\|{#\s\+]]) then + vim.bo[bufnr].filetype = 'htmldjango' + return + end + end + vim.bo[bufnr].filetype = 'html' +end + function M.idl(bufnr) for _, line in ipairs(getlines(bufnr, 1, 50)) do - -- Always ignore case - line = line:lower() - if findany(line, { '^%s*import%s+"unknwn"%.idl', '^%s*import%s+"objidl"%.idl' }) then + if findany(line:lower(), { '^%s*import%s+"unknwn"%.idl', '^%s*import%s+"objidl"%.idl' }) then vim.bo[bufnr].filetype = 'msidl' return end @@ -198,11 +410,83 @@ function M.inp(bufnr) end end -function M.lpc(path, bufnr) end +function M.lpc(bufnr) + if vim.g.lpc_syntax_for_c then + for _, line in ipairs(getlines(bufnr, 1, 12)) do + if + findany(line, { + '^//', + '^inherit', + '^private', + '^protected', + '^nosave', + '^string', + '^object', + '^mapping', + '^mixed', + }) + then + vim.bo[bufnr].filetype = 'lpc' + return + end + end + end + vim.bo[bufnr].filetype = 'c' +end -function M.lprolog(path, bufnr) end +function M.m(bufnr) + if vim.g.filetype_m then + vim.bo[bufnr].filetype = vim.g.filetype_m + return + end -function M.m(path, bufnr) end + -- Excluding end(for|function|if|switch|while) common to Murphi + local octave_block_terminators = + [[\<end\%(_try_catch\|classdef\|enumeration\|events\|methods\|parfor\|properties\)\>]] + local objc_preprocessor = [[\c^\s*#\s*\%(import\|include\|define\|if\|ifn\=def\|undef\|line\|error\|pragma\)\>]] + + -- Whether we've seen a multiline comment leader + local saw_comment = false + for _, line in ipairs(getlines(bufnr, 1, 100)) do + if line:find('^%s*/%*') then + -- /* ... */ is a comment in Objective C and Murphi, so we can't conclude + -- it's either of them yet, but track this as a hint in case we don't see + -- anything more definitive. + saw_comment = true + end + if line:find('^%s*//') or matchregex(line, [[\c^\s*@import\>]]) or matchregex(line, objc_preprocessor) then + vim.bo[bufnr].filetype = 'objc' + return + end + if + findany(line, { '^%s*#', '^%s*%%!' }) + or matchregex(line, [[\c^\s*unwind_protect\>]]) + or matchregex(line, [[\c\%(^\|;\)\s*]] .. octave_block_terminators) + then + vim.bo[bufnr].filetype = 'octave' + return + elseif line:find('^%s*%%') then + vim.bo[bufnr].filetype = 'matlab' + return + elseif line:find('^%s*%(%*') then + vim.bo[bufnr].filetype = 'mma' + return + elseif matchregex(line, [[\c^\s*\(\(type\|var\)\>\|--\)]]) then + vim.bo[bufnr].filetype = 'murphi' + return + end + end + + if saw_comment then + -- We didn't see anything definitive, but this looks like either Objective C + -- or Murphi based on the comment leader. Assume the former as it is more + -- common. + vim.bo[bufnr].filetype = 'objc' + else + -- Default is Matlab + vim.bo[bufnr].filetype = 'matlab' + end +end -- Rely on the file to start with a comment. -- MS message text files use ';', Sendmail files use '#' or 'dnl' @@ -221,7 +505,15 @@ function M.mc(bufnr) vim.bo[bufnr].filetype = 'm4' end -function M.mm(path, bufnr) end +function M.mm(bufnr) + for _, line in ipairs(getlines(bufnr, 1, 20)) do + if matchregex(line, [[\c^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)]]) then + vim.bo[bufnr].filetype = 'objcpp' + return + end + end + vim.bo[bufnr].filetype = 'nroff' +end function M.mms(bufnr) for _, line in ipairs(getlines(bufnr, 1, 20)) do @@ -236,35 +528,129 @@ function M.mms(bufnr) vim.bo[bufnr].filetype = 'mmix' end -function M.mod(path, bufnr) end +-- Returns true if file content looks like LambdaProlog +local function is_lprolog(bufnr) + -- Skip apparent comments and blank lines, what looks like + -- LambdaProlog comment may be RAPID header + for _, line in ipairs(getlines(bufnr, 1, -1)) do + -- The second pattern matches a LambdaProlog comment + if not findany(line, { '^%s*$', '^%s*%%' }) then + -- The pattern must not catch a go.mod file + return matchregex(line, [[\c\<module\s\+\w\+\s*\.\s*\(%\|$\)]]) ~= nil + end + end +end + +-- Determine if *.mod is ABB RAPID, LambdaProlog, Modula-2, Modsim III or go.mod +function M.mod(path, bufnr) + if vim.g.filetype_mod then + vim.bo[bufnr].filetype = vim.g.filetype_mod + elseif is_lprolog(bufnr) then + vim.bo[bufnr].filetype = 'lprolog' + elseif matchregex(nextnonblank(bufnr, 1), [[\%(\<MODULE\s\+\w\+\s*;\|^\s*(\*\)]]) then + vim.bo[bufnr].filetype = 'modula2' + elseif is_rapid(bufnr) then + vim.bo[bufnr].filetype = 'rapid' + elseif matchregex(path, [[\c\<go\.mod$]]) then + vim.bo[bufnr].filetype = 'gomod' + else + -- Nothing recognized, assume modsim3 + vim.bo[bufnr].filetype = 'modsim3' + end +end -- This function checks if one of the first five lines start with a dot. In --- that case it is probably an nroff file: 'filetype' is set and 1 is returned. +-- that case it is probably an nroff file: 'filetype' is set and true is returned. function M.nroff(bufnr) for _, line in ipairs(getlines(bufnr, 1, 5)) do if line:find('^%.') then vim.bo[bufnr].filetype = 'nroff' - return 1 + return true end end - return 0 + return false end -function M.perl(path, bufnr) end +-- If the file has an extension of 't' and is in a directory 't' or 'xt' then +-- it is almost certainly a Perl test file. +-- 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 + vim.bo[bufnr].filetype = 'perl' + return true + end + local first_line = getlines(bufnr, 1) + if first_line:find('^#') and first_line:lower():find('perl') then + vim.bo[bufnr].filetype = 'perl' + return true + end + for _, line in ipairs(getlines(bufnr, 1, 30)) do + if matchregex(line, [[\c^use\s\s*\k]]) then + vim.bo[bufnr].filetype = 'perl' + return true + end + end + return false +end function M.pl(path, bufnr) end -function M.pp(path, bufnr) end +local pascal_comments = { '^%s*{', '^%s*%(*', '^%s*//' } +local pascal_keywords = [[\c^\s*\%(program\|unit\|library\|uses\|begin\|procedure\|function\|const\|type\|var\)\>]] + +function M.pp(bufnr) + -- TODO: WIP + + -- if vim.g.filetype_pp then + -- vim.bo[bufnr].filetype = vim.g.filetype_pp + -- return + -- end + -- local first_line = nextnonblank(bufnr, 1):lower() + -- if findany(first_line, { pascal_comments, pascal_keywords }) then + -- vim.bo[bufnr].filetype = "pascal" + -- else + -- vim.bo[bufnr].filetype = "puppet" + -- end +end + +function M.prg(bufnr) + if vim.g.filetype_prg then + vim.bo[bufnr].filetype = vim.g.filetype_prg + elseif is_rapid(bufnr) then + vim.bo[bufnr].filetype = 'rapid' + else + -- Nothing recognized, assume Clipper + vim.bo[bufnr].filetype = 'clipper' + end +end -function M.prg(path, bufnr) end +-- This function checks for an assembly comment in the first ten lines. +-- If not found, assume Progress. +function M.progress_asm(bufnr) + if vim.g.filetype_i then + vim.bo[bufnr].filetype = vim.g.filetype_i + return + end -function M.progress_asm(path, bufnr) end + for _, line in ipairs(getlines(bufnr, 1, 10)) do + if line:find('^%s*;') or line:find('^/%*') then + return M.asm(bufnr) + elseif not line:find('^%s*$') or line:find('^/%*') then + -- Not an empty line: doesn't look like valid assembly code + -- or it looks like a Progress /* comment. + break + end + end + vim.bo[bufnr].filetype = 'progress' +end function M.progress_cweb(bufnr) if vim.g.filetype_w then vim.bo[bufnr].filetype = vim.g.filetype_w else - if getlines(bufnr, 1):find('^&ANALYZE') or getlines(bufnr, 3):find('^&GLOBAL%-DEFINE') then + if getlines(bufnr, 1):lower():find('^&analyze') or getlines(bufnr, 3):lower():find('^&global%-define') then vim.bo[bufnr].filetype = 'progress' else vim.bo[bufnr].filetype = 'cweb' @@ -272,15 +658,33 @@ function M.progress_cweb(bufnr) end end -function M.progress_pascal(path, bufnr) end +-- This function checks for valid Pascal syntax in the first 10 lines. +-- Look for either an opening comment or a program start. +-- If not found, assume Progress. +function M.progress_pascal(bufnr) + if vim.g.filetype_p then + vim.bo[bufnr].filetype = vim.g.filetype_p + return + end + for _, line in ipairs(getlines(bufnr, 1, 10)) do + if findany(line, pascal_comments) or matchregex(line, pascal_keywords) then + vim.bo[bufnr].filetype = 'pascal' + return + elseif not line:find('^%s*$') or line:find('^/%*') then + -- Not an empty line: Doesn't look like valid Pascal code. + -- Or it looks like a Progress /* comment + break + end + end + vim.bo[bufnr].filetype = 'progress' +end function M.proto(path, bufnr) end function M.r(bufnr) local lines = getlines(bufnr, 1, 50) - -- TODO: \< / \> which match the beginning / end of a word -- Rebol is easy to recognize, check for that first - if table.concat(lines):lower():find('rebol') then + if matchregex(table.concat(lines), [[\c\<rebol\>]]) then vim.bo[bufnr].filetype = 'rebol' return end @@ -315,7 +719,43 @@ function M.redif(bufnr) end end -function M.rules(path, bufnr) end +local udev_rules_pattern = '^%s*udev_rules%s*=%s*"([%^"]+)/*".*' +function M.rules(path, bufnr) + path = path:lower() + if + findany(path, { + '/etc/udev/.*%.rules$', + '/etc/udev/rules%.d/.*$.rules$', + '/usr/lib/udev/.*%.rules$', + '/usr/lib/udev/rules%.d/.*%.rules$', + '/lib/udev/.*%.rules$', + '/lib/udev/rules%.d/.*%.rules$', + }) + then + vim.bo[bufnr].filetype = 'udevrules' + elseif path:find('^/etc/ufw/') then + -- Better than hog + vim.bo[bufnr].filetype = 'conf' + elseif findany(path, { '^/etc/polkit%-1/rules%.d', '/usr/share/polkit%-1/rules%.d' }) then + vim.bo[bufnr].filetype = 'javascript' + else + local ok, config_lines = pcall(vim.fn.readfile, '/etc/udev/udev.conf') + if not ok then + vim.bo[bufnr].filetype = 'hog' + return + end + local dir = vim.fn.expand(path, ':h') + for _, line in ipairs(config_lines) do + local match = line:match(udev_rules_pattern) + local udev_rules = line:gsub(udev_rules_pattern, match, 1) + if dir == udev_rules then + vim.bo[bufnr].filetype = 'udevrules' + return + end + end + vim.bo[bufnr].filetype = 'hog' + end +end -- This function checks the first 25 lines of file extension "sc" to resolve -- detection between scala and SuperCollider @@ -359,9 +799,27 @@ function M.sql(bufnr) end end -function M.src(path, bufnr) end +-- Determine if a *.src file is Kuka Robot Language +function M.src(bufnr) + if vim.g.filetype_src then + vim.bo[bufnr].filetype = vim.g.filetype_src + return + end + local line = nextnonblank(bufnr, 1) + if matchregex(line, [[\c\v^\s*%(\&\w+|%(global\s+)?def%(fct)?>)]]) then + vim.bo[bufnr].filetype = 'krl' + end +end -function M.sys(path, bufnr) end +function M.sys(bufnr) + if vim.g.filetype_sys then + vim.bo[bufnr].filetype = vim.g.filetype_sys + elseif is_rapid(bufnr) then + vim.bo[bufnr].filetype = 'rapid' + else + vim.bo[bufnr].filetype = 'bat' + end +end function M.tex(path, bufnr) end @@ -380,8 +838,8 @@ end function M.xml(bufnr) for _, line in ipairs(getlines(bufnr, 1, 100)) do + local is_docbook4 = line:find('<!DOCTYPE.*DocBook') line = line:lower() - local is_docbook4 = line:find('<!doctype.*docbook') local is_docbook5 = line:find([[ xmlns="http://docbook.org/ns/docbook"]]) if is_docbook4 or is_docbook5 then vim.b[bufnr].docbk_type = 'xml' @@ -403,10 +861,9 @@ function M.y(bufnr) vim.bo[bufnr].filetype = 'yacc' return end - -- TODO: in the Vim regex, \> is used to match the end of the word after "class", - -- can this be omitted? - if findany(line, { '^%s*#', '^%class', '^%s*#%s*include' }) then + if matchregex(line, [[\c^\s*\(#\|class\>\)]]) and not line:lower():find('^%s*#%s*include') then vim.bo[bufnr].filetype = 'racc' + return end end vim.bo[bufnr].filetype = 'yacc' diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index 74714ebc6b..bf8fe0932e 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -17,7 +17,8 @@ function M.check() local log_path = vim.lsp.get_log_path() report_info(string.format('Log path: %s', log_path)) - local log_size = vim.loop.fs_stat(log_path).size + local log_file = vim.loop.fs_stat(log_path) + local log_size = log_file and log_file.size or 0 local report_fn = (log_size / 1000000 > 100 and report_warn or report_info) report_fn(string.format('Log size: %d KB', log_size / 1000)) diff --git a/scripts/lintcommit.lua b/scripts/lintcommit.lua index 34d1263ff4..16326cfe66 100644 --- a/scripts/lintcommit.lua +++ b/scripts/lintcommit.lua @@ -78,7 +78,7 @@ local function validate_commit(commit_message) -- Check if type is correct local type = vim.split(before_colon, "%(")[1] - local allowed_types = {'build', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'test', 'chore', 'vim-patch'} + local allowed_types = {'build', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'test', 'dist', 'vim-patch'} if not vim.tbl_contains(allowed_types, type) then return string.format( 'Invalid commit type "%s". Allowed types are:\n %s', @@ -181,7 +181,7 @@ function M._test() ['refactor: normal message'] = true, ['revert: normal message'] = true, ['test: normal message'] = true, - ['chore: normal message'] = true, + ['dist: normal message'] = true, ['ci(window): message with scope'] = true, ['ci!: message with breaking change'] = true, ['ci(tui)!: message with scope and breaking change'] = true, @@ -205,10 +205,10 @@ function M._test() ['ci :extra space before colon'] = false, ['refactor(): empty scope'] = false, ['ci( ): whitespace as scope'] = false, - ['chore: period at end of sentence.'] = false, + ['ci: period at end of sentence.'] = false, ['ci: Starting sentence capitalized'] = false, ['unknown: using unknown type'] = false, - ['chore: you\'re saying this commit message just goes on and on and on and on and on and on for way too long?'] = false, + ['ci: you\'re saying this commit message just goes on and on and on and on and on and on for way too long?'] = false, } local failed = 0 diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c index 42101af7f0..b8f7b33cd5 100644 --- a/src/nvim/api/vimscript.c +++ b/src/nvim/api/vimscript.c @@ -34,7 +34,7 @@ /// Unlike |nvim_command()| this function supports heredocs, script-scope (s:), /// etc. /// -/// On execution error: fails with VimL error, does not update v:errmsg. +/// On execution error: fails with VimL error, updates v:errmsg. /// /// @see |execute()| /// @see |nvim_command()| @@ -98,7 +98,7 @@ theend: /// Executes an Ex command. /// -/// On execution error: fails with VimL error, does not update v:errmsg. +/// On execution error: fails with VimL error, updates v:errmsg. /// /// Prefer using |nvim_cmd()| or |nvim_exec()| over this. To evaluate multiple lines of Vim script /// or an Ex command directly, use |nvim_exec()|. To construct an Ex command using a structured @@ -118,7 +118,7 @@ void nvim_command(String command, Error *err) /// Evaluates a VimL |expression|. /// Dictionaries and Lists are recursively expanded. /// -/// On execution error: fails with VimL error, does not update v:errmsg. +/// On execution error: fails with VimL error, updates v:errmsg. /// /// @param expr VimL expression string /// @param[out] err Error details, if any @@ -226,7 +226,7 @@ free_vim_args: /// Calls a VimL function with the given arguments. /// -/// On execution error: fails with VimL error, does not update v:errmsg. +/// On execution error: fails with VimL error, updates v:errmsg. /// /// @param fn Function to call /// @param args Function arguments packed in an Array @@ -240,7 +240,7 @@ Object nvim_call_function(String fn, Array args, Error *err) /// Calls a VimL |Dictionary-function| with the given arguments. /// -/// On execution error: fails with VimL error, does not update v:errmsg. +/// On execution error: fails with VimL error, updates v:errmsg. /// /// @param dict Dictionary, or String evaluating to a VimL |self| dict /// @param fn Name of the function defined on the VimL dict @@ -996,6 +996,8 @@ end: /// such as having spaces inside a command argument, expanding filenames in a command that otherwise /// doesn't expand filenames, etc. /// +/// On execution error: fails with VimL error, updates v:errmsg. +/// /// @see |nvim_exec()| /// @see |nvim_command()| /// diff --git a/src/nvim/change.c b/src/nvim/change.c index a21665dc23..94e5a19edc 100644 --- a/src/nvim/change.c +++ b/src/nvim/change.c @@ -209,6 +209,10 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra curwin->w_changelistidx = curbuf->b_changelistlen; } + if (VIsual_active) { + check_visual_pos(); + } + FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == curbuf) { // Mark this window to be redrawn later. diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index 11c734479c..1446257f7e 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -399,6 +399,24 @@ void check_cursor(void) check_cursor_col(); } +/// Check if VIsual position is valid, correct it if not. +/// Can be called when in Visual mode and a change has been made. +void check_visual_pos(void) +{ + if (VIsual.lnum > curbuf->b_ml.ml_line_count) { + VIsual.lnum = curbuf->b_ml.ml_line_count; + VIsual.col = 0; + VIsual.coladd = 0; + } else { + int len = (int)STRLEN(ml_get(VIsual.lnum)); + + if (VIsual.col > len) { + VIsual.col = len; + VIsual.coladd = 0; + } + } +} + /// Make sure curwin->w_cursor is not on the NUL at the end of the line. /// Allow it when in Visual mode and 'selection' is not "old". void adjust_cursor_col(void) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 357e9184d7..aa77c03b48 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -6770,13 +6770,8 @@ static void stop_insert(pos_T *end_insert_pos, int esc, int nomove) // <C-S-Right> may have started Visual mode, adjust the position for // deleted characters. - if (VIsual_active && VIsual.lnum == curwin->w_cursor.lnum) { - int len = (int)STRLEN(get_cursor_line_ptr()); - - if (VIsual.col > len) { - VIsual.col = len; - VIsual.coladd = 0; - } + if (VIsual_active) { + check_visual_pos(); } } } diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 89fced59c5..d4e20e2f66 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -143,7 +143,7 @@ bool loop_close(Loop *loop, bool wait) while (true) { // Run the loop to tickle close-callbacks (which should then free memory). // Use UV_RUN_NOWAIT to avoid a hang. #11820 - uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT); + uv_run(&loop->uv, didstop ? UV_RUN_DEFAULT : UV_RUN_NOWAIT); // -V547 if ((uv_loop_close(&loop->uv) != UV_EBUSY) || !wait) { break; } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 0510faa57d..7506c353dd 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -345,7 +345,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) // here. The value of 200 allows nested function calls, ":source", etc. // Allow 200 or 'maxfuncdepth', whatever is larger. if (call_depth >= 200 && call_depth >= p_mfd) { - emsg(_("E169: Command too recursive")); + emsg(_(e_command_too_recursive)); // When converting to an exception, we do not include the command name // since this is not an error of the specific command. do_errthrow((cstack_T *)NULL, NULL); @@ -1683,14 +1683,13 @@ void execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo) (eap->argt & EX_BUFUNL) != 0, false, false); eap->addr_count = 1; // Shift each argument by 1 - if (eap->args != NULL) { - for (size_t i = 0; i < eap->argc - 1; i++) { - eap->args[i] = eap->args[i + 1]; - } - // Make the last argument point to the NUL terminator at the end of string - eap->args[eap->argc - 1] = eap->args[eap->argc - 1] + eap->arglens[eap->argc - 1]; - eap->argc -= 1; + for (size_t i = 0; i < eap->argc - 1; i++) { + eap->args[i] = eap->args[i + 1]; } + // Make the last argument point to the NUL terminator at the end of string + eap->args[eap->argc - 1] = eap->args[eap->argc - 1] + eap->arglens[eap->argc - 1]; + eap->argc -= 1; + eap->arg = eap->args[0]; } if (eap->line2 < 0) { // failed diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 114f1e2ae5..1d496cbf25 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -804,6 +804,12 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init ccline.cmdlen = s->indent; } + if (cmdline_level == 50) { + // Somehow got into a loop recursively calling getcmdline(), bail out. + emsg(_(e_command_too_recursive)); + goto theend; + } + ExpandInit(&s->xpc); ccline.xpc = &s->xpc; @@ -995,12 +1001,13 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init State = s->save_State; setmouse(); ui_cursor_shape(); // may show different cursor shape + sb_text_end_cmdline(); + +theend: xfree(s->save_p_icm); xfree(ccline.last_colors.cmdbuff); kv_destroy(ccline.last_colors.colors); - sb_text_end_cmdline(); - char_u *p = ccline.cmdbuff; if (ui_has(kUICmdline)) { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index e34e3983db..3231ac011a 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -877,7 +877,8 @@ EXTERN char e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job")); EXTERN char e_argreq[] INIT(= N_("E471: Argument required")); EXTERN char e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &")); EXTERN char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> executes, CTRL-C quits")); -EXTERN char e_curdir[] INIT(= N_( "E12: Command not allowed from exrc/vimrc in current dir or tag search")); +EXTERN char e_curdir[] INIT(= N_("E12: Command not allowed from exrc/vimrc in current dir or tag search")); +EXTERN char e_command_too_recursive[] INIT(= N_("E169: Command too recursive")); EXTERN char e_endif[] INIT(= N_("E171: Missing :endif")); EXTERN char e_endtry[] INIT(= N_("E600: Missing :endtry")); EXTERN char e_endwhile[] INIT(= N_("E170: Missing :endwhile")); diff --git a/src/nvim/grid.c b/src/nvim/grid.c index 8a5d8081c0..13429994de 100644 --- a/src/nvim/grid.c +++ b/src/nvim/grid.c @@ -63,7 +63,7 @@ void grid_clear_line(ScreenGrid *grid, size_t off, int width, bool valid) void grid_invalidate(ScreenGrid *grid) { - (void)memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)(grid->Rows * grid->Columns)); + (void)memset(grid->attrs, -1, sizeof(sattr_T) * (size_t)grid->Rows * (size_t)grid->Columns); } bool grid_invalid_row(ScreenGrid *grid, int row) diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 6f75183a5a..e6dc985726 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -150,7 +150,7 @@ static const char_u *skip_string(const char_u *p) i++; } } - if (p[i] == '\'') { // check for trailing ' + if (p[i - 1] != NUL && p[i] == '\'') { // check for trailing ' p += i; continue; } diff --git a/src/nvim/strings.c b/src/nvim/strings.c index c0c942ffd2..cde2059a9d 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -634,12 +634,12 @@ static const void *tv_ptr(const typval_T *const tvs, int *const idxp) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { #define OFF(attr) offsetof(union typval_vval_union, attr) - STATIC_ASSERT(OFF(v_string) == OFF(v_list) + STATIC_ASSERT(OFF(v_string) == OFF(v_list) // -V568 && OFF(v_string) == OFF(v_dict) && OFF(v_string) == OFF(v_partial) - && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list) // -V568 - && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_dict) // -V568 - && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_partial), // -V568 + && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list) + && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_dict) + && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_partial), "Strings, dictionaries, lists and partials are expected to be pointers, " "so that all three of them can be accessed via v_string"); #undef OFF diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 2d3102707c..c8f70d4afd 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -1374,26 +1374,21 @@ static void fetch_row(Terminal *term, int row, int end_col) while (col < end_col) { VTermScreenCell cell; fetch_cell(term, row, col, &cell); - int cell_len = 0; if (cell.chars[0]) { + int cell_len = 0; for (int i = 0; cell.chars[i]; i++) { cell_len += utf_char2bytes((int)cell.chars[i], ptr + cell_len); } - } else { - *ptr = ' '; - cell_len = 1; - } - char c = *ptr; - ptr += cell_len; - if (c != ' ') { - // only increase the line length if the last character is not whitespace + ptr += cell_len; line_len = (size_t)(ptr - term->textbuf); + } else { + *ptr++ = ' '; } col += cell.width; } - // trim trailing whitespace - term->textbuf[line_len] = 0; + // end of line + term->textbuf[line_len] = NUL; } static bool fetch_cell(Terminal *term, int row, int col, VTermScreenCell *cell) diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim index 4d69aed96c..7ba8ef3397 100644 --- a/src/nvim/testdir/test_cindent.vim +++ b/src/nvim/testdir/test_cindent.vim @@ -5311,6 +5311,13 @@ func Test_cindent_case() bwipe! endfunc +" This was reading past the end of the line +func Test_cindent_check_funcdecl() + new + sil norm o0('\0=L + bwipe! +endfunc + func Test_cindent_scopedecls() new setl cindent ts=4 sw=4 diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 759caac878..b2c752376f 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -1224,4 +1224,16 @@ func Test_screenpos_and_completion() call feedkeys(":let a\<C-R>=Check_completion()\<CR>\<Esc>", "xt") endfunc +func Test_recursive_register() + let @= = '' + silent! ?e/ + let caught = 'no' + try + normal // + catch /E169:/ + let caught = 'yes' + endtry + call assert_equal('yes', caught) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 2b88deb813..41c29c5bb0 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -1249,11 +1249,23 @@ endfunc func Test_visual_block_append_invalid_char() " this was going over the end of the line + set isprint=@,161-255 new call setline(1, [' let xxx', 'xxxxx', 'xxxxxxxxxxx']) exe "normal 0\<C-V>jjA-\<Esc>" call assert_equal([' - let xxx', 'xxxxx -', 'xxxxxxxx-xxx'], getline(1, 3)) bwipe! + set isprint& +endfunc + +func Test_visual_block_with_substitute() + " this was reading beyond the end of the line + new + norm a0) + sil! norm O + s/) + sil! norm + bwipe! endfunc func Test_visual_reselect_with_count() diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index fc09e4cde0..2728dcf74c 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -785,7 +785,8 @@ describe('API: buffer events:', function() local function lines_subset(first, second) for i = 1,#first do - if first[i] ~= second[i] then + -- need to ignore trailing spaces + if first[i]:gsub(' +$', '') ~= second[i]:gsub(' +$', '') then return false end end @@ -827,7 +828,6 @@ describe('API: buffer events:', function() end it('when :terminal lines change', function() - if helpers.pending_win32(pending) then return end local buffer_lines = {} local expected_lines = {} command('terminal "'..nvim_prog..'" -u NONE -i NONE -n -c "set shortmess+=A"') diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 20ea3621f0..f87fd8e951 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -53,7 +53,6 @@ describe('startup', function() ]]) end) it('in a TTY: has("ttyin")==1 has("ttyout")==1', function() - if helpers.pending_win32(pending) then return end local screen = Screen.new(25, 4) screen:attach() if iswin() then @@ -105,7 +104,6 @@ describe('startup', function() end) end) it('input from pipe (implicit) #7679', function() - if helpers.pending_win32(pending) then return end local screen = Screen.new(25, 4) screen:attach() if iswin() then @@ -261,7 +259,6 @@ describe('startup', function() end) it('ENTER dismisses early message #7967', function() - if helpers.pending_win32(pending) then return end local screen screen = Screen.new(60, 6) screen:attach() @@ -494,14 +491,14 @@ describe('sysinit', function() end) it('fixed hang issue with -D (#12647)', function() - if helpers.pending_win32(pending) then return end local screen - screen = Screen.new(60, 6) + screen = Screen.new(60, 7) screen:attach() command([[let g:id = termopen('"]]..nvim_prog.. [[" -u NONE -i NONE --cmd "set noruler" -D')]]) screen:expect([[ ^ | + | Entering Debug mode. Type "cont" to continue. | cmd: augroup nvim_terminal | > | @@ -512,6 +509,7 @@ describe('sysinit', function() screen:expect([[ ^ | ~ | + ~ | [No Name] | | <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All| diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua index 2702fb196f..97e41bbe83 100644 --- a/test/functional/ex_cmds/mksession_spec.lua +++ b/test/functional/ex_cmds/mksession_spec.lua @@ -10,11 +10,11 @@ local funcs = helpers.funcs local matches = helpers.matches local pesc = helpers.pesc local rmdir = helpers.rmdir +local sleep = helpers.sleep local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec' describe(':mksession', function() - if helpers.pending_win32(pending) then return end local session_file = file_prefix .. '.vim' local tab_dir = file_prefix .. '.d' @@ -103,9 +103,11 @@ describe(':mksession', function() local session_path = cwd_dir..'/'..session_file command('cd '..tab_dir) - command('terminal echo $PWD') + command('terminal') command('cd '..cwd_dir) command('mksession '..session_path) + command('bd!') + sleep(100) -- Make sure the process exits. command('qall!') -- Create a new test instance of Nvim. @@ -114,6 +116,7 @@ describe(':mksession', function() local expected_cwd = cwd_dir..'/'..tab_dir matches('^term://'..pesc(expected_cwd)..'//%d+:', funcs.expand('%')) - command('qall!') + command('bd!') + sleep(100) -- Make sure the process exits. end) end) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index 3b905f1f56..6e06304acd 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -5,6 +5,7 @@ local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim local nvim_dir, command = helpers.nvim_dir, helpers.command local nvim_prog = helpers.nvim_prog local eq, eval = helpers.eq, helpers.eval +local matches = helpers.matches local feed_command = helpers.feed_command local hide_cursor = thelpers.hide_cursor local show_cursor = thelpers.show_cursor @@ -177,7 +178,6 @@ describe('cursor with customized highlighting', function() end) describe('buffer cursor position is correct in terminal without number column', function() - if helpers.pending_win32(pending) then return end local screen local function setup_ex_register(str) @@ -525,10 +525,36 @@ describe('buffer cursor position is correct in terminal without number column', eq({6, 1}, eval('nvim_win_get_cursor(0)')) end) end) + + it('at the end of a line with trailing spaces #16234', function() + setup_ex_register('aaaaaaaa ') + feed('<C-R>r') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :aaaaaaaa {1: } | + {3:-- TERMINAL --} | + ]]) + matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()')) + eq({6, 13}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :aaaaaaaa ^ {2: } | + | + ]]) + eq({6, 12}, eval('nvim_win_get_cursor(0)')) + end) end) describe('buffer cursor position is correct in terminal with number column', function() - if helpers.pending_win32(pending) then return end local screen local function setup_ex_register(str) @@ -879,4 +905,31 @@ describe('buffer cursor position is correct in terminal with number column', fun eq({6, 1}, eval('nvim_win_get_cursor(0)')) end) end) + + it('at the end of a line with trailing spaces #16234', function() + setup_ex_register('aaaaaaaa ') + feed('<C-R>r') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:aaaaaaaa {1: } | + {3:-- TERMINAL --} | + ]]) + matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()')) + eq({6, 13}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:aaaaaaaa ^ {2: } | + | + ]]) + eq({6, 12}, eval('nvim_win_get_cursor(0)')) + end) end) diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index 32c911a5e8..2a63971d48 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -117,7 +117,6 @@ describe(':terminal highlight', function() end) it(':terminal highlight has lower precedence than editor #9964', function() - if helpers.pending_win32(pending) then return end clear() local screen = Screen.new(30, 4) screen:set_default_attr_ids({ diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index d1cfc7e91b..34fcb6cab9 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -6,6 +6,7 @@ local feed, nvim_dir, feed_command = helpers.feed, helpers.nvim_dir, helpers.fee local iswin = helpers.iswin local eval = helpers.eval local command = helpers.command +local matches = helpers.matches local poke_eventloop = helpers.poke_eventloop local retry = helpers.retry local curbufmeths = helpers.curbufmeths @@ -460,8 +461,8 @@ describe("'scrollback' option", function() expect_lines(58) -- Verify off-screen state - eq((iswin() and '36: line' or '35: line'), eval("getline(line('w0') - 1)")) - eq((iswin() and '27: line' or '26: line'), eval("getline(line('w0') - 10)")) + matches((iswin() and '^36: line[ ]*$' or '^35: line[ ]*$'), eval("getline(line('w0') - 1)")) + matches((iswin() and '^27: line[ ]*$' or '^26: line[ ]*$'), eval("getline(line('w0') - 10)")) end) it('defaults to 10000 in :terminal buffers', function() diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 041337df8a..ee1e37c675 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1312,7 +1312,6 @@ end) describe('ui/msg_puts_printf', function() it('output multibyte characters correctly', function() - if helpers.pending_win32(pending) then return end local screen local cmd = '' local locale_dir = test_build_dir..'/share/locale/ja/LC_MESSAGES' diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 7305baa761..50e5dfac84 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -14,7 +14,6 @@ local has_powershell = helpers.has_powershell local set_shell_powershell = helpers.set_shell_powershell describe("shell command :!", function() - if helpers.pending_win32(pending) then return end local screen before_each(function() clear() diff --git a/test/unit/fixtures/rbuffer.c b/test/unit/fixtures/rbuffer.c index 3f4062fa18..efa7ab1986 100644 --- a/test/unit/fixtures/rbuffer.c +++ b/test/unit/fixtures/rbuffer.c @@ -15,7 +15,7 @@ void ut_rbuffer_each_read_chunk(RBuffer *buf, each_ptr_cb cb) void ut_rbuffer_each_write_chunk(RBuffer *buf, each_ptr_cb cb) { - RBUFFER_UNTIL_FULL(buf, wptr, wcnt) { + RBUFFER_UNTIL_FULL(buf, wptr, wcnt) { // -V1044 cb(wptr, wcnt); rbuffer_produced(buf, wcnt); } diff --git a/third-party/CMakeLists.txt b/third-party/CMakeLists.txt index 36965a2e98..0464f90734 100644 --- a/third-party/CMakeLists.txt +++ b/third-party/CMakeLists.txt @@ -130,8 +130,8 @@ endif() include(ExternalProject) -set(LIBUV_URL https://github.com/libuv/libuv/archive/v1.44.1.tar.gz) -set(LIBUV_SHA256 e91614e6dc2dd0bfdd140ceace49438882206b7a6fb00b8750914e67a9ed6d6b) +set(LIBUV_URL https://github.com/libuv/libuv/archive/730e07e2f77a4001bdf6894112271c926399f5a8.tar.gz) +set(LIBUV_SHA256 271869759a7dbdaf1d1bf75f1ec388a7307592153b34ebb52d3934715cbaac8a) set(MSGPACK_URL https://github.com/msgpack/msgpack-c/releases/download/c-4.0.0/msgpack-c-4.0.0.tar.gz) set(MSGPACK_SHA256 420fe35e7572f2a168d17e660ef981a589c9cbe77faa25eb34a520e1fcc032c8) |