1" Vim functions for file type detection 2" 3" Maintainer: Bram Moolenaar <[email protected]> 4" Last Change: 2019 Mar 08 5 6" These functions are moved here from runtime/filetype.vim to make startup 7" faster. 8 9" Line continuation is used here, remove 'C' from 'cpoptions' 10let s:cpo_save = &cpo 11set cpo&vim 12 13func dist#ft#Check_inp() 14 if getline(1) =~ '^\*' 15 setf abaqus 16 else 17 let n = 1 18 if line("$") > 500 19 let nmax = 500 20 else 21 let nmax = line("$") 22 endif 23 while n <= nmax 24 if getline(n) =~? "^header surface data" 25 setf trasys 26 break 27 endif 28 let n = n + 1 29 endwhile 30 endif 31endfunc 32 33" This function checks for the kind of assembly that is wanted by the user, or 34" can be detected from the first five lines of the file. 35func dist#ft#FTasm() 36 " make sure b:asmsyntax exists 37 if !exists("b:asmsyntax") 38 let b:asmsyntax = "" 39 endif 40 41 if b:asmsyntax == "" 42 call dist#ft#FTasmsyntax() 43 endif 44 45 " if b:asmsyntax still isn't set, default to asmsyntax or GNU 46 if b:asmsyntax == "" 47 if exists("g:asmsyntax") 48 let b:asmsyntax = g:asmsyntax 49 else 50 let b:asmsyntax = "asm" 51 endif 52 endif 53 54 exe "setf " . fnameescape(b:asmsyntax) 55endfunc 56 57func dist#ft#FTasmsyntax() 58 " see if file contains any asmsyntax=foo overrides. If so, change 59 " b:asmsyntax appropriately 60 let head = " ".getline(1)." ".getline(2)." ".getline(3)." ".getline(4). 61 \" ".getline(5)." " 62 let match = matchstr(head, '\sasmsyntax=\zs[a-zA-Z0-9]\+\ze\s') 63 if match != '' 64 let b:asmsyntax = match 65 elseif ((head =~? '\.title') || (head =~? '\.ident') || (head =~? '\.macro') || (head =~? '\.subtitle') || (head =~? '\.library')) 66 let b:asmsyntax = "vmasm" 67 endif 68endfunc 69 70" Check if one of the first five lines contains "VB_Name". In that case it is 71" probably a Visual Basic file. Otherwise it's assumed to be "alt" filetype. 72func dist#ft#FTVB(alt) 73 if getline(1).getline(2).getline(3).getline(4).getline(5) =~? 'VB_Name\|Begin VB\.\(Form\|MDIForm\|UserControl\)' 74 setf vb 75 else 76 exe "setf " . a:alt 77 endif 78endfunc 79 80func dist#ft#FTbtm() 81 if exists("g:dosbatch_syntax_for_btm") && g:dosbatch_syntax_for_btm 82 setf dosbatch 83 else 84 setf btm 85 endif 86endfunc 87 88func dist#ft#BindzoneCheck(default) 89 if getline(1).getline(2).getline(3).getline(4) =~ '^; <<>> DiG [0-9.]\+.* <<>>\|$ORIGIN\|$TTL\|IN\s\+SOA' 90 setf bindzone 91 elseif a:default != '' 92 exe 'setf ' . a:default 93 endif 94endfunc 95 96func dist#ft#FTlpc() 97 if exists("g:lpc_syntax_for_c") 98 let lnum = 1 99 while lnum <= 12 100 if getline(lnum) =~# '^\(//\|inherit\|private\|protected\|nosave\|string\|object\|mapping\|mixed\)' 101 setf lpc 102 return 103 endif 104 let lnum = lnum + 1 105 endwhile 106 endif 107 setf c 108endfunc 109 110func dist#ft#FTheader() 111 if match(getline(1, min([line("$"), 200])), '^@\(interface\|end\|class\)') > -1 112 if exists("g:c_syntax_for_h") 113 setf objc 114 else 115 setf objcpp 116 endif 117 elseif exists("g:c_syntax_for_h") 118 setf c 119 elseif exists("g:ch_syntax_for_h") 120 setf ch 121 else 122 setf cpp 123 endif 124endfunc 125 126" This function checks if one of the first ten lines start with a '@'. In 127" that case it is probably a change file. 128" If the first line starts with # or ! it's probably a ch file. 129" If a line has "main", "include", "//" or "/*" it's probably ch. 130" Otherwise CHILL is assumed. 131func dist#ft#FTchange() 132 let lnum = 1 133 while lnum <= 10 134 if getline(lnum)[0] == '@' 135 setf change 136 return 137 endif 138 if lnum == 1 && (getline(1)[0] == '#' || getline(1)[0] == '!') 139 setf ch 140 return 141 endif 142 if getline(lnum) =~ "MODULE" 143 setf chill 144 return 145 endif 146 if getline(lnum) =~ 'main\s*(\|#\s*include\|//' 147 setf ch 148 return 149 endif 150 let lnum = lnum + 1 151 endwhile 152 setf chill 153endfunc 154 155func dist#ft#FTent() 156 " This function checks for valid cl syntax in the first five lines. 157 " Look for either an opening comment, '#', or a block start, '{". 158 " If not found, assume SGML. 159 let lnum = 1 160 while lnum < 6 161 let line = getline(lnum) 162 if line =~ '^\s*[#{]' 163 setf cl 164 return 165 elseif line !~ '^\s*$' 166 " Not a blank line, not a comment, and not a block start, 167 " so doesn't look like valid cl code. 168 break 169 endif 170 let lnum = lnum + 1 171 endw 172 setf dtd 173endfunc 174 175func dist#ft#EuphoriaCheck() 176 if exists('g:filetype_euphoria') 177 exe 'setf ' . g:filetype_euphoria 178 else 179 setf euphoria3 180 endif 181endfunc 182 183func dist#ft#DtraceCheck() 184 let lines = getline(1, min([line("$"), 100])) 185 if match(lines, '^module\>\|^import\>') > -1 186 " D files often start with a module and/or import statement. 187 setf d 188 elseif match(lines, '^#!\S\+dtrace\|#pragma\s\+D\s\+option\|:\S\{-}:\S\{-}:') > -1 189 setf dtrace 190 else 191 setf d 192 endif 193endfunc 194 195func dist#ft#FTe() 196 if exists('g:filetype_euphoria') 197 exe 'setf ' . g:filetype_euphoria 198 else 199 let n = 1 200 while n < 100 && n <= line("$") 201 if getline(n) =~ "^\\s*\\(<'\\|'>\\)\\s*$" 202 setf specman 203 return 204 endif 205 let n = n + 1 206 endwhile 207 setf eiffel 208 endif 209endfunc 210 211" Distinguish between HTML, XHTML and Django 212func dist#ft#FThtml() 213 let n = 1 214 while n < 10 && n <= line("$") 215 if getline(n) =~ '\<DTD\s\+XHTML\s' 216 setf xhtml 217 return 218 endif 219 if getline(n) =~ '{%\s*\(extends\|block\|load\)\>\|{#\s\+' 220 setf htmldjango 221 return 222 endif 223 let n = n + 1 224 endwhile 225 setf FALLBACK html 226endfunc 227 228" Distinguish between standard IDL and MS-IDL 229func dist#ft#FTidl() 230 let n = 1 231 while n < 50 && n <= line("$") 232 if getline(n) =~ '^\s*import\s\+"\(unknwn\|objidl\)\.idl"' 233 setf msidl 234 return 235 endif 236 let n = n + 1 237 endwhile 238 setf idl 239endfunc 240 241" Distinguish between "default" and Cproto prototype file. */ 242func dist#ft#ProtoCheck(default) 243 " Cproto files have a comment in the first line and a function prototype in 244 " the second line, it always ends in ";". Indent files may also have 245 " comments, thus we can't match comments to see the difference. 246 " IDL files can have a single ';' in the second line, require at least one 247 " chacter before the ';'. 248 if getline(2) =~ '.;$' 249 setf cpp 250 else 251 exe 'setf ' . a:default 252 endif 253endfunc 254 255func dist#ft#FTm() 256 let n = 1 257 let saw_comment = 0 " Whether we've seen a multiline comment leader. 258 while n < 100 259 let line = getline(n) 260 if line =~ '^\s*/\*' 261 " /* ... */ is a comment in Objective C and Murphi, so we can't conclude 262 " it's either of them yet, but track this as a hint in case we don't see 263 " anything more definitive. 264 let saw_comment = 1 265 endif 266 if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|//\)' 267 setf objc 268 return 269 endif 270 if line =~ '^\s*%' 271 setf matlab 272 return 273 endif 274 if line =~ '^\s*(\*' 275 setf mma 276 return 277 endif 278 if line =~ '^\c\s*\(\(type\|var\)\>\|--\)' 279 setf murphi 280 return 281 endif 282 let n = n + 1 283 endwhile 284 285 if saw_comment 286 " We didn't see anything definitive, but this looks like either Objective C 287 " or Murphi based on the comment leader. Assume the former as it is more 288 " common. 289 setf objc 290 elseif exists("g:filetype_m") 291 " Use user specified default filetype for .m 292 exe "setf " . g:filetype_m 293 else 294 " Default is matlab 295 setf matlab 296 endif 297endfunc 298 299func dist#ft#FTmms() 300 let n = 1 301 while n < 10 302 let line = getline(n) 303 if line =~ '^\s*\(%\|//\)' || line =~ '^\*' 304 setf mmix 305 return 306 endif 307 if line =~ '^\s*#' 308 setf make 309 return 310 endif 311 let n = n + 1 312 endwhile 313 setf mmix 314endfunc 315 316" This function checks if one of the first five lines start with a dot. In 317" that case it is probably an nroff file: 'filetype' is set and 1 is returned. 318func dist#ft#FTnroff() 319 if getline(1)[0] . getline(2)[0] . getline(3)[0] . getline(4)[0] . getline(5)[0] =~ '\.' 320 setf nroff 321 return 1 322 endif 323 return 0 324endfunc 325 326func dist#ft#FTmm() 327 let n = 1 328 while n < 10 329 let line = getline(n) 330 if line =~ '^\s*\(#\s*\(include\|import\)\>\|@import\>\|/\*\)' 331 setf objcpp 332 return 333 endif 334 let n = n + 1 335 endwhile 336 setf nroff 337endfunc 338 339func dist#ft#FTpl() 340 if exists("g:filetype_pl") 341 exe "setf " . g:filetype_pl 342 else 343 " recognize Prolog by specific text in the first non-empty line 344 " require a blank after the '%' because Perl uses "%list" and "%translate" 345 let l = getline(nextnonblank(1)) 346 if l =~ '\<prolog\>' || l =~ '^\s*\(%\+\(\s\|$\)\|/\*\)' || l =~ ':-' 347 setf prolog 348 else 349 setf perl 350 endif 351 endif 352endfunc 353 354func dist#ft#FTinc() 355 if exists("g:filetype_inc") 356 exe "setf " . g:filetype_inc 357 else 358 let lines = getline(1).getline(2).getline(3) 359 if lines =~? "perlscript" 360 setf aspperl 361 elseif lines =~ "<%" 362 setf aspvbs 363 elseif lines =~ "<?" 364 setf php 365 else 366 call dist#ft#FTasmsyntax() 367 if exists("b:asmsyntax") 368 exe "setf " . fnameescape(b:asmsyntax) 369 else 370 setf pov 371 endif 372 endif 373 endif 374endfunc 375 376func dist#ft#FTprogress_cweb() 377 if exists("g:filetype_w") 378 exe "setf " . g:filetype_w 379 return 380 endif 381 if getline(1) =~ '&ANALYZE' || getline(3) =~ '&GLOBAL-DEFINE' 382 setf progress 383 else 384 setf cweb 385 endif 386endfunc 387 388func dist#ft#FTprogress_asm() 389 if exists("g:filetype_i") 390 exe "setf " . g:filetype_i 391 return 392 endif 393 " This function checks for an assembly comment the first ten lines. 394 " If not found, assume Progress. 395 let lnum = 1 396 while lnum <= 10 && lnum < line('$') 397 let line = getline(lnum) 398 if line =~ '^\s*;' || line =~ '^\*' 399 call dist#ft#FTasm() 400 return 401 elseif line !~ '^\s*$' || line =~ '^/\*' 402 " Not an empty line: Doesn't look like valid assembly code. 403 " Or it looks like a Progress /* comment 404 break 405 endif 406 let lnum = lnum + 1 407 endw 408 setf progress 409endfunc 410 411func dist#ft#FTprogress_pascal() 412 if exists("g:filetype_p") 413 exe "setf " . g:filetype_p 414 return 415 endif 416 " This function checks for valid Pascal syntax in the first ten lines. 417 " Look for either an opening comment or a program start. 418 " If not found, assume Progress. 419 let lnum = 1 420 while lnum <= 10 && lnum < line('$') 421 let line = getline(lnum) 422 if line =~ '^\s*\(program\|unit\|procedure\|function\|const\|type\|var\)\>' 423 \ || line =~ '^\s*{' || line =~ '^\s*(\*' 424 setf pascal 425 return 426 elseif line !~ '^\s*$' || line =~ '^/\*' 427 " Not an empty line: Doesn't look like valid Pascal code. 428 " Or it looks like a Progress /* comment 429 break 430 endif 431 let lnum = lnum + 1 432 endw 433 setf progress 434endfunc 435 436func dist#ft#FTr() 437 let max = line("$") > 50 ? 50 : line("$") 438 439 for n in range(1, max) 440 " Rebol is easy to recognize, check for that first 441 if getline(n) =~? '\<REBOL\>' 442 setf rebol 443 return 444 endif 445 endfor 446 447 for n in range(1, max) 448 " R has # comments 449 if getline(n) =~ '^\s*#' 450 setf r 451 return 452 endif 453 " Rexx has /* comments */ 454 if getline(n) =~ '^\s*/\*' 455 setf rexx 456 return 457 endif 458 endfor 459 460 " Nothing recognized, use user default or assume Rexx 461 if exists("g:filetype_r") 462 exe "setf " . g:filetype_r 463 else 464 " Rexx used to be the default, but R appears to be much more popular. 465 setf r 466 endif 467endfunc 468 469func dist#ft#McSetf() 470 " Rely on the file to start with a comment. 471 " MS message text files use ';', Sendmail files use '#' or 'dnl' 472 for lnum in range(1, min([line("$"), 20])) 473 let line = getline(lnum) 474 if line =~ '^\s*\(#\|dnl\)' 475 setf m4 " Sendmail .mc file 476 return 477 elseif line =~ '^\s*;' 478 setf msmessages " MS Message text file 479 return 480 endif 481 endfor 482 setf m4 " Default: Sendmail .mc file 483endfunc 484 485" Called from filetype.vim and scripts.vim. 486func dist#ft#SetFileTypeSH(name) 487 if did_filetype() 488 " Filetype was already detected 489 return 490 endif 491 if expand("<amatch>") =~ g:ft_ignore_pat 492 return 493 endif 494 if a:name =~ '\<csh\>' 495 " Some .sh scripts contain #!/bin/csh. 496 call dist#ft#SetFileTypeShell("csh") 497 return 498 elseif a:name =~ '\<tcsh\>' 499 " Some .sh scripts contain #!/bin/tcsh. 500 call dist#ft#SetFileTypeShell("tcsh") 501 return 502 elseif a:name =~ '\<zsh\>' 503 " Some .sh scripts contain #!/bin/zsh. 504 call dist#ft#SetFileTypeShell("zsh") 505 return 506 elseif a:name =~ '\<ksh\>' 507 let b:is_kornshell = 1 508 if exists("b:is_bash") 509 unlet b:is_bash 510 endif 511 if exists("b:is_sh") 512 unlet b:is_sh 513 endif 514 elseif exists("g:bash_is_sh") || a:name =~ '\<bash\>' || a:name =~ '\<bash2\>' 515 let b:is_bash = 1 516 if exists("b:is_kornshell") 517 unlet b:is_kornshell 518 endif 519 if exists("b:is_sh") 520 unlet b:is_sh 521 endif 522 elseif a:name =~ '\<sh\>' 523 let b:is_sh = 1 524 if exists("b:is_kornshell") 525 unlet b:is_kornshell 526 endif 527 if exists("b:is_bash") 528 unlet b:is_bash 529 endif 530 endif 531 call dist#ft#SetFileTypeShell("sh") 532endfunc 533 534" For shell-like file types, check for an "exec" command hidden in a comment, 535" as used for Tcl. 536" Also called from scripts.vim, thus can't be local to this script. 537func dist#ft#SetFileTypeShell(name) 538 if did_filetype() 539 " Filetype was already detected 540 return 541 endif 542 if expand("<amatch>") =~ g:ft_ignore_pat 543 return 544 endif 545 let l = 2 546 while l < 20 && l < line("$") && getline(l) =~ '^\s*\(#\|$\)' 547 " Skip empty and comment lines. 548 let l = l + 1 549 endwhile 550 if l < line("$") && getline(l) =~ '\s*exec\s' && getline(l - 1) =~ '^\s*#.*\\$' 551 " Found an "exec" line after a comment with continuation 552 let n = substitute(getline(l),'\s*exec\s\+\([^ ]*/\)\=', '', '') 553 if n =~ '\<tclsh\|\<wish' 554 setf tcl 555 return 556 endif 557 endif 558 exe "setf " . a:name 559endfunc 560 561func dist#ft#CSH() 562 if did_filetype() 563 " Filetype was already detected 564 return 565 endif 566 if exists("g:filetype_csh") 567 call dist#ft#SetFileTypeShell(g:filetype_csh) 568 elseif &shell =~ "tcsh" 569 call dist#ft#SetFileTypeShell("tcsh") 570 else 571 call dist#ft#SetFileTypeShell("csh") 572 endif 573endfunc 574 575let s:ft_rules_udev_rules_pattern = '^\s*\cudev_rules\s*=\s*"\([^"]\{-1,}\)/*".*' 576func dist#ft#FTRules() 577 let path = expand('<amatch>:p') 578 if path =~ '^/\(etc/udev/\%(rules\.d/\)\=.*\.rules\|lib/udev/\%(rules\.d/\)\=.*\.rules\)$' 579 setf udevrules 580 return 581 endif 582 if path =~ '^/etc/ufw/' 583 setf conf " Better than hog 584 return 585 endif 586 if path =~ '^/\(etc\|usr/share\)/polkit-1/rules\.d' 587 setf javascript 588 return 589 endif 590 try 591 let config_lines = readfile('/etc/udev/udev.conf') 592 catch /^Vim\%((\a\+)\)\=:E484/ 593 setf hog 594 return 595 endtry 596 let dir = expand('<amatch>:p:h') 597 for line in config_lines 598 if line =~ s:ft_rules_udev_rules_pattern 599 let udev_rules = substitute(line, s:ft_rules_udev_rules_pattern, '\1', "") 600 if dir == udev_rules 601 setf udevrules 602 endif 603 break 604 endif 605 endfor 606 setf hog 607endfunc 608 609func dist#ft#SQL() 610 if exists("g:filetype_sql") 611 exe "setf " . g:filetype_sql 612 else 613 setf sql 614 endif 615endfunc 616 617" If the file has an extension of 't' and is in a directory 't' or 'xt' then 618" it is almost certainly a Perl test file. 619" If the first line starts with '#' and contains 'perl' it's probably a Perl 620" file. 621" (Slow test) If a file contains a 'use' statement then it is almost certainly 622" a Perl file. 623func dist#ft#FTperl() 624 let dirname = expand("%:p:h:t") 625 if expand("%:e") == 't' && (dirname == 't' || dirname == 'xt') 626 setf perl 627 return 1 628 endif 629 if getline(1)[0] == '#' && getline(1) =~ 'perl' 630 setf perl 631 return 1 632 endif 633 let save_cursor = getpos('.') 634 call cursor(1,1) 635 let has_use = search('^use\s\s*\k', 'c', 30) 636 call setpos('.', save_cursor) 637 if has_use 638 setf perl 639 return 1 640 endif 641 return 0 642endfunc 643 644" Choose context, plaintex, or tex (LaTeX) based on these rules: 645" 1. Check the first line of the file for "%&<format>". 646" 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords. 647" 3. Default to "plain" or to g:tex_flavor, can be set in user's vimrc. 648func dist#ft#FTtex() 649 let firstline = getline(1) 650 if firstline =~ '^%&\s*\a\+' 651 let format = tolower(matchstr(firstline, '\a\+')) 652 let format = substitute(format, 'pdf', '', '') 653 if format == 'tex' 654 let format = 'latex' 655 elseif format == 'plaintex' 656 let format = 'plain' 657 endif 658 elseif expand('%') =~ 'tex/context/.*/.*.tex' 659 let format = 'context' 660 else 661 " Default value, may be changed later: 662 let format = exists("g:tex_flavor") ? g:tex_flavor : 'plain' 663 " Save position, go to the top of the file, find first non-comment line. 664 let save_cursor = getpos('.') 665 call cursor(1,1) 666 let firstNC = search('^\s*[^[:space:]%]', 'c', 1000) 667 if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword. 668 let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>' 669 let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>' 670 let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)', 671 \ 'cnp', firstNC + 1000) 672 if kwline == 1 " lpat matched 673 let format = 'latex' 674 elseif kwline == 2 " cpat matched 675 let format = 'context' 676 endif " If neither matched, keep default set above. 677 " let lline = search('^\s*\\\%(' . lpat . '\)', 'cn', firstNC + 1000) 678 " let cline = search('^\s*\\\%(' . cpat . '\)', 'cn', firstNC + 1000) 679 " if cline > 0 680 " let format = 'context' 681 " endif 682 " if lline > 0 && (cline == 0 || cline > lline) 683 " let format = 'tex' 684 " endif 685 endif " firstNC 686 call setpos('.', save_cursor) 687 endif " firstline =~ '^%&\s*\a\+' 688 689 " Translation from formats to file types. TODO: add AMSTeX, RevTex, others? 690 if format == 'plain' 691 setf plaintex 692 elseif format == 'context' 693 setf context 694 else " probably LaTeX 695 setf tex 696 endif 697 return 698endfunc 699 700func dist#ft#FTxml() 701 let n = 1 702 while n < 100 && n <= line("$") 703 let line = getline(n) 704 " DocBook 4 or DocBook 5. 705 let is_docbook4 = line =~ '<!DOCTYPE.*DocBook' 706 let is_docbook5 = line =~ ' xmlns="http://docbook.org/ns/docbook"' 707 if is_docbook4 || is_docbook5 708 let b:docbk_type = "xml" 709 if is_docbook5 710 let b:docbk_ver = 5 711 else 712 let b:docbk_ver = 4 713 endif 714 setf docbk 715 return 716 endif 717 if line =~ 'xmlns:xbl="http://www.mozilla.org/xbl"' 718 setf xbl 719 return 720 endif 721 let n += 1 722 endwhile 723 setf xml 724endfunc 725 726func dist#ft#FTy() 727 let n = 1 728 while n < 100 && n <= line("$") 729 let line = getline(n) 730 if line =~ '^\s*%' 731 setf yacc 732 return 733 endif 734 if getline(n) =~ '^\s*\(#\|class\>\)' && getline(n) !~ '^\s*#\s*include' 735 setf racc 736 return 737 endif 738 let n = n + 1 739 endwhile 740 setf yacc 741endfunc 742 743func dist#ft#Redif() 744 let lnum = 1 745 while lnum <= 5 && lnum < line('$') 746 if getline(lnum) =~ "^\ctemplate-type:" 747 setf redif 748 return 749 endif 750 let lnum = lnum + 1 751 endwhile 752endfunc 753 754 755" Restore 'cpoptions' 756let &cpo = s:cpo_save 757unlet s:cpo_save 758