aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Ryder <tom@sanctum.geek.nz>2019-12-19 17:19:41 +1300
committerTom Ryder <tom@sanctum.geek.nz>2019-12-19 17:19:41 +1300
commitba42c743f57ffbc0546c8c11fe92b3f95a15e6cc (patch)
tree58cd96aa4eee23c7a876618d30db1ab814b3a628
parentRemove complex 'spellfile' setting (diff)
downloaddotfiles-ba42c743f57ffbc0546c8c11fe92b3f95a15e6cc.tar.gz
dotfiles-ba42c743f57ffbc0546c8c11fe92b3f95a15e6cc.zip
Overhaul spellfile_local.vim plugin
Ready to be spun out into its own distribution shortly.
-rw-r--r--vim/autoload/spellfile_local.vim186
-rw-r--r--vim/plugin/spellfile_local.vim18
2 files changed, 147 insertions, 57 deletions
diff --git a/vim/autoload/spellfile_local.vim b/vim/autoload/spellfile_local.vim
index e119b718..7dbe8fb5 100644
--- a/vim/autoload/spellfile_local.vim
+++ b/vim/autoload/spellfile_local.vim
@@ -1,72 +1,154 @@
-function! s:SplitOption(string) abort
- return map(
- \ split(a:string, '\\\@<!,[, ]*')
- \,'substitute(v:val, ''\\,'', '''', ''g'')'
- \)
+" 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
-function! s:JoinOption(list) abort
- return join(map(
- \ a:list
- \,'substitute(v:val, ''\\\@<!,'', ''\\,'', ''g'')'
- \), ',')
+" 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
-function! spellfile_local#() abort
+" 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
- set spellfile<
+" 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
- let spelllangs = s:SplitOption(&spelllang)
- if !len(spelllangs) || spelllangs[0] ==# ''
- echoerr 'Blank ''spelllang'''
- endif
- let spelllang = substitute(spelllangs[0], '_.*', '', '')
+" 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
- if !len(&encoding)
- echoerr 'Blank ''encoding'''
+ " Pull in the type variable if it was defined
+ if a:0 > 0
+ let type = a:1
endif
- let spellfiles = s:SplitOption(&spellfile)
- if len(spellfiles) != 1 || spellfiles[0] ==# ''
- return
+ " 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
- let spelldir = fnamemodify(spellfiles[0], ':h')
- if spelldir ==# ''
- echoerr 'Blank directory'
+ " 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
- try
- let path = tr(expand('%:p'), '/', '%')
- if path ==# ''
- echoerr 'Blank path'
- endif
- call s:Establish(spelldir.'/path')
- call add(spellfiles, spelldir.'/path/'.join([
- \ path
- \,spelllang
- \,&encoding
- \,'add'
- \], '.'))
-
- if &filetype ==# ''
- echoerr 'Blank filetype'
- endif
- call s:Establish(spelldir.'/filetype')
- call add(spellfiles, spelldir.'/filetype/'.join([
- \ &filetype
- \,spelllang
- \,&encoding
- \,'add'
- \], '.'))
- catch
- endtry
-
- let &l:spellfile = s:JoinOption(spellfiles)
+ " 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
diff --git a/vim/plugin/spellfile_local.vim b/vim/plugin/spellfile_local.vim
index f6918bfb..07307754 100644
--- a/vim/plugin/spellfile_local.vim
+++ b/vim/plugin/spellfile_local.vim
@@ -1,12 +1,20 @@
+"
+" spellfile_local.vim: Set extra 'spellfile' elements for full file paths and
+" filetype, to give the option of adding to file-specific or filetype-specific
+" spelling word lists.
+"
+" Author: Tom Ryder <tom@sanctum.geek.nz>
+" License: Same as Vim itself
+"
if exists('loaded_spellfile_local') || &compatible
finish
endif
let loaded_spellfile_local = 1
-command! -bar SetLocalSpellFiles
- \ call spellfile_local#()
-
+" For various events involving establishing or renaming a file buffer or
+" changing its filetype, rebuild the 'spellfile' definition accordingly
+"
augroup spellfile_local
- autocmd BufNew,BufRead *
- \ SetLocalSpellFiles
+ autocmd BufFilePost,BufNewFile,BufRead,EncodingChanged,FileType *
+ \ call spellfile_local#()
augroup END