1" Vim completion script
2" Language:    All languages, uses existing syntax highlighting rules
3" Maintainer:  David Fishburn <[email protected]>
4" Version:     3.0
5" Last Change: Wed Nov 08 2006 10:46:46 AM
6" Usage:       For detailed help, ":help ft-syntax-omni"
7
8" Set completion with CTRL-X CTRL-O to autoloaded function.
9" This check is in place in case this script is
10" sourced directly instead of using the autoload feature.
11if exists('+omnifunc')
12    " Do not set the option if already set since this
13    " results in an E117 warning.
14    if &omnifunc == ""
15        setlocal omnifunc=syntaxcomplete#Complete
16    endif
17endif
18
19if exists('g:loaded_syntax_completion')
20    finish
21endif
22let g:loaded_syntax_completion = 30
23
24" Set ignorecase to the ftplugin standard
25" This is the default setting, but if you define a buffer local
26" variable you can override this on a per filetype.
27if !exists('g:omni_syntax_ignorecase')
28    let g:omni_syntax_ignorecase = &ignorecase
29endif
30
31" Indicates whether we should use the iskeyword option to determine
32" how to split words.
33" This is the default setting, but if you define a buffer local
34" variable you can override this on a per filetype.
35if !exists('g:omni_syntax_use_iskeyword')
36    let g:omni_syntax_use_iskeyword = 1
37endif
38
39" Only display items in the completion window that are at least
40" this many characters in length.
41" This is the default setting, but if you define a buffer local
42" variable you can override this on a per filetype.
43if !exists('g:omni_syntax_minimum_length')
44    let g:omni_syntax_minimum_length = 0
45endif
46
47" This script will build a completion list based on the syntax
48" elements defined by the files in $VIMRUNTIME/syntax.
49let s:syn_remove_words = 'match,matchgroup=,contains,'.
50            \ 'links to,start=,end=,nextgroup='
51
52let s:cache_name = []
53let s:cache_list = []
54let s:prepended  = ''
55
56" This function is used for the 'omnifunc' option.
57function! syntaxcomplete#Complete(findstart, base)
58
59    " Only display items in the completion window that are at least
60    " this many characters in length
61    if !exists('b:omni_syntax_ignorecase')
62        if exists('g:omni_syntax_ignorecase')
63            let b:omni_syntax_ignorecase = g:omni_syntax_ignorecase
64        else
65            let b:omni_syntax_ignorecase = &ignorecase
66        endif
67    endif
68
69    if a:findstart
70        " Locate the start of the item, including "."
71        let line = getline('.')
72        let start = col('.') - 1
73        let lastword = -1
74        while start > 0
75            " if line[start - 1] =~ '\S'
76            "     let start -= 1
77            " elseif line[start - 1] =~ '\.'
78            if line[start - 1] =~ '\k'
79                let start -= 1
80                let lastword = a:findstart
81            else
82                break
83            endif
84        endwhile
85
86        " Return the column of the last word, which is going to be changed.
87        " Remember the text that comes before it in s:prepended.
88        if lastword == -1
89            let s:prepended = ''
90            return start
91        endif
92        let s:prepended = strpart(line, start, (col('.') - 1) - start)
93        return start
94    endif
95
96    " let base = s:prepended . a:base
97    let base = s:prepended
98
99    let filetype = substitute(&filetype, '\.', '_', 'g')
100    let list_idx = index(s:cache_name, filetype, 0, &ignorecase)
101    if list_idx > -1
102        let compl_list = s:cache_list[list_idx]
103    else
104        let compl_list   = OmniSyntaxList()
105        let s:cache_name = add( s:cache_name,  filetype )
106        let s:cache_list = add( s:cache_list,  compl_list )
107    endif
108
109    " Return list of matches.
110
111    if base != ''
112        " let compstr    = join(compl_list, ' ')
113        " let expr       = (b:omni_syntax_ignorecase==0?'\C':'').'\<\%('.base.'\)\@!\w\+\s*'
114        " let compstr    = substitute(compstr, expr, '', 'g')
115        " let compl_list = split(compstr, '\s\+')
116
117        " Filter the list based on the first few characters the user
118        " entered
119        let expr = 'v:val '.(g:omni_syntax_ignorecase==1?'=~?':'=~#')." '^".escape(base, '\\/.*$^~[]').".*'"
120        let compl_list = filter(deepcopy(compl_list), expr)
121    endif
122
123    return compl_list
124endfunc
125
126function! OmniSyntaxList()
127    " Default to returning a dictionary, if use_dictionary is set to 0
128    " a list will be returned.
129    " let use_dictionary = 1
130    " if a:0 > 0 && a:1 != ''
131    "     let use_dictionary = a:1
132    " endif
133
134    " Only display items in the completion window that are at least
135    " this many characters in length
136    if !exists('b:omni_syntax_use_iskeyword')
137        if exists('g:omni_syntax_use_iskeyword')
138            let b:omni_syntax_use_iskeyword = g:omni_syntax_use_iskeyword
139        else
140            let b:omni_syntax_use_iskeyword = 1
141        endif
142    endif
143
144    " Only display items in the completion window that are at least
145    " this many characters in length
146    if !exists('b:omni_syntax_minimum_length')
147        if exists('g:omni_syntax_minimum_length')
148            let b:omni_syntax_minimum_length = g:omni_syntax_minimum_length
149        else
150            let b:omni_syntax_minimum_length = 0
151        endif
152    endif
153
154    let saveL = @l
155
156    " Loop through all the syntax groupnames, and build a
157    " syntax file which contains these names.  This can
158    " work generically for any filetype that does not already
159    " have a plugin defined.
160    " This ASSUMES the syntax groupname BEGINS with the name
161    " of the filetype.  From my casual viewing of the vim7\syntax
162    " directory.
163    redir @l
164    silent! exec 'syntax list '
165    redir END
166
167    let syntax_full = "\n".@l
168    let @l = saveL
169
170    if syntax_full =~ 'E28'
171                \ || syntax_full =~ 'E411'
172                \ || syntax_full =~ 'E415'
173                \ || syntax_full =~ 'No Syntax items'
174        return []
175    endif
176
177    let filetype = substitute(&filetype, '\.', '_', 'g')
178
179    " Default the include group to include the requested syntax group
180    let syntax_group_include_{filetype} = ''
181    " Check if there are any overrides specified for this filetype
182    if exists('g:omni_syntax_group_include_'.filetype)
183        let syntax_group_include_{filetype} =
184                    \ substitute( g:omni_syntax_group_include_{filetype},'\s\+','','g')
185        if syntax_group_include_{filetype} =~ '\w'
186            let syntax_group_include_{filetype} =
187                        \ substitute( syntax_group_include_{filetype},
188                        \ '\s*,\s*', '\\|', 'g'
189                        \ )
190        endif
191    endif
192
193    " Default the exclude group to nothing
194    let syntax_group_exclude_{filetype} = ''
195    " Check if there are any overrides specified for this filetype
196    if exists('g:omni_syntax_group_exclude_'.filetype)
197        let syntax_group_exclude_{filetype} =
198                    \ substitute( g:omni_syntax_group_exclude_{filetype},'\s\+','','g')
199        if syntax_group_exclude_{filetype} =~ '\w'
200            let syntax_group_exclude_{filetype} =
201                        \ substitute( syntax_group_exclude_{filetype},
202                        \ '\s*,\s*', '\\|', 'g'
203                        \ )
204        endif
205    endif
206
207    " Sometimes filetypes can be composite names, like c.doxygen
208    " Loop through each individual part looking for the syntax
209    " items specific to each individual filetype.
210    let syn_list = ''
211    let ftindex  = 0
212    let ftindex  = match(&filetype, '\w\+', ftindex)
213
214    while ftindex > -1
215        let ft_part_name = matchstr( &filetype, '\w\+', ftindex )
216
217        " Syntax rules can contain items for more than just the current
218        " filetype.  They can contain additional items added by the user
219        " via autocmds or their vimrc.
220        " Some syntax files can be combined (html, php, jsp).
221        " We want only items that begin with the filetype we are interested in.
222        let next_group_regex = '\n' .
223                    \ '\zs'.ft_part_name.'\w\+\ze'.
224                    \ '\s\+xxx\s\+'
225        let index    = 0
226        let index    = match(syntax_full, next_group_regex, index)
227
228        while index > -1
229            let group_name = matchstr( syntax_full, '\w\+', index )
230
231            let get_syn_list = 1
232            " if syntax_group_include_{&filetype} == ''
233            "     if syntax_group_exclude_{&filetype} != ''
234            "         if '\<'.syntax_group_exclude_{&filetype}.'\>' =~ '\<'.group_name.'\>'
235            "             let get_syn_list = 0
236            "         endif
237            "     endif
238            " else
239            "     if '\<'.syntax_group_include_{&filetype}.'\>' !~ '\<'.group_name.'\>'
240            "         let get_syn_list = 0
241            "     endif
242            " endif
243            if syntax_group_exclude_{filetype} != ''
244                if '\<'.syntax_group_exclude_{filetype}.'\>' =~ '\<'.group_name.'\>'
245                    let get_syn_list = 0
246                endif
247            endif
248
249            if get_syn_list == 1
250                if syntax_group_include_{filetype} != ''
251                    if '\<'.syntax_group_include_{filetype}.'\>' !~ '\<'.group_name.'\>'
252                        let get_syn_list = 0
253                    endif
254                endif
255            endif
256
257            if get_syn_list == 1
258                " Pass in the full syntax listing, plus the group name we
259                " are interested in.
260                let extra_syn_list = s:SyntaxCSyntaxGroupItems(group_name, syntax_full)
261
262                " if !empty(extra_syn_list)
263                "     for elem in extra_syn_list
264                "         let item = {'word':elem, 'kind':'t', 'info':group_name}
265                "         let compl_list += [item]
266                "     endfor
267                " endif
268
269                let syn_list = syn_list . extra_syn_list . "\n"
270            endif
271
272            let index = index + strlen(group_name)
273            let index = match(syntax_full, next_group_regex, index)
274        endwhile
275
276        let ftindex  = ftindex + len(ft_part_name)
277        let ftindex  = match( &filetype, '\w\+', ftindex )
278    endwhile
279
280    " Convert the string to a List and sort it.
281    let compl_list = sort(split(syn_list))
282
283    if &filetype == 'vim'
284        let short_compl_list = []
285        for i in range(len(compl_list))
286            if i == len(compl_list)-1
287                let next = i
288            else
289                let next = i + 1
290            endif
291            if  compl_list[next] !~ '^'.compl_list[i].'.$'
292                let short_compl_list += [compl_list[i]]
293            endif
294        endfor
295
296        return short_compl_list
297    else
298        return compl_list
299    endif
300endfunction
301
302function! s:SyntaxCSyntaxGroupItems( group_name, syntax_full )
303
304    let syn_list = ""
305
306    " From the full syntax listing, strip out the portion for the
307    " request group.
308    " Query:
309    "     \n           - must begin with a newline
310    "     a:group_name - the group name we are interested in
311    "     \s\+xxx\s\+  - group names are always followed by xxx
312    "     \zs          - start the match
313    "     .\{-}        - everything ...
314    "     \ze          - end the match
315    "     \n\w         - at the first newline starting with a character
316    let syntax_group = matchstr(a:syntax_full,
317                \ "\n".a:group_name.'\s\+xxx\s\+\zs.\{-}\ze'."\n".'\w'
318                \ )
319
320    if syntax_group != ""
321        " let syn_list = substitute( @l, '^.*xxx\s*\%(contained\s*\)\?', "", '' )
322        " let syn_list = substitute( @l, '^.*xxx\s*', "", '' )
323
324        " We only want the words for the lines begining with
325        " containedin, but there could be other items.
326
327        " Tried to remove all lines that do not begin with contained
328        " but this does not work in all cases since you can have
329        "    contained nextgroup=...
330        " So this will strip off the ending of lines with known
331        " keywords.
332        let syn_list = substitute(
333                    \    syntax_group, '\<\('.
334                    \    substitute(
335                    \      escape(s:syn_remove_words, '\\/.*$^~[]')
336                    \      , ',', '\\|', 'g'
337                    \    ).
338                    \    '\).\{-}\%($\|'."\n".'\)'
339                    \    , "\n", 'g'
340                    \  )
341
342        " Now strip off the newline + blank space + contained
343        let syn_list = substitute(
344                    \    syn_list, '\%(^\|\n\)\@<=\s*\<\(contained\)'
345                    \    , "", 'g'
346                    \ )
347
348        if b:omni_syntax_use_iskeyword == 0
349            " There are a number of items which have non-word characters in
350            " them, *'T_F1'*.  vim.vim is one such file.
351            " This will replace non-word characters with spaces.
352            let syn_list = substitute( syn_list, '[^0-9A-Za-z_ ]', ' ', 'g' )
353        else
354            let accept_chars = ','.&iskeyword.','
355            " Remove all character ranges
356            let accept_chars = substitute(accept_chars, ',[^,]\+-[^,]\+,', ',', 'g')
357            " Remove all numeric specifications
358            let accept_chars = substitute(accept_chars, ',\d\{-},', ',', 'g')
359            " Remove all commas
360            let accept_chars = substitute(accept_chars, ',', '', 'g')
361            " Escape special regex characters
362            let accept_chars = escape(accept_chars, '\\/.*$^~[]' )
363            " Remove all characters that are not acceptable
364            let syn_list = substitute( syn_list, '[^0-9A-Za-z_ '.accept_chars.']', ' ', 'g' )
365        endif
366
367        if b:omni_syntax_minimum_length > 0
368            " If the user specified a minimum length, enforce it
369            let syn_list = substitute(' '.syn_list.' ', ' \S\{,'.b:omni_syntax_minimum_length.'}\ze ', ' ', 'g')
370        endif
371    else
372        let syn_list = ''
373    endif
374
375    return syn_list
376endfunction
377