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