This commit is contained in:
Eduardo Cuducos 2024-09-10 08:28:03 -07:00 committed by GitHub
commit 493cf79a11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 427 additions and 211 deletions

View File

@ -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

135
runtime/lua/spellfile.lua Normal file
View File

@ -0,0 +1,135 @@
local M = {}
M.config = {
url = 'https://ftp.nluug.nl/pub/vim/runtime/spell',
encoding = vim.o.encoding,
rtp = vim.opt.rtp:get(),
}
M.done = {}
M.setup = function(opts)
M.done = {}
M.config = vim.tbl_extend('force', M.config, opts or {})
end
M.directory_choices = function()
local options = {}
for _, dir in ipairs(M.config.rtp) do
local spell = dir .. '/spell'
if vim.fn.isdirectory(spell) == 1 then
table.insert(options, spell)
end
end
return options
end
M.choose_directory = function()
local options = M.directory_choices()
if #options == 0 then
vim.notify('No spell directory found in the runtimepath')
return
elseif #options == 1 then
return options[1]
end
local prompt = {}
for idx, dir in pairs(options) do
table.insert(prompt, string.format('%d: %s', idx, dir))
end
local choice = vim.ui.inputlist(prompt)
if choice <= 0 or choice > #options then
return
end
return options[choice]
end
M.exists = function(file_name)
local is_file = function(pth)
local stat = vim.uv.fs_stat(pth)
return stat ~= nil and stat.type ~= nil and stat.type == 'file'
end
for _, dir in pairs(M.directory_choices()) do
if is_file(dir .. '/' .. file_name) then
return true
end
end
return false
end
M.parse = function(lang)
local code = string.sub(lang:lower(), 1, 2)
local encoding = vim.bo.fileencoding
if encoding == '' then
encoding = M.config.encoding
end
if encoding == 'iso-8859-1' then
encoding = 'latin1'
end
local files = {
string.format('%s.%s.spl', code, encoding),
string.format('%s.%s.sug', code, encoding),
}
local missing = {}
for _, name in ipairs(files) do
if not M.exists(name) then
table.insert(missing, name)
end
end
return {
files = missing,
key = string.format('%s.%s', code, encoding),
lang = code,
encoding = encoding,
}
end
M.download = function(data)
local prompt =
string.format('No spell file found for %s in %s. Download it? [y/N] ', data.lang, data.encoding)
if vim.fn.input(prompt):lower() ~= 'y' then
return
end
local dir = M.choose_directory()
if dir == nil then
return
end
for _, name in ipairs(data.files) do
local url = M.config.url .. '/' .. name
local pth = dir .. '/' .. name
local cmd = ''
if vim.fn.executable('curl') == 1 then
cmd = string.format('curl -fLo %s %s', pth, url)
else
vim.notify('No curl found. You need curl installed to dowloand spell files.')
return
end
vim.notify(string.format('\nDownloading %s...', name))
vim.system(cmd)
end
end
M.load_file = function(lang)
local data = M.parse(lang)
if #data.files == 0 then
return
end
for key, _ in pairs(M.done) do
if key == data.key then
vim.notify('Already tried this language before: ' .. lang:lower())
return
end
end
M.download(data)
M.done[data.key] = true
end
return M

View File

@ -0,0 +1,6 @@
vim.api.nvim_create_autocmd('SpellFileMissing', {
callback = function(args)
local lang = vim.bo[args.bufnr].spelllang
require('spellfile').load_file(lang)
end,
})

View File

@ -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>'))

View File

