aboutsummaryrefslogtreecommitdiff
path: root/autoload/spellfile_local.vim
blob: 7d1877e770300cb8587b9aa340834632e8533b72 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
" 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, ''\\\@<!,'', ''\\,'', ''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