mirror of
https://github.com/neovim/neovim.git
synced 2025-11-14 06:18:50 +00:00
refactor(spellfile): config() interface, docs #36481
Problem: - Exposing the raw config as table is a pattern not seen anywhere else in the Nvim codebase. - Old spellfile.vim docs still available, no new documentation Solution: - Exposing a `config()` function that both acts as "getter" and "setter" is a much more common idiom (e.g. vim.lsp, vim.diagnostic). - Add new documentation and link old docs to |spellfile.lua| instead of |spellfile.vim|.
This commit is contained in:
committed by
GitHub
parent
cf347110c1
commit
9bdb011a50
@@ -6107,7 +6107,7 @@ A jump table for the options with a short description can be found at |Q_op|.
|
||||
encoding is used, Vim doesn't check it.
|
||||
How the related spell files are found is explained here: |spell-load|.
|
||||
|
||||
If the |spellfile.vim| plugin is active and you use a language name
|
||||
If the |spellfile.lua| plugin is active and you use a language name
|
||||
for which Vim cannot find the .spl file in 'runtimepath' the plugin
|
||||
will ask you if you want to download the file.
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ loaded by default while others are not loaded until requested by |:packadd|.
|
||||
|
||||
==============================================================================
|
||||
Standard plugins ~
|
||||
*standard-plugin-list*
|
||||
*standard-plugin-list*
|
||||
Help-link Loaded Short description ~
|
||||
|difftool| No Compares two directories or files side-by-side
|
||||
|editorconfig| Yes Detect and interpret editorconfig
|
||||
@@ -37,7 +37,7 @@ Help-link Loaded Short description ~
|
||||
|pi_tar.txt| Yes Tar file explorer
|
||||
|pi_tutor.txt| Yes Interactive tutorial
|
||||
|pi_zip.txt| Yes Zip archive explorer
|
||||
|spellfile.vim| Yes Install spellfile if missing
|
||||
|spellfile.lua| Yes Install spellfile if missing
|
||||
|tohtml| Yes Convert buffer to html, syntax included
|
||||
|undotree| No Interactive textual undotree
|
||||
|
||||
@@ -166,6 +166,54 @@ trim_trailing_whitespace *editorconfig.trim_trailing_whitespace*
|
||||
buffer is written.
|
||||
|
||||
|
||||
==============================================================================
|
||||
Builtin plugin: spellfile *spellfile.lua*
|
||||
|
||||
Asks the user to download missing spellfiles. The spellfile is written to
|
||||
`stdpath('data') .. 'site/spell'` or the first writable directory in the
|
||||
'runtimepath'.
|
||||
|
||||
The plugin can be disabled by setting `g:loaded_spellfile_plugin = 1`.
|
||||
|
||||
|
||||
*nvim.spellfile.Opts*
|
||||
A table with the following fields:
|
||||
|
||||
Fields: ~
|
||||
• {url} (`string`) The base URL from where the spellfiles are
|
||||
downloaded. Uses `g:spellfile_URL` if it's set,
|
||||
otherwise https://ftp.nluug.nl/pub/vim/runtime/spell.
|
||||
• {timeout_ms} (`integer`, default: 15000) Number of milliseconds after
|
||||
which the |vim.net.request()| times out.
|
||||
|
||||
|
||||
config({opts}) *spellfile.config()*
|
||||
Configure spellfile download options. For example: >lua
|
||||
require('nvim.spellfile').config({ url = '...' })
|
||||
<
|
||||
|
||||
Parameters: ~
|
||||
• {opts} (`nvim.spellfile.Opts?`) When omitted or `nil`, retrieve the
|
||||
current configuration. Otherwise, a configuration table.
|
||||
|
||||
Return: ~
|
||||
(`nvim.spellfile.Opts?`) Current config if {opts} is omitted.
|
||||
|
||||
get({lang}) *spellfile.get()*
|
||||
Download spellfiles for language {lang} if available.
|
||||
|
||||
Parameters: ~
|
||||
• {lang} (`string`) Language code.
|
||||
|
||||
Return: ~
|
||||
(`table?`) A table with the following fields:
|
||||
• {files} (`string[]`)
|
||||
• {key} (`string`)
|
||||
• {lang} (`string`)
|
||||
• {encoding} (`string`)
|
||||
• {dir} (`string`)
|
||||
|
||||
|
||||
==============================================================================
|
||||
Builtin plugin: tohtml *tohtml*
|
||||
|
||||
|
||||
@@ -313,10 +313,6 @@ Only the first file is loaded, the one that is first in 'runtimepath'. If
|
||||
this succeeds then additionally files with the name LL.EEE.add.spl are loaded.
|
||||
All the ones that are found are used.
|
||||
|
||||
If no spell file is found the |SpellFileMissing| autocommand event is
|
||||
triggered. This may trigger the |spellfile.vim| plugin to offer you
|
||||
downloading the spell file.
|
||||
|
||||
Additionally, the files related to the names in 'spellfile' are loaded. These
|
||||
are the files that |zg| and |zw| add good and wrong words to.
|
||||
|
||||
@@ -640,48 +636,11 @@ Comment lines with the name of the .spl file are used as a header above the
|
||||
words that were generated from that .spl file.
|
||||
|
||||
|
||||
SPELL FILE MISSING *spell-SpellFileMissing* *spellfile.vim*
|
||||
SPELL FILE MISSING *spell-SpellFileMissing*
|
||||
|
||||
If the spell file for the language you are using is not available, you will
|
||||
get an error message. But if the "spellfile.vim" plugin is active it will
|
||||
offer you to download the spell file. Just follow the instructions, it will
|
||||
ask you where to write the file (there must be a writable directory in
|
||||
'runtimepath' for this).
|
||||
If a spell file is missing, the user is asked whether to download it. See
|
||||
|spellfile.lua|.
|
||||
|
||||
The plugin has a default place where to look for spell files, on the Vim ftp
|
||||
server. The protocol used is TLS (`https://`) for security. If you want to
|
||||
use another location or another protocol, set the g:spellfile_URL variable to
|
||||
the directory that holds the spell files. You can use `http://` or `ftp://`,
|
||||
but you are taking a security risk then. The |netrw| plugin is used for
|
||||
getting the file, look there for the specific syntax of the URL. Example: >
|
||||
let g:spellfile_URL = 'https://ftp.nluug.nl/vim/runtime/spell'
|
||||
You may need to escape special characters.
|
||||
|
||||
The plugin will only ask about downloading a language once. If you want to
|
||||
try again anyway restart Vim, or set g:spellfile_URL to another value (e.g.,
|
||||
prepend a space).
|
||||
|
||||
To avoid using the "spellfile.vim" plugin do this in your vimrc file: >
|
||||
|
||||
let loaded_spellfile_plugin = 1
|
||||
|
||||
Instead of using the plugin you can define a |SpellFileMissing| autocommand to
|
||||
handle the missing file yourself. You can use it like this: >
|
||||
|
||||
:au SpellFileMissing * call Download_spell_file(expand('<amatch>'))
|
||||
|
||||
Thus the <amatch> item contains the name of the language. Another important
|
||||
value is 'encoding', since every encoding has its own spell file. With two
|
||||
exceptions:
|
||||
- For ISO-8859-15 (latin9) the name "latin1" is used (the encodings only
|
||||
differ in characters not used in dictionary words).
|
||||
- The name "ascii" may also be used for some languages where the words use
|
||||
only ASCII letters for most of the words.
|
||||
|
||||
The default "spellfile.vim" plugin uses this autocommand, if you define your
|
||||
autocommand afterwards you may want to use ":au! SpellFileMissing" to overrule
|
||||
it. If you define your autocommand before the plugin is loaded it will notice
|
||||
this and not do anything.
|
||||
*E797*
|
||||
Note that the SpellFileMissing autocommand must not change or destroy the
|
||||
buffer the user was editing.
|
||||
|
||||
@@ -97,6 +97,8 @@ Defaults *defaults* *nvim-defaults*
|
||||
- |man.lua| plugin is enabled, so |:Man| is available by default.
|
||||
- |matchit| plugin is enabled. To disable it in your config: >vim
|
||||
:let loaded_matchit = 1
|
||||
- |spellfile.lua| plugin is enabled, spellfiles are installed by default if
|
||||
missing.
|
||||
|
||||
- |g:vimsyn_embed| defaults to "l" to enable Lua highlighting
|
||||
|
||||
@@ -741,6 +743,7 @@ Editor:
|
||||
- *cscope* support was removed in favour of plugin-based solutions such as:
|
||||
https://github.com/dhananjaylatkar/cscope_maps.nvim
|
||||
- *popup-window* : Use |floating-windows| instead.
|
||||
- *spellfile.vim* : Replaced by |spellfile.lua|.
|
||||
- *textprop* : Use |extmarks| instead.
|
||||
|
||||
Eval:
|
||||
|
||||
@@ -1,22 +1,56 @@
|
||||
--- @brief
|
||||
--- Asks the user to download missing spellfiles. The spellfile is written to
|
||||
--- `stdpath('data') .. 'site/spell'` or the first writable directory in the
|
||||
--- 'runtimepath'.
|
||||
---
|
||||
--- The plugin can be disabled by setting `g:loaded_spellfile_plugin = 1`.
|
||||
|
||||
local M = {}
|
||||
|
||||
--- @class vim.spellfile.Config
|
||||
--- @class nvim.spellfile.Info
|
||||
--- @inlinedoc
|
||||
--- @field files string[]
|
||||
--- @field key string
|
||||
--- @field lang string
|
||||
--- @field encoding string
|
||||
--- @field dir string
|
||||
|
||||
--- A table with the following fields:
|
||||
--- @class nvim.spellfile.Opts
|
||||
---
|
||||
--- The base URL from where the spellfiles are downloaded. Uses `g:spellfile_URL`
|
||||
--- if it's set, otherwise https://ftp.nluug.nl/pub/vim/runtime/spell.
|
||||
--- @field url string
|
||||
---
|
||||
--- Number of milliseconds after which the [vim.net.request()] times out.
|
||||
--- (default: 15000)
|
||||
--- @field timeout_ms integer
|
||||
|
||||
---@class vim.spellfile.Info
|
||||
---@field files string[]
|
||||
---@field key string
|
||||
---@field lang string
|
||||
---@field encoding string
|
||||
---@field dir string
|
||||
|
||||
---@type vim.spellfile.Config
|
||||
M.config = {
|
||||
url = 'https://ftp.nluug.nl/pub/vim/runtime/spell',
|
||||
--- @type nvim.spellfile.Opts
|
||||
local config = {
|
||||
url = vim.g.spellfile_URL or 'https://ftp.nluug.nl/pub/vim/runtime/spell',
|
||||
timeout_ms = 15000,
|
||||
}
|
||||
|
||||
--- Configure spellfile download options. For example:
|
||||
--- ```lua
|
||||
--- require('nvim.spellfile').config({ url = '...' })
|
||||
--- ```
|
||||
--- @param opts nvim.spellfile.Opts? When omitted or `nil`, retrieve the
|
||||
--- current configuration. Otherwise, a configuration table.
|
||||
--- @return nvim.spellfile.Opts? : Current config if {opts} is omitted.
|
||||
function M.config(opts)
|
||||
vim.validate('opts', opts, 'table', true)
|
||||
if not opts then
|
||||
return vim.deepcopy(config, true)
|
||||
end
|
||||
for k, v in
|
||||
pairs(opts --[[@as table<any,any>]])
|
||||
do
|
||||
config[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
--- TODO(justinmk): add on_done/on_err callbacks to download(), instead of exposing this?
|
||||
---@type table<string, boolean>
|
||||
M._done = {}
|
||||
@@ -26,6 +60,8 @@ local function rtp_list()
|
||||
return vim.opt.rtp:get()
|
||||
end
|
||||
|
||||
---@param msg string
|
||||
---@param level vim.log.levels?
|
||||
local function notify(msg, level)
|
||||
vim.notify(msg, level or vim.log.levels.INFO)
|
||||
end
|
||||
@@ -37,15 +73,20 @@ local function normalize_lang(lang)
|
||||
return (l:match('^[^,%s]+') or l)
|
||||
end
|
||||
|
||||
---@param path string
|
||||
---@return boolean
|
||||
local function file_ok(path)
|
||||
local s = vim.uv.fs_stat(path)
|
||||
return s and s.type == 'file' and (s.size or 0) > 0
|
||||
return s ~= nil and s.type == 'file' and (s.size or 0) > 0
|
||||
end
|
||||
|
||||
---@param dir string
|
||||
---@return boolean
|
||||
local function can_use_dir(dir)
|
||||
return not not (vim.fn.isdirectory(dir) == 1 and vim.uv.fs_access(dir, 'W'))
|
||||
end
|
||||
|
||||
---@return string[]
|
||||
local function writable_spell_dirs_from_rtp()
|
||||
local dirs = {}
|
||||
for _, dir in ipairs(rtp_list()) do
|
||||
@@ -57,6 +98,7 @@ local function writable_spell_dirs_from_rtp()
|
||||
return dirs
|
||||
end
|
||||
|
||||
---@return string?
|
||||
local function ensure_target_dir()
|
||||
local dir = vim.fs.abspath(vim.fs.joinpath(vim.fn.stdpath('data'), 'site/spell'))
|
||||
if vim.fn.isdirectory(dir) == 0 and pcall(vim.fn.mkdir, dir, 'p') then
|
||||
@@ -88,13 +130,15 @@ end
|
||||
---
|
||||
--- Treats status==0 as success if file exists.
|
||||
---
|
||||
--- @param url string
|
||||
--- @param outpath string
|
||||
--- @return boolean ok, integer|nil status, string|nil err
|
||||
local function fetch_file_sync(url, outpath, timeout_ms)
|
||||
local function fetch_file_sync(url, outpath)
|
||||
local done, err, res = false, nil, nil
|
||||
vim.net.request(url, { outpath = outpath }, function(e, r)
|
||||
err, res, done = e, r, true
|
||||
end)
|
||||
vim.wait(timeout_ms or M.config.timeout_ms, function()
|
||||
vim.wait(config.timeout_ms, function()
|
||||
return done
|
||||
end, 50, false)
|
||||
|
||||
@@ -103,6 +147,8 @@ local function fetch_file_sync(url, outpath, timeout_ms)
|
||||
return not not ok, (status ~= 0 and status or nil), err
|
||||
end
|
||||
|
||||
---@param lang string
|
||||
---@return nvim.spellfile.Info
|
||||
local function parse(lang)
|
||||
local code = normalize_lang(lang)
|
||||
local enc = 'utf-8'
|
||||
@@ -128,7 +174,7 @@ local function parse(lang)
|
||||
}
|
||||
end
|
||||
|
||||
---@param info vim.spellfile.Info
|
||||
---@param info nvim.spellfile.Info
|
||||
local function download(info)
|
||||
local dir = info.dir or ensure_target_dir()
|
||||
if not dir then
|
||||
@@ -143,10 +189,10 @@ local function download(info)
|
||||
local spl_ascii = string.format('%s.ascii.spl', lang)
|
||||
local sug_name = string.format('%s.%s.sug', lang, enc)
|
||||
|
||||
local url_utf8 = M.config.url .. '/' .. spl_utf8
|
||||
local url_utf8 = config.url .. '/' .. spl_utf8
|
||||
local out_utf8 = vim.fs.joinpath(dir, spl_utf8)
|
||||
notify('Downloading ' .. spl_utf8 .. ' …')
|
||||
local ok, st, err = fetch_file_sync(url_utf8, out_utf8, M.config.timeout_ms)
|
||||
local ok, st, err = fetch_file_sync(url_utf8, out_utf8)
|
||||
if not ok then
|
||||
notify(
|
||||
('Could not get %s (status %s): trying %s …'):format(
|
||||
@@ -155,9 +201,9 @@ local function download(info)
|
||||
spl_ascii
|
||||
)
|
||||
)
|
||||
local url_ascii = M.config.url .. '/' .. spl_ascii
|
||||
local url_ascii = config.url .. '/' .. spl_ascii
|
||||
local out_ascii = vim.fs.joinpath(dir, spl_ascii)
|
||||
local ok2, st2, err2 = fetch_file_sync(url_ascii, out_ascii, M.config.timeout_ms)
|
||||
local ok2, st2, err2 = fetch_file_sync(url_ascii, out_ascii)
|
||||
if not ok2 then
|
||||
notify(
|
||||
('No spell file available for %s (utf8:%s ascii:%s) — %s'):format(
|
||||
@@ -182,10 +228,10 @@ local function download(info)
|
||||
reload_spell_silent()
|
||||
|
||||
if not file_ok(vim.fs.joinpath(dir, sug_name)) then
|
||||
local url_sug = M.config.url .. '/' .. sug_name
|
||||
local url_sug = config.url .. '/' .. sug_name
|
||||
local out_sug = vim.fs.joinpath(dir, sug_name)
|
||||
notify('Downloading ' .. sug_name .. ' …')
|
||||
local ok3, st3, err3 = fetch_file_sync(url_sug, out_sug, M.config.timeout_ms)
|
||||
local ok3, st3, err3 = fetch_file_sync(url_sug, out_sug)
|
||||
if ok3 then
|
||||
notify('Saved ' .. sug_name .. ' to ' .. out_sug)
|
||||
else
|
||||
@@ -211,7 +257,10 @@ local function download(info)
|
||||
M._done[info.key] = true
|
||||
end
|
||||
|
||||
function M.load_file(lang)
|
||||
--- Download spellfiles for language {lang} if available.
|
||||
--- @param lang string Language code.
|
||||
--- @return nvim.spellfile.Info?
|
||||
function M.get(lang)
|
||||
local info = parse(lang)
|
||||
if #info.files == 0 then
|
||||
return
|
||||
@@ -221,14 +270,18 @@ function M.load_file(lang)
|
||||
return
|
||||
end
|
||||
|
||||
local answer = vim.fn.input(
|
||||
string.format('No spell file found for %s (%s). Download? [y/N] ', info.lang, info.encoding)
|
||||
local prompt = ('No spell file found for %s (%s). Download? [y/N] '):format(
|
||||
info.lang,
|
||||
info.encoding
|
||||
)
|
||||
if (answer or ''):lower() ~= 'y' then
|
||||
return
|
||||
end
|
||||
|
||||
download(info)
|
||||
vim.ui.input({ prompt = prompt }, function(input)
|
||||
-- properly clear the message window
|
||||
vim.api.nvim_echo({ { ' ' } }, false, { kind = 'empty' })
|
||||
if not input or input:lower() ~= 'y' then
|
||||
return
|
||||
end
|
||||
download(info)
|
||||
end)
|
||||
|
||||
return info
|
||||
end
|
||||
|
||||
2
runtime/lua/vim/_meta/options.lua
generated
2
runtime/lua/vim/_meta/options.lua
generated
@@ -6527,7 +6527,7 @@ vim.bo.spf = vim.bo.spellfile
|
||||
--- encoding is used, Vim doesn't check it.
|
||||
--- How the related spell files are found is explained here: `spell-load`.
|
||||
---
|
||||
--- If the `spellfile.vim` plugin is active and you use a language name
|
||||
--- If the `spellfile.lua` plugin is active and you use a language name
|
||||
--- for which Vim cannot find the .spl file in 'runtimepath' the plugin
|
||||
--- will ask you if you want to download the file.
|
||||
---
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
if vim.g.loaded_spellfile_plugin ~= nil then
|
||||
return
|
||||
end
|
||||
vim.g.loaded_spellfile_plugin = true
|
||||
|
||||
--- Downloads missing .spl file.
|
||||
---
|
||||
--- @param args { bufnr: integer, match: string }
|
||||
local function on_spellfile_missing(args)
|
||||
local spellfile = require('nvim.spellfile')
|
||||
spellfile.load_file(args.match)
|
||||
end
|
||||
|
||||
vim.api.nvim_create_autocmd('SpellFileMissing', {
|
||||
group = vim.api.nvim_create_augroup('nvim_spellfile', { clear = true }),
|
||||
pattern = '*',
|
||||
group = vim.api.nvim_create_augroup('nvim.spellfile', {}),
|
||||
desc = 'Download missing spell files when setting spelllang',
|
||||
callback = on_spellfile_missing,
|
||||
callback = function(args)
|
||||
require('nvim.spellfile').get(args.match)
|
||||
end,
|
||||
})
|
||||
|
||||
@@ -415,6 +415,7 @@ local config = {
|
||||
section_order = {
|
||||
'difftool.lua',
|
||||
'editorconfig.lua',
|
||||
'spellfile.lua',
|
||||
'tohtml.lua',
|
||||
'undotree.lua',
|
||||
},
|
||||
@@ -423,6 +424,7 @@ local config = {
|
||||
'runtime/lua/tohtml.lua',
|
||||
'runtime/pack/dist/opt/nvim.undotree/lua/undotree.lua',
|
||||
'runtime/pack/dist/opt/nvim.difftool/lua/difftool.lua',
|
||||
'runtime/lua/nvim/spellfile.lua',
|
||||
},
|
||||
fn_xform = function(fun)
|
||||
if fun.module == 'editorconfig' then
|
||||
@@ -430,11 +432,17 @@ local config = {
|
||||
fun.table = true
|
||||
fun.name = vim.split(fun.name, '.', { plain = true })[2] or fun.name
|
||||
end
|
||||
if vim.startswith(fun.module, 'nvim.') then
|
||||
fun.module = fun.module:sub(#'nvim.' + 1)
|
||||
end
|
||||
end,
|
||||
section_fmt = function(name)
|
||||
return 'Builtin plugin: ' .. name:lower()
|
||||
end,
|
||||
helptag_fmt = function(name)
|
||||
if name:lower() == 'spellfile' then
|
||||
name = 'spellfile.lua'
|
||||
end
|
||||
return name:lower()
|
||||
end,
|
||||
},
|
||||
|
||||
@@ -8547,7 +8547,7 @@ local options = {
|
||||
encoding is used, Vim doesn't check it.
|
||||
How the related spell files are found is explained here: |spell-load|.
|
||||
|
||||
If the |spellfile.vim| plugin is active and you use a language name
|
||||
If the |spellfile.lua| plugin is active and you use a language name
|
||||
for which Vim cannot find the .spl file in 'runtimepath' the plugin
|
||||
will ask you if you want to download the file.
|
||||
|
||||
|
||||
@@ -1610,7 +1610,7 @@ static void spell_load_lang(char *lang)
|
||||
// Plugins aren't loaded yet, so nvim/spellfile.lua cannot handle this case.
|
||||
char autocmd_buf[512] = { 0 };
|
||||
snprintf(autocmd_buf, sizeof(autocmd_buf),
|
||||
"autocmd VimEnter * call v:lua.require'nvim.spellfile'.load_file('%s')|set spell",
|
||||
"autocmd VimEnter * call v:lua.require'nvim.spellfile'.get('%s')|set spell",
|
||||
lang);
|
||||
do_cmdline_cmd(autocmd_buf);
|
||||
} else {
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('nvim.spellfile', function()
|
||||
local requests = 0
|
||||
vim.net.request = function(...) requests = requests + 1 end
|
||||
|
||||
s.load_file('en_gb')
|
||||
s.get('en_gb')
|
||||
|
||||
return { prompted = prompted, requests = requests }
|
||||
]],
|
||||
@@ -88,7 +88,7 @@ describe('nvim.spellfile', function()
|
||||
end
|
||||
end
|
||||
|
||||
s.load_file('en_gb')
|
||||
s.get('en_gb')
|
||||
|
||||
local spl = vim.fs.joinpath(data_root, 'site/spell/en_gb.utf-8.spl')
|
||||
local sug = vim.fs.joinpath(data_root, 'site/spell/en_gb.utf-8.sug')
|
||||
@@ -136,7 +136,7 @@ describe('nvim.spellfile', function()
|
||||
|
||||
vim.net.request = function(_, _, cb) cb(nil, { status = 404 }) end
|
||||
|
||||
local info = s.load_file('zz')
|
||||
local info = s.get('zz')
|
||||
local done = s._done[info.key] == true
|
||||
|
||||
return { warns = warns, done = done, did_reload = did_reload }
|
||||
|
||||
Reference in New Issue
Block a user