xref: /vim-8.2.3635/runtime/autoload/dist/ft.vim (revision 12ee7ff0)
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