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
|