mirror of
https://github.com/neovim/neovim.git
synced 2025-11-16 15:21:20 +00:00
refactor(spell): migrate to Lua, drop netrw dependency
Problem: Spell file downloads relied on Vimscript and netrw (:Nread). If netrw is disabled, downloads fail. Solution: Port the logic to Lua as `nvim.spellfile` and wire it via a Lua plugin that handles `SpellFileMissing`. Use `vim.net.request()` with a timeout for HTTP, prompt via `vim.fn.input` and report via `vim.notify`. Closes #7189
This commit is contained in:
committed by
Justin M. Keyes
parent
5db3544991
commit
7c5ff99e8a
@@ -1,203 +0,0 @@
|
|||||||
" Vim script to download a missing spell file
|
|
||||||
|
|
||||||
if !exists('g:spellfile_URL')
|
|
||||||
" Always use https:// because it's secure. The certificate is for nluug.nl,
|
|
||||||
" thus we can't use the alias ftp.vim.org here.
|
|
||||||
let g:spellfile_URL = 'https://ftp.nluug.nl/pub/vim/runtime/spell'
|
|
||||||
endif
|
|
||||||
let s:spellfile_URL = '' " Start with nothing so that s:donedict is reset.
|
|
||||||
|
|
||||||
" This function is used for the spellfile plugin.
|
|
||||||
function! spellfile#LoadFile(lang)
|
|
||||||
" Check for sandbox/modeline. #11359
|
|
||||||
try
|
|
||||||
:!
|
|
||||||
catch /\<E12\>/
|
|
||||||
throw 'Cannot download spellfile in sandbox/modeline. Try ":set spell" from the cmdline.'
|
|
||||||
endtry
|
|
||||||
|
|
||||||
" If the netrw plugin isn't loaded we silently skip everything.
|
|
||||||
if !exists(":Nread")
|
|
||||||
if &verbose
|
|
||||||
echomsg 'spellfile#LoadFile(): Nread command is not available.'
|
|
||||||
endif
|
|
||||||
return
|
|
||||||
endif
|
|
||||||
let lang = tolower(a:lang)
|
|
||||||
|
|
||||||
" If the URL changes we try all files again.
|
|
||||||
if s:spellfile_URL != g:spellfile_URL
|
|
||||||
let s:donedict = {}
|
|
||||||
let s:spellfile_URL = g:spellfile_URL
|
|
||||||
endif
|
|
||||||
|
|
||||||
" I will say this only once!
|
|
||||||
if has_key(s:donedict, lang . &enc)
|
|
||||||
if &verbose
|
|
||||||
echomsg 'spellfile#LoadFile(): Tried this language/encoding before.'
|
|
||||||
endif
|
|
||||||
return
|
|
||||||
endif
|
|
||||||
let s:donedict[lang . &enc] = 1
|
|
||||||
|
|
||||||
" Find spell directories we can write in.
|
|
||||||
let [dirlist, dirchoices] = spellfile#GetDirChoices()
|
|
||||||
if len(dirlist) == 0
|
|
||||||
let dir_to_create = spellfile#WritableSpellDir()
|
|
||||||
if &verbose || dir_to_create != ''
|
|
||||||
echomsg 'spellfile#LoadFile(): No (writable) spell directory found.'
|
|
||||||
endif
|
|
||||||
if dir_to_create != ''
|
|
||||||
call mkdir(dir_to_create, "p")
|
|
||||||
" Now it should show up in the list.
|
|
||||||
let [dirlist, dirchoices] = spellfile#GetDirChoices()
|
|
||||||
endif
|
|
||||||
if len(dirlist) == 0
|
|
||||||
echomsg 'Failed to create: '.dir_to_create
|
|
||||||
return
|
|
||||||
else
|
|
||||||
echomsg 'Created '.dir_to_create
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
let msg = 'No spell file for "' . a:lang . '" in ' . &enc
|
|
||||||
let msg .= "\nDownload it?"
|
|
||||||
if confirm(msg, "&Yes\n&No", 2) == 1
|
|
||||||
let enc = &encoding
|
|
||||||
if enc == 'iso-8859-15'
|
|
||||||
let enc = 'latin1'
|
|
||||||
endif
|
|
||||||
let fname = a:lang . '.' . enc . '.spl'
|
|
||||||
|
|
||||||
" Split the window, read the file into a new buffer.
|
|
||||||
" Remember the buffer number, we check it below.
|
|
||||||
new
|
|
||||||
let newbufnr = winbufnr(0)
|
|
||||||
setlocal bin fenc=
|
|
||||||
echo 'Downloading ' . fname . '...'
|
|
||||||
call spellfile#Nread(fname)
|
|
||||||
if getline(2) !~ 'VIMspell'
|
|
||||||
" Didn't work, perhaps there is an ASCII one.
|
|
||||||
" Careful: Nread() may have opened a new window for the error message,
|
|
||||||
" we need to go back to our own buffer and window.
|
|
||||||
if newbufnr != winbufnr(0)
|
|
||||||
let winnr = bufwinnr(newbufnr)
|
|
||||||
if winnr == -1
|
|
||||||
" Our buffer has vanished!? Open a new window.
|
|
||||||
echomsg "download buffer disappeared, opening a new one"
|
|
||||||
new
|
|
||||||
setlocal bin fenc=
|
|
||||||
else
|
|
||||||
exe winnr . "wincmd w"
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
if newbufnr == winbufnr(0)
|
|
||||||
" We are back to the old buffer, remove any (half-finished) download.
|
|
||||||
keeppatterns g/^/d_
|
|
||||||
else
|
|
||||||
let newbufnr = winbufnr(0)
|
|
||||||
endif
|
|
||||||
|
|
||||||
let fname = lang . '.ascii.spl'
|
|
||||||
echo 'Could not find it, trying ' . fname . '...'
|
|
||||||
call spellfile#Nread(fname)
|
|
||||||
if getline(2) !~ 'VIMspell'
|
|
||||||
echo 'Download failed'
|
|
||||||
exe newbufnr . "bwipe!"
|
|
||||||
return
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
" Delete the empty first line and mark the file unmodified.
|
|
||||||
1d_
|
|
||||||
set nomod
|
|
||||||
|
|
||||||
if len(dirlist) == 1
|
|
||||||
let dirchoice = 0
|
|
||||||
else
|
|
||||||
let msg = "In which directory do you want to write the file:"
|
|
||||||
for i in range(len(dirlist))
|
|
||||||
let msg .= "\n" . (i + 1) . '. ' . dirlist[i]
|
|
||||||
endfor
|
|
||||||
let dirchoice = confirm(msg, dirchoices) - 2
|
|
||||||
endif
|
|
||||||
if dirchoice >= 0
|
|
||||||
if exists('*fnameescape')
|
|
||||||
let dirname = fnameescape(dirlist[dirchoice])
|
|
||||||
else
|
|
||||||
let dirname = escape(dirlist[dirchoice], ' ')
|
|
||||||
endif
|
|
||||||
setlocal fenc=
|
|
||||||
exe "write " . dirname . '/' . fname
|
|
||||||
|
|
||||||
" Also download the .sug file.
|
|
||||||
keeppatterns g/^/d_
|
|
||||||
let fname = substitute(fname, '\.spl$', '.sug', '')
|
|
||||||
echo 'Downloading ' . fname . '...'
|
|
||||||
call spellfile#Nread(fname)
|
|
||||||
if getline(2) =~ 'VIMsug'
|
|
||||||
1d_
|
|
||||||
exe "write " . dirname . '/' . fname
|
|
||||||
set nomod
|
|
||||||
else
|
|
||||||
echo 'Download failed'
|
|
||||||
" Go back to our own buffer/window, Nread() may have taken us to
|
|
||||||
" another window.
|
|
||||||
if newbufnr != winbufnr(0)
|
|
||||||
let winnr = bufwinnr(newbufnr)
|
|
||||||
if winnr != -1
|
|
||||||
exe winnr . "wincmd w"
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
if newbufnr == winbufnr(0)
|
|
||||||
set nomod
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
" Wipe out the buffer we used.
|
|
||||||
exe newbufnr . "bwipe"
|
|
||||||
endif
|
|
||||||
endfunc
|
|
||||||
|
|
||||||
" Read "fname" from the server.
|
|
||||||
function! spellfile#Nread(fname)
|
|
||||||
" We do our own error handling, don't want a window for it.
|
|
||||||
if exists("g:netrw_use_errorwindow")
|
|
||||||
let save_ew = g:netrw_use_errorwindow
|
|
||||||
endif
|
|
||||||
let g:netrw_use_errorwindow=0
|
|
||||||
|
|
||||||
if g:spellfile_URL =~ '^ftp://'
|
|
||||||
" for an ftp server use a default login and password to avoid a prompt
|
|
||||||
let machine = substitute(g:spellfile_URL, 'ftp://\([^/]*\).*', '\1', '')
|
|
||||||
let dir = substitute(g:spellfile_URL, 'ftp://[^/]*/\(.*\)', '\1', '')
|
|
||||||
exe 'Nread "' . machine . ' anonymous vim7user ' . dir . '/' . a:fname . '"'
|
|
||||||
else
|
|
||||||
exe 'Nread ' g:spellfile_URL . '/' . a:fname
|
|
||||||
endif
|
|
||||||
|
|
||||||
if exists("save_ew")
|
|
||||||
let g:netrw_use_errorwindow = save_ew
|
|
||||||
else
|
|
||||||
unlet g:netrw_use_errorwindow
|
|
||||||
endif
|
|
||||||
endfunc
|
|
||||||
|
|
||||||
" Get a list of writable spell directories and choices for confirm().
|
|
||||||
function! spellfile#GetDirChoices()
|
|
||||||
let dirlist = []
|
|
||||||
let dirchoices = '&Cancel'
|
|
||||||
for dir in split(globpath(&rtp, 'spell'), "\n")
|
|
||||||
if filewritable(dir) == 2
|
|
||||||
call add(dirlist, dir)
|
|
||||||
let dirchoices .= "\n&" . len(dirlist)
|
|
||||||
endif
|
|
||||||
endfor
|
|
||||||
return [dirlist, dirchoices]
|
|
||||||
endfunc
|
|
||||||
|
|
||||||
function! spellfile#WritableSpellDir()
|
|
||||||
" Always use the $XDG_DATA_HOME/…/site directory
|
|
||||||
return stdpath('data').'/site/spell'
|
|
||||||
endfunction
|
|
||||||
278
runtime/lua/nvim/spellfile.lua
Normal file
278
runtime/lua/nvim/spellfile.lua
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
--- @class SpellfileConfig
|
||||||
|
--- @field url string
|
||||||
|
--- @field timeout_ms integer
|
||||||
|
|
||||||
|
---@class SpellInfo
|
||||||
|
---@field files string[]
|
||||||
|
---@field key string
|
||||||
|
---@field lang string
|
||||||
|
---@field encoding string
|
||||||
|
---@field dir string
|
||||||
|
|
||||||
|
---@type SpellfileConfig
|
||||||
|
M.config = {
|
||||||
|
url = 'https://ftp.nluug.nl/pub/vim/runtime/spell',
|
||||||
|
timeout_ms = 15000,
|
||||||
|
}
|
||||||
|
|
||||||
|
---@type table<string, boolean>
|
||||||
|
M._done = {}
|
||||||
|
|
||||||
|
---@return string[]
|
||||||
|
local function rtp_list()
|
||||||
|
return vim.opt.rtp:get()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.isDone(key)
|
||||||
|
return M._done[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
M._done = {}
|
||||||
|
M.config = vim.tbl_extend('force', M.config, opts or {})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function notify(msg, level)
|
||||||
|
vim.notify(msg, level or vim.log.levels.INFO)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param lang string
|
||||||
|
---@return string
|
||||||
|
local function normalize_lang(lang)
|
||||||
|
local l = (lang or ''):lower():gsub('-', '_')
|
||||||
|
return (l:match('^[^,%s]+') or l)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function writable_spell_dirs_from_rtp()
|
||||||
|
local dirs = {}
|
||||||
|
for _, dir in ipairs(rtp_list()) do
|
||||||
|
local spell = vim.fs.joinpath(vim.fn.fnamemodify(dir, ':p'), 'spell')
|
||||||
|
if vim.fn.isdirectory(spell) == 1 and vim.uv.fs_access(spell, 'W') then
|
||||||
|
table.insert(dirs, spell)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return dirs
|
||||||
|
end
|
||||||
|
|
||||||
|
local function default_spell_dir()
|
||||||
|
return vim.fs.joinpath(vim.fn.stdpath('data'), 'site', 'spell')
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ensure_target_dir()
|
||||||
|
local dirs = writable_spell_dirs_from_rtp()
|
||||||
|
if #dirs > 0 then
|
||||||
|
return dirs[1]
|
||||||
|
end
|
||||||
|
local target = default_spell_dir()
|
||||||
|
if vim.fn.isdirectory(target) ~= 1 then
|
||||||
|
vim.fn.mkdir(target, 'p')
|
||||||
|
notify('Created ' .. target)
|
||||||
|
end
|
||||||
|
return target
|
||||||
|
end
|
||||||
|
|
||||||
|
local function file_ok(path)
|
||||||
|
local s = vim.uv.fs_stat(path)
|
||||||
|
return s and s.type == 'file' and (s.size or 0) > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function reload_spell_silent()
|
||||||
|
vim.cmd('silent! setlocal spell!')
|
||||||
|
if vim.bo.spelllang and vim.bo.spelllang ~= '' then
|
||||||
|
vim.cmd('silent! setlocal spelllang=' .. vim.bo.spelllang)
|
||||||
|
end
|
||||||
|
vim.cmd('echo ""')
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Blocking GET to file with timeout; treats status==0 as success if file exists.
|
||||||
|
--- @return boolean ok, integer|nil status, string|nil err
|
||||||
|
local function http_get_to_file_sync(url, outpath, timeout_ms)
|
||||||
|
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()
|
||||||
|
return done
|
||||||
|
end, 50, false)
|
||||||
|
|
||||||
|
local status = res and res.status or 0
|
||||||
|
local ok = (not err) and ((status >= 200 and status < 300) or (status == 0 and file_ok(outpath)))
|
||||||
|
return ok, (status ~= 0 and status or nil), err
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return string[]
|
||||||
|
function M.directory_choices()
|
||||||
|
local opts = {}
|
||||||
|
for _, dir in ipairs(rtp_list()) do
|
||||||
|
local spelldir = vim.fs.joinpath(vim.fn.fnamemodify(dir, ':p'), 'spell')
|
||||||
|
if vim.fn.isdirectory(spelldir) == 1 then
|
||||||
|
table.insert(opts, spelldir)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return opts
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.choose_directory()
|
||||||
|
local dirs = writable_spell_dirs_from_rtp()
|
||||||
|
if #dirs == 0 then
|
||||||
|
return ensure_target_dir()
|
||||||
|
elseif #dirs == 1 then
|
||||||
|
return dirs[1]
|
||||||
|
end
|
||||||
|
local prompt ---@type string[]
|
||||||
|
prompt = {}
|
||||||
|
for i, d in
|
||||||
|
ipairs(dirs --[[@as string[] ]])
|
||||||
|
do
|
||||||
|
prompt[i] = string.format('%d: %s', i, d)
|
||||||
|
end
|
||||||
|
local choice = vim.fn.inputlist(prompt)
|
||||||
|
if choice < 1 or choice > #dirs then
|
||||||
|
return dirs[1]
|
||||||
|
end
|
||||||
|
return dirs[choice]
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.parse(lang)
|
||||||
|
local code = normalize_lang(lang)
|
||||||
|
local enc = 'utf-8'
|
||||||
|
local dir = ensure_target_dir()
|
||||||
|
|
||||||
|
local missing = {}
|
||||||
|
local candidates = {
|
||||||
|
string.format('%s.%s.spl', code, enc),
|
||||||
|
string.format('%s.%s.sug', code, enc),
|
||||||
|
}
|
||||||
|
for _, fn in ipairs(candidates) do
|
||||||
|
if not file_ok(vim.fs.joinpath(dir, fn)) then
|
||||||
|
table.insert(missing, fn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
files = missing,
|
||||||
|
key = code .. '.' .. enc,
|
||||||
|
lang = code,
|
||||||
|
encoding = enc,
|
||||||
|
dir = dir,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param info SpellInfo
|
||||||
|
function M.download(info)
|
||||||
|
local dir = info.dir or ensure_target_dir()
|
||||||
|
if not dir then
|
||||||
|
notify('No (writable) spell directory found and could not create one.', vim.log.levels.ERROR)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local lang = info.lang
|
||||||
|
local enc = info.encoding
|
||||||
|
|
||||||
|
local spl_utf8 = string.format('%s.%s.spl', lang, enc)
|
||||||
|
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 out_utf8 = vim.fs.joinpath(dir, spl_utf8)
|
||||||
|
notify('Downloading ' .. spl_utf8 .. ' …')
|
||||||
|
local ok, st, err = http_get_to_file_sync(url_utf8, out_utf8, M.config.timeout_ms)
|
||||||
|
if not ok then
|
||||||
|
notify(
|
||||||
|
('Could not get %s (status %s): trying %s …'):format(
|
||||||
|
spl_utf8,
|
||||||
|
tostring(st or 'nil'),
|
||||||
|
spl_ascii
|
||||||
|
)
|
||||||
|
)
|
||||||
|
local url_ascii = M.config.url .. '/' .. spl_ascii
|
||||||
|
local out_ascii = vim.fs.joinpath(dir, spl_ascii)
|
||||||
|
local ok2, st2, err2 = http_get_to_file_sync(url_ascii, out_ascii, M.config.timeout_ms)
|
||||||
|
if not ok2 then
|
||||||
|
notify(
|
||||||
|
('No spell file available for %s (utf8:%s ascii:%s) — %s'):format(
|
||||||
|
lang,
|
||||||
|
tostring(st or err or 'fail'),
|
||||||
|
tostring(st2 or err2 or 'fail'),
|
||||||
|
url_utf8
|
||||||
|
),
|
||||||
|
vim.log.levels.WARN
|
||||||
|
)
|
||||||
|
vim.schedule(function()
|
||||||
|
vim.cmd('echo ""')
|
||||||
|
end)
|
||||||
|
M._done[info.key] = true
|
||||||
|
return
|
||||||
|
end
|
||||||
|
notify('Saved ' .. spl_ascii .. ' to ' .. out_ascii)
|
||||||
|
else
|
||||||
|
notify('Saved ' .. spl_utf8 .. ' to ' .. out_utf8)
|
||||||
|
end
|
||||||
|
|
||||||
|
reload_spell_silent()
|
||||||
|
|
||||||
|
if not file_ok(vim.fs.joinpath(dir, sug_name)) then
|
||||||
|
local url_sug = M.config.url .. '/' .. sug_name
|
||||||
|
local out_sug = vim.fs.joinpath(dir, sug_name)
|
||||||
|
notify('Downloading ' .. sug_name .. ' …')
|
||||||
|
local ok3, st3, err3 = http_get_to_file_sync(url_sug, out_sug, M.config.timeout_ms)
|
||||||
|
if ok3 then
|
||||||
|
notify('Saved ' .. sug_name .. ' to ' .. out_sug)
|
||||||
|
else
|
||||||
|
local is404 = (st3 == 404) or (tostring(err3 or ''):match('%f[%d]404%f[%D]') ~= nil)
|
||||||
|
if is404 then
|
||||||
|
notify('Suggestion file not available: ' .. sug_name, vim.log.levels.DEBUG)
|
||||||
|
else
|
||||||
|
notify(
|
||||||
|
('Failed to download %s (status %s): %s'):format(
|
||||||
|
sug_name,
|
||||||
|
tostring(st3 or 'nil'),
|
||||||
|
tostring(err3 or '')
|
||||||
|
),
|
||||||
|
vim.log.levels.INFO
|
||||||
|
)
|
||||||
|
end
|
||||||
|
vim.schedule(function()
|
||||||
|
vim.cmd('echo ""')
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
M._done[info.key] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.load_file(lang)
|
||||||
|
local info = M.parse(lang)
|
||||||
|
if #info.files == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if M._done[info.key] then
|
||||||
|
notify('Already attempted spell load for ' .. lang, vim.log.levels.DEBUG)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local answer = vim.fn.input(
|
||||||
|
string.format('No spell file found for %s (%s). Download? [y/N] ', info.lang, info.encoding)
|
||||||
|
)
|
||||||
|
if (answer or ''):lower() ~= 'y' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
M.download(info)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.exists(filename)
|
||||||
|
local stat = (vim.uv or vim.loop).fs_stat
|
||||||
|
for _, dir in ipairs(M.directory_choices()) do
|
||||||
|
local p = vim.fs.joinpath(dir, filename)
|
||||||
|
local s = stat(p)
|
||||||
|
if s and s.type == 'file' then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
15
runtime/plugin/nvim/spellfile.lua
Normal file
15
runtime/plugin/nvim/spellfile.lua
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
vim.g.loaded_spellfile_plugin = true
|
||||||
|
|
||||||
|
--- Callback for SpellFileMissing: download missing .spl
|
||||||
|
--- @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 = '*',
|
||||||
|
desc = 'Download missing spell files when setting spelllang',
|
||||||
|
callback = on_spellfile_missing,
|
||||||
|
})
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
" Vim plugin for downloading spell files
|
|
||||||
|
|
||||||
if exists("loaded_spellfile_plugin") || &cp || exists("#SpellFileMissing")
|
|
||||||
finish
|
|
||||||
endif
|
|
||||||
let loaded_spellfile_plugin = 1
|
|
||||||
|
|
||||||
autocmd SpellFileMissing * call spellfile#LoadFile(expand('<amatch>'))
|
|
||||||
160
test/functional/lua/spellfile_spec.lua
Normal file
160
test/functional/lua/spellfile_spec.lua
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
local n = require('test.functional.testnvim')()
|
||||||
|
local t = require('test.testutil')
|
||||||
|
|
||||||
|
local exec = n.exec
|
||||||
|
local exec_lua = n.exec_lua
|
||||||
|
local mkdir_p = n.mkdir_p
|
||||||
|
local write_file = t.write_file
|
||||||
|
local eq = t.eq
|
||||||
|
|
||||||
|
describe('nvim.spellfile', function()
|
||||||
|
before_each(function()
|
||||||
|
n.clear()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('no-op when .spl and .sug already exist on rtp', function()
|
||||||
|
mkdir_p('Xplug/spell')
|
||||||
|
write_file('Xplug/spell/en_gb.utf-8.spl', 'dummy')
|
||||||
|
write_file('Xplug/spell/en_gb.utf-8.sug', 'dummy')
|
||||||
|
exec('set rtp+=' .. 'Xplug')
|
||||||
|
|
||||||
|
local out = exec_lua([[
|
||||||
|
local s = require('nvim.spellfile')
|
||||||
|
|
||||||
|
local my_spell = vim.fs.joinpath(vim.fn.fnamemodify('Xplug', ':p'), 'spell')
|
||||||
|
local old_access = vim.uv.fs_access
|
||||||
|
vim.uv.fs_access = function(p, mode)
|
||||||
|
return p == my_spell
|
||||||
|
end
|
||||||
|
|
||||||
|
local prompted = false
|
||||||
|
vim.fn.input = function() prompted = true; return 'n' end
|
||||||
|
|
||||||
|
local requests = 0
|
||||||
|
local orig_req = vim.net.request
|
||||||
|
vim.net.request = function(...) requests = requests + 1 end
|
||||||
|
|
||||||
|
s.load_file('en_gb')
|
||||||
|
|
||||||
|
vim.uv.fs_access = old_access
|
||||||
|
vim.net.request = orig_req
|
||||||
|
|
||||||
|
return { prompted = prompted, requests = requests }
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq(false, out.prompted)
|
||||||
|
eq(0, out.requests)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it(
|
||||||
|
'downloads UTF-8 .spl to stdpath(data)/site/spell when no rtp spelldir; .sug 404 is non-fatal; reloads',
|
||||||
|
function()
|
||||||
|
mkdir_p('Xempty')
|
||||||
|
exec('set rtp+=' .. 'Xempty')
|
||||||
|
|
||||||
|
local out = exec_lua([[
|
||||||
|
local s = require('nvim.spellfile')
|
||||||
|
|
||||||
|
local data_root = 'Xdata'
|
||||||
|
vim.fn.stdpath = function(k)
|
||||||
|
assert(k == 'data')
|
||||||
|
return data_root
|
||||||
|
end
|
||||||
|
|
||||||
|
local old_access = vim.uv.fs_access
|
||||||
|
vim.uv.fs_access = function(_, _) return false end
|
||||||
|
|
||||||
|
vim.fn.input = function() return 'y' end
|
||||||
|
|
||||||
|
local reloaded = false
|
||||||
|
local orig_cmd = vim.cmd
|
||||||
|
vim.cmd = function(c)
|
||||||
|
if c:match('setlocal%s+spell!') then reloaded = true end
|
||||||
|
return orig_cmd(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
local orig_req = vim.net.request
|
||||||
|
vim.net.request = function(url, opts, cb)
|
||||||
|
local name = url:match('/([^/]+)$')
|
||||||
|
if name and name:find('%.spl$') then
|
||||||
|
vim.fn.mkdir(vim.fs.dirname(opts.outpath), 'p')
|
||||||
|
vim.fn.writefile({'ok'}, opts.outpath)
|
||||||
|
cb(nil, { status = 200 })
|
||||||
|
else
|
||||||
|
cb(nil, { status = 404 })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
s.load_file('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')
|
||||||
|
local has_spl = vim.uv.fs_stat(spl) ~= nil
|
||||||
|
local has_sug = vim.uv.fs_stat(sug) ~= nil
|
||||||
|
|
||||||
|
vim.net.request = orig_req
|
||||||
|
vim.cmd = orig_cmd
|
||||||
|
vim.uv.fs_access = old_access
|
||||||
|
|
||||||
|
return { spl = has_spl, sug = has_sug, reloaded = reloaded }
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq(true, out.spl)
|
||||||
|
eq(false, out.sug)
|
||||||
|
eq(true, out.reloaded)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
|
it('dual-fail: UTF-8 and ASCII 404 -> warn once, mark done, no reload', function()
|
||||||
|
mkdir_p('Xempty2')
|
||||||
|
exec('set rtp+=' .. 'Xempty2')
|
||||||
|
|
||||||
|
local out = exec_lua([[
|
||||||
|
local s = require('nvim.spellfile')
|
||||||
|
|
||||||
|
local data_root = 'Xdata2'
|
||||||
|
vim.fn.stdpath = function(k)
|
||||||
|
assert(k == 'data')
|
||||||
|
return data_root
|
||||||
|
end
|
||||||
|
|
||||||
|
local old_access = vim.uv.fs_access
|
||||||
|
vim.uv.fs_access = function(_, _) return false end
|
||||||
|
local old_stat = vim.uv.fs_stat
|
||||||
|
vim.uv.fs_stat = function(p) return old_stat and old_stat(p) or nil end
|
||||||
|
|
||||||
|
vim.fn.input = function() return 'y' end
|
||||||
|
|
||||||
|
local warns = 0
|
||||||
|
local orig_notify = vim.notify
|
||||||
|
vim.notify = function(_, lvl)
|
||||||
|
if lvl and lvl >= vim.log.levels.WARN then warns = warns + 1 end
|
||||||
|
end
|
||||||
|
|
||||||
|
local reloaded = false
|
||||||
|
local orig_cmd = vim.cmd
|
||||||
|
vim.cmd = function(c)
|
||||||
|
if c:match('setlocal%s+spell!') then reloaded = true end
|
||||||
|
return orig_cmd(c)
|
||||||
|
end
|
||||||
|
|
||||||
|
local orig_req = vim.net.request
|
||||||
|
vim.net.request = function(_, _, cb) cb(nil, { status = 404 }) end
|
||||||
|
|
||||||
|
local key = s.parse('zz').key
|
||||||
|
s.load_file('zz')
|
||||||
|
local done = (s.isDone(key)) == true
|
||||||
|
|
||||||
|
vim.net.request = orig_req
|
||||||
|
vim.notify = orig_notify
|
||||||
|
vim.cmd = orig_cmd
|
||||||
|
vim.uv.fs_access = old_access
|
||||||
|
|
||||||
|
return { warns = warns, done = done, reloaded = reloaded }
|
||||||
|
]])
|
||||||
|
|
||||||
|
eq(1, out.warns)
|
||||||
|
eq(true, out.done)
|
||||||
|
eq(false, out.reloaded)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
Reference in New Issue
Block a user