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