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