@ -0,0 +1,286 @@
local stub = require('luassert.stub')
describe('Setup', function()
after_each(function()
package.loaded['spellfile'] = nil
end)
it('loads with default config', function()
local spellfile = require('spellfile')
assert.are.same(spellfile.config.url, 'https://ftp.nluug.nl/pub/vim/runtime/spell')
end)
it('loads with custom config', function()
local spellfile = require('spellfile')
spellfile.setup({ url = '42', encoding = 'iso-8859-1' })
assert.are.same(spellfile.config.url, '42')
assert.are.same(spellfile.config.encoding, 'iso-8859-1')
end)
end)
describe('Load file', function()
before_each(function()
local spellfile = require('spellfile')
spellfile.download = function() end
spellfile.exists = function() end
end)
after_each(function()
package.loaded['spellfile'] = nil
end)
it('adds current language to the done table', function()
local spellfile = require('spellfile')
spellfile.load_file('en')
assert.are.same({ ['en.utf-8'] = true }, spellfile.done)
end)
it('does not retry a language', function()
local spellfile = require('spellfile')
spellfile.done['en.utf-8'] = true
local notify = stub(vim, 'notify')
spellfile.load_file('En')
assert.stub(notify).was_called_with('Already tried this language before: en')
assert.are.same({ ['en.utf-8'] = true }, spellfile.done)
end)
end)
describe('Directory choices function', function()
before_each(function()
local spellfile = require('spellfile')
spellfile.config.rtp = { '/tmp' }
end)
it('returns at least one directory', function()
local spellfile = require('spellfile')
vim.fn.isdirectory = function()
return 1
end
local choices = spellfile.directory_choices()
assert.is_true(#choices >= 1)
end)
end)
describe('Parse', function()
before_each(function()
local spellfile = require('spellfile')
spellfile.config.rtp = { '/tmp' }
vim.loop.fs_stat = function(pth)
if pth == '/tmp/spell/en.utf-8.sug' then
return { type = 'file' }
end
return nil
end
end)
it('returns the correct encoding', function()
local spellfile = require('spellfile')
local data = spellfile.parse('en')
assert.are.same('utf-8', data.encoding)
end)
it('returns the correct language code', function()
local spellfile = require('spellfile')
local data = spellfile.parse('EN')
assert.are.same('en', data.lang)
end)
it('returns the correct file name', function()
local spellfile = require('spellfile')
local data = spellfile.parse('en')
assert.are.same({ 'en.utf-8.spl' }, data.files)
end)
it('discards variation/region keeping only the language code', function()
local spellfile = require('spellfile')
local data = spellfile.parse('en_US')
assert.are.same('en', data.lang)
assert.are.same({ 'en.utf-8.spl' }, data.files)
end)
end)
describe('Exists function', function()
before_each(function()
local spellfile = require('spellfile')
spellfile.config.rtp = { '/tmp' }
vim.loop.fs_stat = function(pth)
if pth == '/tmp/spell/en.utf-8.spl' then
return { type = 'file' }
end
return nil
end
end)
it('returns true when the spell file exists', function()
local spellfile = require('spellfile')
vim.loop.fs_stat = function()
return { type = 'file' }
end
assert.is_true(spellfile.exists('en.utf-8.spl'))
end)
it('returns false when the spell file exists', function()
local spellfile = require('spellfile')
vim.loop.fs_stat = function()
return nil
end
assert.is_false(spellfile.exists('en.utf-42.spl'))
end)
end)
describe('Download', function()
before_each(function()
local spellfile = require('spellfile')
spellfile.config.rtp = { '/tmp' }
vim.fn.input = function()
return 'y'
end
end)
it('downloads the file using curl', function()
local spellfile = require('spellfile')
local notify = stub(vim, 'notify')
local system = stub(vim.fn, 'system')
vim.fn.executable = function(name)
if name == 'curl' then
return 1
end
return 0
end
local data = {
files = { 'en.utf-8.spl' },
lang = 'en',
encoding = 'utf-8',
}
spellfile.download(data)
assert.stub(notify).was_called_with('\nDownloading en.utf-8.spl...')
assert
.stub(system)
.was_called_with('curl -fLo /tmp/spell/en.utf-8.spl https://ftp.nluug.nl/pub/vim/runtime/spell/en.utf-8.spl')
end)
it('downloads the file using wget', function()
local spellfile = require('spellfile')
local notify = stub(vim, 'notify')
local system = stub(vim.fn, 'system')
vim.fn.executable = function(name)
if name == 'wget' then
return 1
end
return 0
end
local data = {
files = { 'en.utf-8.spl' },
lang = 'en',
encoding = 'utf-8',
}
spellfile.download(data)
assert.stub(notify).was_called_with('\nDownloading en.utf-8.spl...')
assert
.stub(system)
.was_called_with('wget -O /tmp/spell/en.utf-8.spl https://ftp.nluug.nl/pub/vim/runtime/spell/en.utf-8.spl')
end)
it('shows error when there is no curl or wget', function()
local spellfile = require('spellfile')
local notify = stub(vim, 'notify')
local system = stub(vim.fn, 'system')
vim.fn.executable = function(name)
return 0
end
local data = {
files = { 'en.utf-8.spl' },
lang = 'en',
encoding = 'utf-8',
}
spellfile.download(data)
assert.stub(notify).was_called_with('No curl or wget found. Please install one of them.')
assert.stub(system).was_not_called()
end)
it('asks for confirmation and cancels if user does not confirm', function()
local spellfile = require('spellfile')
local notify = stub(vim, 'notify')
local system = stub(vim.fn, 'system')
vim.fn.input = function()
return 'n'
end
local data = {
files = { 'en.utf-8.spl' },
lang = 'en',
encoding = 'utf-8',
}
spellfile.download(data)
assert.stub(notify).was_not_called()
assert.stub(system).was_not_called()
end)
it('asks for confirmation and proceeds if user confirms', function()
local spellfile = require('spellfile')
local notify = stub(vim, 'notify')
local system = stub(vim.fn, 'system')
vim.fn.executable = function(name)
return 0
end
local data = {
files = { 'en.utf-8.spl' },
lang = 'en',
encoding = 'utf-8',
}
spellfile.download(data)
assert.stub(notify).was_called_with('No curl or wget found. Please install one of them.')
assert.stub(system).was_not_called()
end)
end)
describe('Choose directory', function()
it('shows notification when there are no directories', function()
local notify = stub(vim, 'notify')
local spellfile = require('spellfile')
spellfile.config.rtp = {}
assert.is_nil(spellfile.choose_directory())
assert.stub(notify).was_called_with('No spell directory found in the runtimepath')
end)
it('returns the first directory when there is only one', function()
local spellfile = require('spellfile')
spellfile.config.rtp = { '/tmp' }
local choice = spellfile.choose_directory()
assert.are.same('/tmp/spell', choice)
end)
it('asks for user input when there are multiple directories', function()
local spellfile = require('spellfile')
spellfile.config.rtp = { '/tmp/1', '/tmp2' }
vim.fn.inputlist = function()
return 1
end
assert.are.same('/tmp/1/spell', spellfile.choose_directory())
end)
it('returns nil when user chooses a non-existent option', function()
local spellfile = require('spellfile')
spellfile.config.rtp = { '/tmp/1', '/tmp2' }
vim.fn.inputlist = function()
return 42
end
assert.is_nil(spellfile.choose_directory())
end)
end)