aboutsummaryrefslogblamecommitdiff
path: root/autoload/spellfile_local.vim
blob: 92ace4cba4e71c65a01bfb1f37f541a76af8ce4f (plain) (tree)




























                                                                              

                                       
















                                                                         
                                                                         

               







                                   















                                                                              


                                                                    







                              

       
























                                                                             

                                                           





                                                       

                                                    






































                                                                              
                                                           









                                                                           
" 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.
  "
  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)

  " Remap the internal word list mappings to use the second entry in
  " the new local 'spellfile'
  if get(g:, 'spellfile_local_remap_internal', 1)
    nnoremap <buffer> zG 2zg
    xnoremap <buffer> zG 2zg
    nnoremap <buffer> zW 2zw
    xnoremap <buffer> zW 2zw
    nnoremap <buffer> zuG 2zug
    xnoremap <buffer> zuG 2zug
    nnoremap <buffer> zuW 2zuw
    xnoremap <buffer> zuW 2zuw
  endif

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, ''\\\@<!,'', ''\\,'', ''g'')',
        \), ',')
endfunction

" Split a comma-separated option into a list of strings
function! s:OptionSplit(string) abort
  return map(
        \ split(a:string, '\\\@<!,[, ]*'),
        \ 'substitute(v:val, ''\\,'', '''', ''g'')',
        \)
endfunction

" Given a directory, language, encoding, and optionally a type with
" subdirectory and filename value to extend it, calculate a path, ensuring
" that the relevant directory is created; otherwise return nothing
"
function! s:Path(dir, lang, encoding, ...) abort

  " Pull in the type variable if it was defined
  if a:0 > 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, 'p', 0700)
    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