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