1" Vim completion script
2" Language:    All languages, uses existing syntax highlighting rules
3" Maintainer:  David Fishburn <dfishburn dot vim at gmail dot com>
4" Version:     13.0
5" Last Change: 2013 May 14
6" Usage:       For detailed help, ":help ft-syntax-omni"
7
8" History
9"
10" Version 13.0
11"   - Extended the option omni_syntax_group_include_{filetype}
12"     to accept a comma separated list of regex's rather than
13"     string.  For example, for the javascript filetype you could
14"     use:
15"        let g:omni_syntax_group_include_javascript = 'javascript\w\+,jquery\w\+'
16"   - Some syntax files (perl.vim) use the match // syntax as a mechanism
17"     to identify keywords.  This update attempts to parse the
18"     match syntax and pull out syntax items which are at least
19"     3 words or more.
20"
21" Version 12.0
22"   - It is possible to have '-' as part of iskeyword, when
23"     checking for character ranges, tighten up the regex.
24"     E688: More targets than List items.
25"
26" Version 11.0
27"   - Corrected which characters required escaping during
28"     substitution calls.
29"
30" Version 10.0
31"   - Cycle through all the character ranges specified in the
32"     iskeyword option and build a list of valid word separators.
33"     Prior to this change, only actual characters were used,
34"     where for example ASCII "45" == "-".  If "45" were used
35"     in iskeyword the hyphen would not be picked up.
36"     This introduces a new option, since the character ranges
37"     specified could be multibyte:
38"         let g:omni_syntax_use_single_byte = 1
39"   - This by default will only allow single byte ASCII
40"     characters to be added and an additional check to ensure
41"     the charater is printable (see documentation for isprint).
42"
43" Version 9.0
44"   - Add the check for cpo.
45"
46" Version 8.0
47"   - Updated SyntaxCSyntaxGroupItems()
48"         - Some additional syntax items were also allowed
49"           on nextgroup= lines which were ignored by default.
50"           Now these lines are processed independently.
51"
52" Version 7.0
53"   - Updated syntaxcomplete#OmniSyntaxList()
54"         - Looking up the syntax groups defined from a syntax file
55"           looked for only 1 format of {filetype}GroupName, but some
56"           syntax writers use this format as well:
57"               {b:current_syntax}GroupName
58"   -       OmniSyntaxList() will now check for both if the first
59"           method does not find a match.
60"
61" Version 6.0
62"   - Added syntaxcomplete#OmniSyntaxList()
63"         - Allows other plugins to use this for their own
64"           purposes.
65"         - It will return a List of all syntax items for the
66"           syntax group name passed in.
67"         - XPTemplate for SQL will use this function via the
68"           sqlcomplete plugin to populate a Choose box.
69"
70" Version 5.0
71"   - Updated SyntaxCSyntaxGroupItems()
72"         - When processing a list of syntax groups, the final group
73"           was missed in function SyntaxCSyntaxGroupItems.
74"
75" Set completion with CTRL-X CTRL-O to autoloaded function.
76" This check is in place in case this script is
77" sourced directly instead of using the autoload feature.
78if exists('+omnifunc')
79    " Do not set the option if already set since this
80    " results in an E117 warning.
81    if &omnifunc == ""
82        setlocal omnifunc=syntaxcomplete#Complete
83    endif
84endif
85
86if exists('g:loaded_syntax_completion')
87    finish
88endif
89let g:loaded_syntax_completion = 130
90
91" Turn on support for line continuations when creating the script
92let s:cpo_save = &cpo
93set cpo&vim
94
95" Set ignorecase to the ftplugin standard
96" This is the default setting, but if you define a buffer local
97" variable you can override this on a per filetype.
98if !exists('g:omni_syntax_ignorecase')
99    let g:omni_syntax_ignorecase = &ignorecase
100endif
101
102" Indicates whether we should use the iskeyword option to determine
103" how to split words.
104" This is the default setting, but if you define a buffer local
105" variable you can override this on a per filetype.
106if !exists('g:omni_syntax_use_iskeyword')
107    let g:omni_syntax_use_iskeyword = 1
108endif
109
110" When using iskeyword, this setting controls whether the characters
111" should be limited to single byte characters.
112if !exists('g:omni_syntax_use_single_byte')
113    let g:omni_syntax_use_single_byte = 1
114endif
115
116" When using iskeyword, this setting controls whether the characters
117" should be limited to single byte characters.
118if !exists('g:omni_syntax_use_iskeyword_numeric')
119    let g:omni_syntax_use_iskeyword_numeric = 1
120endif
121
122" Only display items in the completion window that are at least
123" this many characters in length.
124" This is the default setting, but if you define a buffer local
125" variable you can override this on a per filetype.
126if !exists('g:omni_syntax_minimum_length')
127    let g:omni_syntax_minimum_length = 0
128endif
129
130" This script will build a completion list based on the syntax
131" elements defined by the files in $VIMRUNTIME/syntax.
132" let s:syn_remove_words = 'match,matchgroup=,contains,'.
133let s:syn_remove_words = 'matchgroup=,contains,'.
134            \ 'links to,start=,end='
135            " \ 'links to,start=,end=,nextgroup='
136
137let s:cache_name = []
138let s:cache_list = []
139let s:prepended  = ''
140
141" This function is used for the 'omnifunc' option.
142function! syntaxcomplete#Complete(findstart, base)
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_ignorecase')
147        if exists('g:omni_syntax_ignorecase')
148            let b:omni_syntax_ignorecase = g:omni_syntax_ignorecase
149        else
150            let b:omni_syntax_ignorecase = &ignorecase
151        endif
152    endif
153
154    if a:findstart
155        " Locate the start of the item, including "."
156        let line = getline('.')
157        let start = col('.') - 1
158        let lastword = -1
159        while start > 0
160            " if line[start - 1] =~ '\S'
161            "     let start -= 1
162            " elseif line[start - 1] =~ '\.'
163            if line[start - 1] =~ '\k'
164                let start -= 1
165                let lastword = a:findstart
166            else
167                break
168            endif
169        endwhile
170
171        " Return the column of the last word, which is going to be changed.
172        " Remember the text that comes before it in s:prepended.
173        if lastword == -1
174            let s:prepended = ''
175            return start
176        endif
177        let s:prepended = strpart(line, start, (col('.') - 1) - start)
178        return start
179    endif
180
181    " let base = s:prepended . a:base
182    let base = s:prepended
183
184    let filetype = substitute(&filetype, '\.', '_', 'g')
185    let list_idx = index(s:cache_name, filetype, 0, &ignorecase)
186    if list_idx > -1
187        let compl_list = s:cache_list[list_idx]
188    else
189        let compl_list   = OmniSyntaxList()
190        let s:cache_name = add( s:cache_name,  filetype )
191        let s:cache_list = add( s:cache_list,  compl_list )
192    endif
193
194    " Return list of matches.
195
196    if base != ''
197        " let compstr    = join(compl_list, ' ')
198        " let expr       = (b:omni_syntax_ignorecase==0?'\C':'').'\<\%('.base.'\)\@!\w\+\s*'
199        " let compstr    = substitute(compstr, expr, '', 'g')
200        " let compl_list = split(compstr, '\s\+')
201
202        " Filter the list based on the first few characters the user
203        " entered
204        let expr = 'v:val '.(g:omni_syntax_ignorecase==1?'=~?':'=~#')." '^".escape(base, '\\/.*$^~[]').".*'"
205        let compl_list = filter(deepcopy(compl_list), expr)
206    endif
207
208    return compl_list
209endfunc
210
211function! syntaxcomplete#OmniSyntaxList(...)
212    if a:0 > 0
213        let parms = []
214        if 3 == type(a:1)
215            let parms = a:1
216        elseif 1 == type(a:1)
217            let parms = split(a:1, ',')
218        endif
219        return OmniSyntaxList( parms )
220    else
221        return OmniSyntaxList()
222    endif
223endfunc
224
225function! OmniSyntaxList(...)
226    let list_parms = []
227    if a:0 > 0
228        if 3 == type(a:1)
229            let list_parms = a:1
230        elseif 1 == type(a:1)
231            let list_parms = split(a:1, ',')
232        endif
233    endif
234
235    " Default to returning a dictionary, if use_dictionary is set to 0
236    " a list will be returned.
237    " let use_dictionary = 1
238    " if a:0 > 0 && a:1 != ''
239    "     let use_dictionary = a:1
240    " endif
241
242    " Only display items in the completion window that are at least
243    " this many characters in length
244    if !exists('b:omni_syntax_use_iskeyword')
245        if exists('g:omni_syntax_use_iskeyword')
246            let b:omni_syntax_use_iskeyword = g:omni_syntax_use_iskeyword
247        else
248            let b:omni_syntax_use_iskeyword = 1
249        endif
250    endif
251
252    " Only display items in the completion window that are at least
253    " this many characters in length
254    if !exists('b:omni_syntax_minimum_length')
255        if exists('g:omni_syntax_minimum_length')
256            let b:omni_syntax_minimum_length = g:omni_syntax_minimum_length
257        else
258            let b:omni_syntax_minimum_length = 0
259        endif
260    endif
261
262    let saveL = @l
263    let filetype = substitute(&filetype, '\.', '_', 'g')
264
265    if empty(list_parms)
266        " Default the include group to include the requested syntax group
267        let syntax_group_include_{filetype} = ''
268        " Check if there are any overrides specified for this filetype
269        if exists('g:omni_syntax_group_include_'.filetype)
270            let syntax_group_include_{filetype} =
271                        \ substitute( g:omni_syntax_group_include_{filetype},'\s\+','','g')
272            let list_parms = split(g:omni_syntax_group_include_{filetype}, ',')
273            if syntax_group_include_{filetype} =~ '\w'
274                let syntax_group_include_{filetype} =
275                            \ substitute( syntax_group_include_{filetype},
276                            \ '\s*,\s*', '\\|', 'g'
277                            \ )
278            endif
279        endif
280    else
281        " A specific list was provided, use it
282    endif
283
284    " Loop through all the syntax groupnames, and build a
285    " syntax file which contains these names.  This can
286    " work generically for any filetype that does not already
287    " have a plugin defined.
288    " This ASSUMES the syntax groupname BEGINS with the name
289    " of the filetype.  From my casual viewing of the vim7\syntax
290    " directory this is true for almost all syntax definitions.
291    " As an example, the SQL syntax groups have this pattern:
292    "     sqlType
293    "     sqlOperators
294    "     sqlKeyword ...
295    if !empty(list_parms) && empty(substitute(join(list_parms), '[a-zA-Z ]', '', 'g'))
296        " If list_parms only includes word characters, use it to limit
297        " the syntax elements.
298        " If using regex syntax list will fail to find those items, so
299        " simply grab the who syntax list.
300        redir @l
301        silent! exec 'syntax list '.join(list_parms)
302        redir END
303    else
304        redir @l
305        silent! exec 'syntax list'
306        redir END
307    endif
308
309    let syntax_full = "\n".@l
310    let @l = saveL
311
312    if syntax_full =~ 'E28'
313                \ || syntax_full =~ 'E411'
314                \ || syntax_full =~ 'E415'
315                \ || syntax_full =~ 'No Syntax items'
316        return []
317    endif
318
319    let filetype = substitute(&filetype, '\.', '_', 'g')
320
321    let list_exclude_groups = []
322    if a:0 > 0
323        " Do nothing since we have specific a specific list of groups
324    else
325        " Default the exclude group to nothing
326        let syntax_group_exclude_{filetype} = ''
327        " Check if there are any overrides specified for this filetype
328        if exists('g:omni_syntax_group_exclude_'.filetype)
329            let syntax_group_exclude_{filetype} =
330                        \ substitute( g:omni_syntax_group_exclude_{filetype},'\s\+','','g')
331            let list_exclude_groups = split(g:omni_syntax_group_exclude_{filetype}, ',')
332            if syntax_group_exclude_{filetype} =~ '\w'
333                let syntax_group_exclude_{filetype} =
334                            \ substitute( syntax_group_exclude_{filetype},
335                            \ '\s*,\s*', '\\|', 'g'
336                            \ )
337            endif
338        endif
339    endif
340
341    if empty(list_parms)
342        let list_parms = [&filetype.'\w\+']
343    endif
344
345    let syn_list = ''
346    let index    = 0
347    for group_regex in list_parms
348        " Sometimes filetypes can be composite names, like c.doxygen
349        " Loop through each individual part looking for the syntax
350        " items specific to each individual filetype.
351        " let ftindex  = 0
352        " let ftindex  = match(syntax_full, group_regex, ftindex)
353
354        " while ftindex > -1
355            " let ft_part_name = matchstr( syntax_full, '\w\+', ftindex )
356
357            " Syntax rules can contain items for more than just the current
358            " filetype.  They can contain additional items added by the user
359            " via autocmds or their vimrc.
360            " Some syntax files can be combined (html, php, jsp).
361            " We want only items that begin with the filetype we are interested in.
362            let next_group_regex = '\n' .
363                        \ '\zs'.group_regex.'\ze'.
364                        \ '\s\+xxx\s\+'
365            let index    = match(syntax_full, next_group_regex, index)
366
367            " For the matched group name, strip off any of the regex special
368            " characters and see if we get a match with the current syntax
369            if index == -1 && exists('b:current_syntax') && substitute(group_regex, '[^a-zA-Z ]\+.*', '', 'g') !~ '^'.b:current_syntax
370                " There appears to be two standards when writing syntax files.
371                " Either items begin as:
372                "     syn keyword {filetype}Keyword         values ...
373                "     let b:current_syntax = "sql"
374                "     let b:current_syntax = "sqlanywhere"
375                " Or
376                "     syn keyword {syntax_filename}Keyword  values ...
377                "     let b:current_syntax = "mysql"
378                " So, we will make the format of finding the syntax group names
379                " a bit more flexible and look for both if the first fails to
380                " find a match.
381                let next_group_regex = '\n' .
382                            \ '\zs'.b:current_syntax.'\w\+\ze'.
383                            \ '\s\+xxx\s\+'
384                let index    = 0
385                let index    = match(syntax_full, next_group_regex, index)
386            endif
387
388            while index > -1
389                let group_name = matchstr( syntax_full, '\w\+', index )
390
391                let get_syn_list = 1
392                for exclude_group_name in list_exclude_groups
393                    if '\<'.exclude_group_name.'\>' =~ '\<'.group_name.'\>'
394                        let get_syn_list = 0
395                    endif
396                endfor
397
398                " This code is no longer needed in version 6.0 since we have
399                " augmented the syntax list command to only retrieve the syntax
400                " groups we are interested in.
401                "
402                " if get_syn_list == 1
403                "     if syntax_group_include_{filetype} != ''
404                "         if '\<'.syntax_group_include_{filetype}.'\>' !~ '\<'.group_name.'\>'
405                "             let get_syn_list = 0
406                "         endif
407                "     endif
408                " endif
409
410                if get_syn_list == 1
411                    " Pass in the full syntax listing, plus the group name we
412                    " are interested in.
413                    let extra_syn_list = s:SyntaxCSyntaxGroupItems(group_name, syntax_full)
414                    let syn_list = syn_list . extra_syn_list . "\n"
415                endif
416
417                let index = index + strlen(group_name)
418                let index = match(syntax_full, next_group_regex, index)
419            endwhile
420
421            " let ftindex  = ftindex + len(ft_part_name)
422            " let ftindex  = match( syntax_full, group_regex, ftindex )
423        " endwhile
424    endfor
425
426"   " Sometimes filetypes can be composite names, like c.doxygen
427"   " Loop through each individual part looking for the syntax
428"   " items specific to each individual filetype.
429"   let syn_list = ''
430"   let ftindex  = 0
431"   let ftindex  = match(&filetype, '\w\+', ftindex)
432
433"   while ftindex > -1
434"       let ft_part_name = matchstr( &filetype, '\w\+', ftindex )
435
436"       " Syntax rules can contain items for more than just the current
437"       " filetype.  They can contain additional items added by the user
438"       " via autocmds or their vimrc.
439"       " Some syntax files can be combined (html, php, jsp).
440"       " We want only items that begin with the filetype we are interested in.
441"       let next_group_regex = '\n' .
442"                   \ '\zs'.ft_part_name.'\w\+\ze'.
443"                   \ '\s\+xxx\s\+'
444"       let index    = 0
445"       let index    = match(syntax_full, next_group_regex, index)
446
447"       if index == -1 && exists('b:current_syntax') && ft_part_name != b:current_syntax
448"           " There appears to be two standards when writing syntax files.
449"           " Either items begin as:
450"           "     syn keyword {filetype}Keyword         values ...
451"           "     let b:current_syntax = "sql"
452"           "     let b:current_syntax = "sqlanywhere"
453"           " Or
454"           "     syn keyword {syntax_filename}Keyword  values ...
455"           "     let b:current_syntax = "mysql"
456"           " So, we will make the format of finding the syntax group names
457"           " a bit more flexible and look for both if the first fails to
458"           " find a match.
459"           let next_group_regex = '\n' .
460"                       \ '\zs'.b:current_syntax.'\w\+\ze'.
461"                       \ '\s\+xxx\s\+'
462"           let index    = 0
463"           let index    = match(syntax_full, next_group_regex, index)
464"       endif
465
466"       while index > -1
467"           let group_name = matchstr( syntax_full, '\w\+', index )
468
469"           let get_syn_list = 1
470"           for exclude_group_name in list_exclude_groups
471"               if '\<'.exclude_group_name.'\>' =~ '\<'.group_name.'\>'
472"                   let get_syn_list = 0
473"               endif
474"           endfor
475
476"           " This code is no longer needed in version 6.0 since we have
477"           " augmented the syntax list command to only retrieve the syntax
478"           " groups we are interested in.
479"           "
480"           " if get_syn_list == 1
481"           "     if syntax_group_include_{filetype} != ''
482"           "         if '\<'.syntax_group_include_{filetype}.'\>' !~ '\<'.group_name.'\>'
483"           "             let get_syn_list = 0
484"           "         endif
485"           "     endif
486"           " endif
487
488"           if get_syn_list == 1
489"               " Pass in the full syntax listing, plus the group name we
490"               " are interested in.
491"               let extra_syn_list = s:SyntaxCSyntaxGroupItems(group_name, syntax_full)
492"               let syn_list = syn_list . extra_syn_list . "\n"
493"           endif
494
495"           let index = index + strlen(group_name)
496"           let index = match(syntax_full, next_group_regex, index)
497"       endwhile
498
499"       let ftindex  = ftindex + len(ft_part_name)
500"       let ftindex  = match( &filetype, '\w\+', ftindex )
501"   endwhile
502
503    " Convert the string to a List and sort it.
504    let compl_list = sort(split(syn_list))
505
506    if &filetype == 'vim'
507        let short_compl_list = []
508        for i in range(len(compl_list))
509            if i == len(compl_list)-1
510                let next = i
511            else
512                let next = i + 1
513            endif
514            if  compl_list[next] !~ '^'.compl_list[i].'.$'
515                let short_compl_list += [compl_list[i]]
516            endif
517        endfor
518
519        return short_compl_list
520    else
521        return compl_list
522    endif
523endfunction
524
525function! s:SyntaxCSyntaxGroupItems( group_name, syntax_full )
526
527    let syn_list = ""
528
529    " From the full syntax listing, strip out the portion for the
530    " request group.
531    " Query:
532    "     \n           - must begin with a newline
533    "     a:group_name - the group name we are interested in
534    "     \s\+xxx\s\+  - group names are always followed by xxx
535    "     \zs          - start the match
536    "     .\{-}        - everything ...
537    "     \ze          - end the match
538    "     \(           - start a group or 2 potential matches
539    "     \n\w         - at the first newline starting with a character
540    "     \|           - 2nd potential match
541    "     \%$          - matches end of the file or string
542    "     \)           - end a group
543    let syntax_group = matchstr(a:syntax_full,
544                \ "\n".a:group_name.'\s\+xxx\s\+\zs.\{-}\ze\(\n\w\|\%$\)'
545                \ )
546
547    if syntax_group != ""
548        " let syn_list = substitute( @l, '^.*xxx\s*\%(contained\s*\)\?', "", '' )
549        " let syn_list = substitute( @l, '^.*xxx\s*', "", '' )
550
551        " We only want the words for the lines begining with
552        " containedin, but there could be other items.
553
554        " Tried to remove all lines that do not begin with contained
555        " but this does not work in all cases since you can have
556        "    contained nextgroup=...
557        " So this will strip off the ending of lines with known
558        " keywords.
559        let syn_list = substitute(
560                    \    syntax_group, '\<\('.
561                    \    substitute(
562                    \      escape(s:syn_remove_words, '\\/.*$^~[]')
563                    \      , ',', '\\|', 'g'
564                    \    ).
565                    \    '\).\{-}\%($\|'."\n".'\)'
566                    \    , "\n", 'g'
567                    \  )
568
569        " Attempt to deal with lines using the match syntax
570        " javaScriptDocTags xxx match /@\(param\|argument\|requires\|file\)\>/
571        " Though it can use any types of regex, so this plugin will attempt
572        " to restrict it
573        " 1.  Only use \( or \%( constructs remove all else
574        " 2   Remove and []s
575        " 3.  Account for match //constructs
576        "                       \%(\%(ms\|me\|hs\|he\|rs\|re\|lc\)\S\+\)\?
577        " 4.  Hope for the best
578        "
579        "
580        let syn_list_old = syn_list
581        while syn_list =~ '\<match\>\s\+\/'
582            if syn_list =~ 'perlElseIfError'
583                let syn_list = syn_list
584            endif
585            " Check if the match has words at least 3 characters long
586            if syn_list =~ '\<match \/\zs.\{-}\<\w\{3,}\>.\{-}\ze\\\@<!\/\%(\%(ms\|me\|hs\|he\|rs\|re\|lc\)\S\+\)\?\s\+'
587                " Remove everything after / and before the first \(
588                let syn_list = substitute( syn_list, '\<match \/\zs.\{-}\ze\\%\?(.\{-}\\\@<!\/\%(\%(ms\|me\|hs\|he\|rs\|re\|lc\)\S\+\)\?\s\+', '', 'g' )
589                " Remove everything after \) and up to the ending /
590                let syn_list = substitute( syn_list, '\<match \/.\{-}\\)\zs.\{-}\ze\/\%(\%(ms\|me\|hs\|he\|rs\|re\|lc\)\S\+\)\?\s\+', '', 'g' )
591
592                " Remove any character classes
593                " let syn_list = substitute( syn_list, '\<match /\zs.\{-}\[[^]]*\].\{-}\ze\/ ', '', 'g' )
594                let syn_list = substitute( syn_list, '\%(\<match \/[^/]\{-}\)\@<=\[[^]]*\]\ze.\{-}\\\@<!\/\%(\%(ms\|me\|hs\|he\|rs\|re\|lc\)\S\+\)\?', '', 'g' )
595                " Remove any words < 3 characters
596                let syn_list = substitute( syn_list, '\%(\<match \/[^/]\{-}\)\@<=\<\w\{1,2}\>\ze.\{-}\\\@<!\/\%(\%(ms\|me\|hs\|he\|rs\|re\|lc\)\S\+\)\?\s\+', '', 'g' )
597                " Remove all non-word characters
598                " let syn_list = substitute( syn_list, '\<match /\zs.\{-}\<\W\+\>.\{-}\ze\/ ', "", 'g' )
599                " let syn_list = substitute( syn_list, '\%(\<match \/[^/]\{-}\)\@<=\W\+\ze.\{-}\/ ', ' ', 'g' )
600                " Do this by using the outer substitue() call to gather all
601                " text between the match /.../ tags.
602                " The inner substitute() call operates on the text selected
603                " and replaces all non-word characters.
604                let syn_list = substitute( syn_list, '\<match \/\zs\(.\{-}\)\ze\\\@<!\/\%(\%(ms\|me\|hs\|he\|rs\|re\|lc\)\S\+\)\?\s\+'
605                            \ , '\=substitute(submatch(1), "\\W\\+", " ", "g")'
606                            \ , 'g' )
607                " Remove the match / / syntax
608                let syn_list = substitute( syn_list, '\<match \/\(.\{-}\)\/\%(\%(ms\|me\|hs\|he\|rs\|re\|lc\)\S\+\)\?\s\+', '\1', 'g' )
609            else
610                " No words long enough, remove the match
611                " Remove the match syntax
612                " let syn_list = substitute( syn_list, '\<match \/[^\/]*\/\%(\%(ms\|me\|hs\|he\|rs\|re\|lc\)\S\+\)\?\s\+', '', 'g' )
613                let syn_list = substitute( syn_list, '\<match \/\%(.\{-}\)\?\/\%(\%(ms\|me\|hs\|he\|rs\|re\|lc\)\S\+\)\?\s\+', '', 'g' )
614            endif
615            if syn_list =~ '\<match\>\s\+\/'
616                " Problem removing the match / / tags
617                let syn_list = ''
618            endif
619        endwhile
620
621
622        " Now strip off the newline + blank space + contained.
623        " Also include lines with nextgroup=@someName skip_key_words syntax_element
624                    " \    syn_list, '\%(^\|\n\)\@<=\s*\<\(contained\|nextgroup=\)'
625                    " \    syn_list, '\%(^\|\n\)\@<=\s*\<\(contained\|nextgroup=[@a-zA-Z,]*\)'
626        let syn_list = substitute(
627                    \    syn_list, '\<\(contained\|nextgroup=[@a-zA-Z,]*\)'
628                    \    , "", 'g'
629                    \ )
630
631        " This can leave lines like this
632        "     =@vimMenuList  skipwhite onoremenu
633        " Strip the special option keywords first
634        "     :h :syn-skipwhite*
635        let syn_list = substitute(
636                    \    syn_list, '\<\(skipwhite\|skipnl\|skipempty\)\>'
637                    \    , "", 'g'
638                    \ )
639
640        " Now remove the remainder of the nextgroup=@someName lines
641        let syn_list = substitute(
642                    \    syn_list, '\%(^\|\n\)\@<=\s*\(@\w\+\)'
643                    \    , "", 'g'
644                    \ )
645
646        if b:omni_syntax_use_iskeyword == 0
647            " There are a number of items which have non-word characters in
648            " them, *'T_F1'*.  vim.vim is one such file.
649            " This will replace non-word characters with spaces.
650            let syn_list = substitute( syn_list, '[^0-9A-Za-z_ ]', ' ', 'g' )
651        else
652            if g:omni_syntax_use_iskeyword_numeric == 1
653                " iskeyword can contain value like this
654                " 38,42,43,45,47-58,60-62,64-90,97-122,_,+,-,*,/,%,<,=,>,:,$,?,!,@-@,94
655                " Numeric values convert to their ASCII equivalent using the
656                " nr2char() function.
657                "     &       38
658                "     *       42
659                "     +       43
660                "     -       45
661                "     ^       94
662                " Iterate through all numeric specifications and convert those
663                " to their ascii equivalent ensuring the character is printable.
664                " If so, add it to the list.
665                let accepted_chars = ''
666                for item in split(&iskeyword, ',')
667                    if item =~ '\d-\d'
668                        " This is a character range (ie 47-58),
669                        " cycle through each character within the range
670                        let [b:start, b:end] = split(item, '-')
671                        for range_item in range( b:start, b:end )
672                            if range_item <= 127 || g:omni_syntax_use_single_byte == 0
673                                if nr2char(range_item) =~ '\p'
674                                    let accepted_chars = accepted_chars . nr2char(range_item)
675                                endif
676                            endif
677                        endfor
678                    elseif item =~ '^\d\+$'
679                        " Only numeric, translate to a character
680                        if item < 127 || g:omni_syntax_use_single_byte == 0
681                            if nr2char(item) =~ '\p'
682                                let accepted_chars = accepted_chars . nr2char(item)
683                            endif
684                        endif
685                    else
686                        if char2nr(item) < 127 || g:omni_syntax_use_single_byte == 0
687                            if item =~ '\p'
688                                let accepted_chars = accepted_chars . item
689                            endif
690                        endif
691                    endif
692                endfor
693                " Escape special regex characters
694                " Looks like the wrong chars are escaped.  In a collection,
695                "      :h /[]
696                "      only `]', `\', `-' and `^' are special:
697                " let accepted_chars = escape(accepted_chars, '\\/.*$^~[]' )
698                let accepted_chars = escape(accepted_chars, ']\-^' )
699                " Remove all characters that are not acceptable
700                let syn_list = substitute( syn_list, '[^A-Za-z'.accepted_chars.']', ' ', 'g' )
701            else
702                let accept_chars = ','.&iskeyword.','
703                " Remove all character ranges
704                " let accept_chars = substitute(accept_chars, ',[^,]\+-[^,]\+,', ',', 'g')
705                let accept_chars = substitute(accept_chars, ',\@<=[^,]\+-[^,]\+,', '', 'g')
706                " Remove all numeric specifications
707                " let accept_chars = substitute(accept_chars, ',\d\{-},', ',', 'g')
708                let accept_chars = substitute(accept_chars, ',\@<=\d\{-},', '', 'g')
709                " Remove all commas
710                let accept_chars = substitute(accept_chars, ',', '', 'g')
711                " Escape special regex characters
712                " Looks like the wrong chars are escaped.  In a collection,
713                "      :h /[]
714                "      only `]', `\', `-' and `^' are special:
715                " let accept_chars = escape(accept_chars, '\\/.*$^~[]' )
716                let accept_chars = escape(accept_chars, ']\-^' )
717                " Remove all characters that are not acceptable
718                let syn_list = substitute( syn_list, '[^0-9A-Za-z_'.accept_chars.']', ' ', 'g' )
719            endif
720        endif
721
722        if b:omni_syntax_minimum_length > 0
723            " If the user specified a minimum length, enforce it
724            let syn_list = substitute(' '.syn_list.' ', ' \S\{,'.b:omni_syntax_minimum_length.'}\ze ', ' ', 'g')
725        endif
726    else
727        let syn_list = ''
728    endif
729
730    return syn_list
731endfunction
732
733function! OmniSyntaxShowChars(spec)
734  let result = []
735  for item in split(a:spec, ',')
736    if len(item) > 1
737      if item == '@-@'
738        call add(result, char2nr(item))
739      else
740        call extend(result, call('range', split(item, '-')))
741      endif
742    else
743      if item == '@'  " assume this is [A-Za-z]
744        for [c1, c2] in [['A', 'Z'], ['a', 'z']]
745          call extend(result, range(char2nr(c1), char2nr(c2)))
746        endfor
747      else
748        call add(result, char2nr(item))
749      endif
750    endif
751  endfor
752  return join(map(result, 'nr2char(v:val)'), ', ')
753endfunction
754let &cpo = s:cpo_save
755unlet s:cpo_save
756