" Entry point for plugin function! spellfile_local#() abort " If this is a special buffer, don't do anything if index(['nofile', 'quickfix', 'help'], &buftype) >= 0 return endif " Get the first item in the spelling languages list, bail if there aren't " any; strip any regional suffix (e.g. en_NZ), too, as the final 'spellfile' " value won't tolerate it " let spelllangs = s:OptionSplit(&spelllang) if len(spelllangs) == 0 return endif let lang = split(spelllangs[0], '_')[0] " Use current encoding let encoding = &encoding " Start a list of spellfiles let spellfiles = [] " Imitate Vim's behaviour in creating a `spell` subdir in the first " writeable element of 'runtimepath' " for runtimepath in s:OptionSplit(&runtimepath) let path = s:Path(join(add( \ split(runtimepath, '/', 1) \,'spell' \), '/'), lang, encoding) if path !=# '' call add(spellfiles, path) break endif endfor " Still no 'spellfile'? Quietly give up if len(spellfiles) == 0 return endif " Get the path to the spelling files directory let dir = fnamemodify(spellfiles[0], ':h') " Specify the name and type of spelling files we'll add, with a list of " two-key dictionaries. For each of these, the `name` is used as the " subdirectory, and the `value` as the first component of the filename. We " let types = [ \ { 'name': 'path', 'value': expand('%:p') } \,{ 'name': 'filetype', 'value': &filetype } \] " Iterate through the specified types to add them to the spelling files list for type in types " Add a new calculated path to the spellfiles list, if valid let spellfile = s:Path(dir, lang, encoding, type) if spellfile !=# '' call add(spellfiles, spellfile) endif endfor " Set the spellfile path list to the concatenated list let &spellfile = s:OptionJoin(spellfiles) endfunction " Escape a path for use as a valid spelling file name; replace any characters " not in 'isfname' with percent symbols function! s:Escape(filename) abort let filename = '' for char in split(a:filename, '\zs') if char !=# '_' && char !=# '/' && char =~# '^\f$' let filename .= char else let filename .= '%' endif endfor return filename endfunction " Ensure a directory exists, or create it function! s:Establish(path) abort return isdirectory(a:path) \ || exists('*mkdir') && mkdir(a:path, 'p', 0700) endfunction " Join a list of strings into a comma-separated option function! s:OptionJoin(list) abort return join(map( \ a:list \,'substitute(v:val, ''\\\@ 0 let type = a:1 endif " Make lists representing the directory path elements and the " period-separated filename " let dir = split(a:dir, '/', 1) let file = [a:lang, a:encoding, 'add'] " If we have an optional type, extend the directory with another element " according to its name, and insert the value before the filename, " e.g. append "filetype" to the directory, and insert the current value of " &filetype before the filename; if we have a type but a blank value, which " is not necessarily an error condition, stop here and return nothing " if exists('type') if type['value'] ==# '' return endif call add(dir, type['name']) call insert(file, type['value']) endif " Ensure the directory is in place, trying to create it if need be, and that " all of it passes an 'isfname' filter, since 'spellfile' checks that " let ds = join(dir, '/') if ds !~# '^\f\+$' \ || filewritable(ds) != 2 && !mkdir(ds, '0700', 'p') return endif " Build the full spellfile path, escaping the filename appropriately, and " return it as a path string " let path = add(copy(dir), s:Escape(join(file, '.'))) return join(path, '/') endfunction