diff options
-rw-r--r-- | runtime/doc/api.txt | 4 | ||||
-rw-r--r-- | runtime/doc/lua.txt | 22 | ||||
-rw-r--r-- | runtime/doc/news.txt | 3 | ||||
-rw-r--r-- | runtime/lua/vim/filetype.lua | 20 | ||||
-rw-r--r-- | runtime/lua/vim/filetype/options.lua | 82 | ||||
-rw-r--r-- | src/nvim/api/options.c | 10 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 25 | ||||
-rw-r--r-- | test/functional/lua/filetype_spec.lua | 15 |
8 files changed, 176 insertions, 5 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 04825381ff..289788f036 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -1971,8 +1971,8 @@ nvim_get_option_value({name}, {*opts}) *nvim_get_option_value()* Implies {scope} is "local". • filetype: |filetype|. Used to get the default option for a specific filetype. Cannot be used with any other option. - Note: this is expensive, it is recommended to cache this - value. + Note: this will trigger |ftplugin| and all |FileType| + autocommands for the corresponding filetype. Return: ~ Option value diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 611f2e0d32..e7dcc79f4a 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -2206,6 +2206,28 @@ add({filetypes}) *vim.filetype.add()* • {filetypes} (table) A table containing new filetype maps (see example). +get_option({filetype}, {option}) *vim.filetype.get_option()* + Get the default option value for a {filetype}. + + The returned value is what would be set in a new buffer after 'filetype' + is set, meaning it should respect all FileType autocmds and ftplugin + files. + + Example: >lua + vim.filetype.get_option('vim', 'commentstring') +< + + Note: this uses |nvim_get_option_value()| but caches the result. This + means |ftplugin| and |FileType| autocommands are only triggered once and + may not reflect later changes. + + Parameters: ~ + • {filetype} string Filetype + • {option} string Option name + + Return: ~ + string|boolean|integer: Option value + match({args}) *vim.filetype.match()* Perform filetype detection. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index b7d5694802..5ac6db6f84 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -213,6 +213,9 @@ The following new APIs or features were added. • |nvim_get_option_value()| now has a `filetype` option so it can return the default option for a specific filetype. +• |vim.filetype.get_option()| to get the default option value for a specific + filetype. This is a wrapper around |nvim_get_option_value()| with caching. + ============================================================================== CHANGED FEATURES *news-changes* diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index cf813b19b1..69fcb01e8c 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -2631,4 +2631,24 @@ function M.match(args) end end +--- Get the default option value for a {filetype}. +--- +--- The returned value is what would be set in a new buffer after 'filetype' +--- is set, meaning it should respect all FileType autocmds and ftplugin files. +--- +--- Example: +--- <pre>lua +--- vim.filetype.get_option('vim', 'commentstring') +--- </pre> +--- +--- Note: this uses |nvim_get_option_value()| but caches the result. +--- This means |ftplugin| and |FileType| autocommands are only +--- triggered once and may not reflect later changes. +--- @param filetype string Filetype +--- @param option string Option name +--- @return string|boolean|integer: Option value +function M.get_option(filetype, option) + return require('vim.filetype.options').get_option(filetype, option) +end + return M diff --git a/runtime/lua/vim/filetype/options.lua b/runtime/lua/vim/filetype/options.lua new file mode 100644 index 0000000000..77a66991d5 --- /dev/null +++ b/runtime/lua/vim/filetype/options.lua @@ -0,0 +1,82 @@ +local api = vim.api + +local M = {} + +local function get_ftplugin_runtime(filetype) + return api.nvim__get_runtime({ + string.format('ftplugin/%s.vim', filetype), + string.format('ftplugin/%s_*.vim', filetype), + string.format('ftplugin/%s/*.vim', filetype), + string.format('ftplugin/%s.lua', filetype), + string.format('ftplugin/%s_*.lua', filetype), + string.format('ftplugin/%s/*.lua', filetype), + }, true, {}) --[[@as string[] ]] +end + +-- Keep track of ftplugin files +local ftplugin_cache = {} ---@type table<string,table<string,integer>> + +-- Keep track of total number of FileType autocmds +local ft_autocmd_num ---@type integer? + +-- Keep track of filetype options +local ft_option_cache = {} ---@type table<string,table<string,any>> + +--- @param path string +--- @return integer +local function hash(path) + local mtime0 = vim.loop.fs_stat(path).mtime + return mtime0.sec * 1000000000 + mtime0.nsec +end + +--- Only update the cache on changes to the number of FileType autocmds +--- and changes to any ftplugin/ file. This isn't guaranteed to catch everything +--- but should be good enough. +--- @param filetype string +local function update_ft_option_cache(filetype) + local new_ftautos = #api.nvim_get_autocmds({ event = 'FileType' }) + if new_ftautos ~= ft_autocmd_num then + -- invalidate + ft_option_cache[filetype] = nil + ft_autocmd_num = new_ftautos + end + + local files = get_ftplugin_runtime(filetype) + + ftplugin_cache[filetype] = ftplugin_cache[filetype] or {} + + if #files ~= #vim.tbl_keys(ftplugin_cache[filetype]) then + -- invalidate + ft_option_cache[filetype] = nil + ftplugin_cache[filetype] = {} + end + + for _, f in ipairs(files) do + -- VIMRUNTIME should be static so shouldn't need to worry about it changing + if not vim.startswith(f, vim.env.VIMRUNTIME) then + local mtime = hash(f) + if ftplugin_cache[filetype][f] ~= mtime then + -- invalidate + ft_option_cache[filetype] = nil + ftplugin_cache[filetype][f] = mtime + end + end + end +end + +--- @private +--- @param filetype string Filetype +--- @param option string Option name +--- @return string|integer|boolean +function M.get_option(filetype, option) + update_ft_option_cache(filetype) + + if not ft_option_cache[filetype] or not ft_option_cache[filetype][option] then + ft_option_cache[filetype] = ft_option_cache[filetype] or {} + ft_option_cache[filetype][option] = api.nvim_get_option_value(option, { filetype = filetype }) + end + + return ft_option_cache[filetype][option] +end + +return M diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c index a0351cc6cd..7aef6e6146 100644 --- a/src/nvim/api/options.c +++ b/src/nvim/api/options.c @@ -114,7 +114,11 @@ static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err) ftbuf->b_p_ft = xstrdup(filetype); - apply_autocmds(EVENT_FILETYPE, ftbuf->b_p_ft, ftbuf->b_fname, true, ftbuf); + TRY_WRAP({ + try_start(); + apply_autocmds(EVENT_FILETYPE, ftbuf->b_p_ft, ftbuf->b_fname, true, ftbuf); + try_end(err); + }); return ftbuf; } @@ -133,8 +137,8 @@ static buf_T *do_ft_buf(char *filetype, aco_save_T *aco, Error *err) /// Implies {scope} is "local". /// - filetype: |filetype|. Used to get the default option for a /// specific filetype. Cannot be used with any other option. -/// Note: this is expensive, it is recommended to cache this -/// value. +/// Note: this will trigger |ftplugin| and all |FileType| +/// autocommands for the corresponding filetype. /// @param[out] err Error details, if any /// @return Option value Object nvim_get_option_value(String name, Dict(option) *opts, Error *err) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 2af041c706..ff1bfef591 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1518,6 +1518,31 @@ describe('API', function() nvim('get_option_value', 'filetype', {buf = buf}) eq({1, 9}, nvim('win_get_cursor', win)) end) + + it('can get default option values for filetypes', function() + command('filetype plugin on') + for ft, opts in pairs { + lua = { commentstring = '-- %s' }, + vim = { commentstring = '"%s' }, + man = { tagfunc = 'v:lua.require\'man\'.goto_tag' }, + xml = { formatexpr = 'xmlformat#Format()' } + } do + for option, value in pairs(opts) do + eq(value, nvim('get_option_value', option, { filetype = ft })) + end + end + + command'au FileType lua setlocal commentstring=NEW\\ %s' + + eq('NEW %s', nvim('get_option_value', 'commentstring', { filetype = 'lua' })) + end) + + it('errors for bad FileType autocmds', function() + command'au FileType lua setlocal commentstring=BAD' + eq([[FileType Autocommands for "lua": Vim(setlocal):E537: 'commentstring' must be empty or contain %s: commentstring=BAD]], + pcall_err(nvim, 'get_option_value', 'commentstring', { filetype = 'lua' })) + end) + end) describe('nvim_{get,set}_current_buf, nvim_list_bufs', function() diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua index add69235b6..540eae1c9b 100644 --- a/test/functional/lua/filetype_spec.lua +++ b/test/functional/lua/filetype_spec.lua @@ -114,6 +114,21 @@ describe('vim.filetype', function() ]]) end) + it('can get default option values for filetypes via vim.filetype.get_option()', function() + command('filetype plugin on') + + for ft, opts in pairs { + lua = { commentstring = '-- %s' }, + vim = { commentstring = '"%s' }, + man = { tagfunc = 'v:lua.require\'man\'.goto_tag' }, + xml = { formatexpr = 'xmlformat#Format()' } + } do + for option, value in pairs(opts) do + eq(value, exec_lua([[ return vim.filetype.get_option(...) ]], ft, option)) + end + end + + end) end) describe('filetype.lua', function() |