diff options
-rwxr-xr-x | .github/scripts/install_deps.sh | 2 | ||||
-rw-r--r-- | runtime/doc/lsp.txt | 36 | ||||
-rw-r--r-- | runtime/doc/lua.txt | 17 | ||||
-rw-r--r-- | runtime/lua/vim/_watch.lua | 61 | ||||
-rw-r--r-- | runtime/lua/vim/glob.lua | 21 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/_watchfiles.lua | 4 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/health.lua | 6 | ||||
-rw-r--r-- | test/functional/lua/glob_spec.lua | 13 | ||||
-rw-r--r-- | test/functional/lua/watch_spec.lua | 9 | ||||
-rw-r--r-- | test/functional/plugin/lsp_spec.lua | 8 |
10 files changed, 107 insertions, 70 deletions
diff --git a/.github/scripts/install_deps.sh b/.github/scripts/install_deps.sh index 66f418eb10..b90a84fc24 100755 --- a/.github/scripts/install_deps.sh +++ b/.github/scripts/install_deps.sh @@ -30,7 +30,7 @@ if [[ $os == Linux ]]; then fi if [[ -n $TEST ]]; then - sudo apt-get install -y locales-all cpanminus attr libattr1-dev gdb fswatch + sudo apt-get install -y locales-all cpanminus attr libattr1-dev gdb inotify-tools # Use default CC to avoid compilation problems when installing Python modules CC=cc python3 -m pip -q install --user --upgrade pynvim diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index e987f266cc..89ce2d2be5 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -34,16 +34,16 @@ Follow these steps to get LSP features: vim.api.nvim_create_autocmd('FileType', { -- This handler will fire when the buffer's 'filetype' is "python" pattern = 'python', - callback = function(ev) + callback = function(args) vim.lsp.start({ name = 'my-server-name', cmd = {'name-of-language-server-executable', '--option', 'arg1', 'arg2'}, -- Set the "root directory" to the parent directory of the file in the - -- current buffer (`ev.buf`) that contains either a "setup.py" or a + -- current buffer (`args.buf`) that contains either a "setup.py" or a -- "pyproject.toml" file. Files that share a root directory will reuse -- the connection to the same LSP server. - root_dir = vim.fs.root(ev.buf, {'setup.py', 'pyproject.toml'}), + root_dir = vim.fs.root(args.buf, {'setup.py', 'pyproject.toml'}), }) end, }) @@ -86,18 +86,18 @@ To override or delete any of the above defaults, set or unset the options on |LspAttach|: >lua vim.api.nvim_create_autocmd('LspAttach', { - callback = function(ev) - vim.bo[ev.buf].formatexpr = nil - vim.bo[ev.buf].omnifunc = nil - vim.keymap.del('n', 'K', { buffer = ev.buf }) + callback = function(args) + vim.bo[args.buf].formatexpr = nil + vim.bo[args.buf].omnifunc = nil + vim.keymap.del('n', 'K', { buffer = args.buf }) end, }) < *lsp-config* -To use other LSP features, set keymaps on |LspAttach|. Not all language -servers provide the same capabilities. To ensure you only set keymaps if the -language server supports a feature, guard keymaps behind capability checks. -Example: >lua +To use other LSP features, set keymaps and other buffer options on +|LspAttach|. Not all language servers provide the same capabilities. Use +capability checks to ensure you only use features supported by the language +server. Example: >lua vim.api.nvim_create_autocmd('LspAttach', { callback = function(args) @@ -105,6 +105,20 @@ Example: >lua if client.supports_method('textDocument/implementation') then -- Create a keymap for vim.lsp.buf.implementation end + + if client.supports_method('textDocument/completion') then + -- Enable auto-completion + vim.lsp.completion.enable(true, client.id, args.buf, {autotrigger = true}) + end + + if client.supports_method('textDocument/formatting') then + -- Format the current buffer on save + vim.api.nvim_create_autocmd('BufWritePre', { + buffer = args.buf, + callback = function() + vim.lsp.buf.format({bufnr = args.buf, id = client.id}) + end, + }) end, }) < diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 39a047cbab..dfaf666874 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -543,16 +543,19 @@ Example: File-change detection *watch-file* vim.api.nvim_command( "command! -nargs=1 Watch call luaeval('watch_file(_A)', expand('<args>'))") < - *fswatch-limitations* -When on Linux and using fswatch, you may need to increase the maximum number -of `inotify` watches and queued events as the default limit can be too low. To -increase the limit, run: >sh - sysctl fs.inotify.max_user_watches=100000 - sysctl fs.inotify.max_queued_events=100000 + *inotify-limitations* +When on Linux you may need to increase the maximum number of `inotify` watches +and queued events as the default limit can be too low. To increase the limit, +run: >sh + sysctl fs.inotify.max_user_watches=494462 < -This will increase the limit to 100000 watches and queued events. These lines +This will increase the limit to 494462 watches and queued events. These lines can be added to `/etc/sysctl.conf` to make the changes persistent. +Note that each watch is a structure in the Kernel, thus available memory is +also a bottleneck for using inotify. In fact, a watch can take up to 1KB of +space. This means a million watches could result in 1GB of extra RAM usage. + Example: TCP echo-server *tcp-server* 1. Save this code to a file. 2. Execute it with ":luafile %". diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 02b3f536c2..40f18ce5b0 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -227,11 +227,12 @@ end --- @param data string --- @param opts vim._watch.Opts? --- @param callback vim._watch.Callback -local function fswatch_output_handler(data, opts, callback) +local function on_inotifywait_output(data, opts, callback) local d = vim.split(data, '%s+') -- only consider the last reported event - local fullpath, event = d[1], d[#d] + local path, event, file = d[1], d[2], d[#d] + local fullpath = vim.fs.joinpath(path, file) if skip(fullpath, opts) then return @@ -240,20 +241,16 @@ local function fswatch_output_handler(data, opts, callback) --- @type integer local change_type - if event == 'Created' then + if event == 'CREATE' then change_type = M.FileChangeType.Created - elseif event == 'Removed' then + elseif event == 'DELETE' then change_type = M.FileChangeType.Deleted - elseif event == 'Updated' then + elseif event == 'MODIFY' then change_type = M.FileChangeType.Changed - elseif event == 'Renamed' then - local _, staterr, staterrname = uv.fs_stat(fullpath) - if staterrname == 'ENOENT' then - change_type = M.FileChangeType.Deleted - else - assert(not staterr, staterr) - change_type = M.FileChangeType.Created - end + elseif event == 'MOVED_FROM' then + change_type = M.FileChangeType.Deleted + elseif event == 'MOVED_TO' then + change_type = M.FileChangeType.Created end if change_type then @@ -265,24 +262,22 @@ end --- @param opts vim._watch.Opts? --- @param callback vim._watch.Callback Callback for new events --- @return fun() cancel Stops the watcher -function M.fswatch(path, opts, callback) - -- debounce isn't the same as latency but close enough - local latency = 0.5 -- seconds - if opts and opts.debounce then - latency = opts.debounce / 1000 - end - +function M.inotify(path, opts, callback) local obj = vim.system({ - 'fswatch', - '--event=Created', - '--event=Removed', - '--event=Updated', - '--event=Renamed', - '--event-flags', + 'inotifywait', + '--quiet', -- suppress startup messages + '--no-dereference', -- don't follow symlinks + '--monitor', -- keep listening for events forever '--recursive', - '--latency=' .. tostring(latency), - '--exclude', - '/.git/', + '--event', + 'create', + '--event', + 'delete', + '--event', + 'modify', + '--event', + 'move', + '@.git', -- ignore git directory path, }, { stderr = function(err, data) @@ -292,11 +287,11 @@ function M.fswatch(path, opts, callback) if data and #vim.trim(data) > 0 then vim.schedule(function() - if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then - data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.' + if vim.fn.has('linux') == 1 and vim.startswith(data, 'Failed to watch') then + data = 'inotify(7) limit reached, see :h inotify-limitations for more info.' end - vim.notify('fswatch: ' .. data, vim.log.levels.ERROR) + vim.notify('inotify: ' .. data, vim.log.levels.ERROR) end) end end, @@ -306,7 +301,7 @@ function M.fswatch(path, opts, callback) end for line in vim.gsplit(data or '', '\n', { plain = true, trimempty = true }) do - fswatch_output_handler(line, opts, callback) + on_inotifywait_output(line, opts, callback) end end, -- --latency is locale dependent but tostring() isn't and will always have '.' as decimal point. diff --git a/runtime/lua/vim/glob.lua b/runtime/lua/vim/glob.lua index 6de2bc3e94..22073b15c8 100644 --- a/runtime/lua/vim/glob.lua +++ b/runtime/lua/vim/glob.lua @@ -1,6 +1,6 @@ local lpeg = vim.lpeg local P, S, V, R, B = lpeg.P, lpeg.S, lpeg.V, lpeg.R, lpeg.B -local C, Cc, Ct, Cf = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cf +local C, Cc, Ct, Cf, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cf, lpeg.Cmt local M = {} @@ -47,13 +47,22 @@ function M.to_lpeg(pattern) return (-after * P(1)) ^ 0 * after end + -- luacheck: push ignore s + local function cut(s, idx, match) + return idx, match + end + -- luacheck: pop + local p = P({ 'Pattern', Pattern = V('Elem') ^ -1 * V('End'), - Elem = Cf( - (V('DStar') + V('Star') + V('Ques') + V('Class') + V('CondList') + V('Literal')) - * (V('Elem') + V('End')), - mul + Elem = Cmt( + Cf( + (V('DStar') + V('Star') + V('Ques') + V('Class') + V('CondList') + V('Literal')) + * (V('Elem') + V('End')), + mul + ), + cut ), DStar = (B(pathsep) + -B(P(1))) * P('**') @@ -72,7 +81,7 @@ function M.to_lpeg(pattern) -- pattern" which in all other cases is the entire succeeding part of the pattern, but at the end of a {} -- condition means "everything after the {}" where several other options separated by ',' may -- exist in between that should not be matched by '*'. - Cond = Cf((V('Ques') + V('Class') + V('Literal') - S(',}')) ^ 1, mul) + Cc(P(0)), + Cond = Cmt(Cf((V('Ques') + V('Class') + V('Literal') - S(',}')) ^ 1, mul), cut) + Cc(P(0)), Literal = P(1) / P, End = P(-1) * Cc(P(-1)), }) diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua index 49328fbe9b..98e9818bcd 100644 --- a/runtime/lua/vim/lsp/_watchfiles.lua +++ b/runtime/lua/vim/lsp/_watchfiles.lua @@ -9,8 +9,8 @@ local M = {} if vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1 then M._watchfunc = watch.watch -elseif vim.fn.executable('fswatch') == 1 then - M._watchfunc = watch.fswatch +elseif vim.fn.executable('inotifywait') == 1 then + M._watchfunc = watch.inotify else M._watchfunc = watch.watchdirs end diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index ffe595ab37..18066a84db 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -90,8 +90,8 @@ local function check_watcher() watchfunc_name = 'libuv-watch' elseif watchfunc == vim._watch.watchdirs then watchfunc_name = 'libuv-watchdirs' - elseif watchfunc == vim._watch.fswatch then - watchfunc_name = 'fswatch' + elseif watchfunc == vim._watch.inotifywait then + watchfunc_name = 'inotifywait' else local nm = debug.getinfo(watchfunc, 'S').source watchfunc_name = string.format('Custom (%s)', nm) @@ -99,7 +99,7 @@ local function check_watcher() report_info('File watch backend: ' .. watchfunc_name) if watchfunc_name == 'libuv-watchdirs' then - report_warn('libuv-watchdirs has known performance issues. Consider installing fswatch.') + report_warn('libuv-watchdirs has known performance issues. Consider installing inotify-tools.') end end diff --git a/test/functional/lua/glob_spec.lua b/test/functional/lua/glob_spec.lua index b3e1b79ee7..b95d874bb5 100644 --- a/test/functional/lua/glob_spec.lua +++ b/test/functional/lua/glob_spec.lua @@ -205,6 +205,19 @@ describe('glob', function() eq(true, match('[!a-zA-Z0-9]', '!')) end) + it('should handle long patterns', function() + -- lpeg has a recursion limit of 200 by default, make sure the grammar does trigger it on + -- strings longer than that + local fill_200 = + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + eq(200, fill_200:len()) + local long_lit = fill_200 .. 'a' + eq(false, match(long_lit, 'b')) + eq(true, match(long_lit, long_lit)) + local long_pat = fill_200 .. 'a/**/*.c' + eq(true, match(long_pat, fill_200 .. 'a/b/c/d.c')) + end) + it('should match complex patterns', function() eq(false, match('**/*.{c,h}', '')) eq(false, match('**/*.{c,h}', 'c')) diff --git a/test/functional/lua/watch_spec.lua b/test/functional/lua/watch_spec.lua index bd8faadf5b..3d2dda716e 100644 --- a/test/functional/lua/watch_spec.lua +++ b/test/functional/lua/watch_spec.lua @@ -23,10 +23,13 @@ describe('vim._watch', function() local function run(watchfunc) it('detects file changes (watchfunc=' .. watchfunc .. '())', function() - if watchfunc == 'fswatch' then + if watchfunc == 'inotify' then skip(is_os('win'), 'not supported on windows') skip(is_os('mac'), 'flaky test on mac') - skip(not is_ci() and n.fn.executable('fswatch') == 0, 'fswatch not installed and not on CI') + skip( + not is_ci() and n.fn.executable('inotifywait') == 0, + 'inotify-tools not installed and not on CI' + ) end if watchfunc == 'watch' then @@ -123,5 +126,5 @@ describe('vim._watch', function() run('watch') run('watchdirs') - run('fswatch') + run('inotify') end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 0630df65d5..2b8a7aed9e 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -5128,12 +5128,12 @@ describe('LSP', function() it( string.format('sends notifications when files change (watchfunc=%s)', watchfunc), function() - if watchfunc == 'fswatch' then + if watchfunc == 'inotify' then skip(is_os('win'), 'not supported on windows') skip(is_os('mac'), 'flaky test on mac') skip( - not is_ci() and fn.executable('fswatch') == 0, - 'fswatch not installed and not on CI' + not is_ci() and fn.executable('inotifywait') == 0, + 'inotify-tools not installed and not on CI' ) end @@ -5265,7 +5265,7 @@ describe('LSP', function() test_filechanges('watch') test_filechanges('watchdirs') - test_filechanges('fswatch') + test_filechanges('inotify') it('correctly registers and unregisters', function() local root_dir = '/some_dir' |