vis
a vi-like editor based on Plan 9's structural regular expressions
git clone https://9o.is/git/vis.git
filetype.lua
(12913B)
1 vis.ftdetect = {}
2
3 vis.ftdetect.ignoresuffixes = {
4 "~+$", "%.orig$", "%.bak$", "%.old$", "%.new$"
5 }
6
7 vis.ftdetect.filetypes = {
8 actionscript = {
9 ext = { "%.as$", "%.asc$" },
10 },
11 ada = {
12 ext = { "%.adb$", "%.ads$" },
13 },
14 c = {
15 ext = { "%.c$", "%.C$", "%.h$" },
16 mime = { "text/x-c" },
17 },
18 antlr = {
19 ext = { "%.g$", "%.g4$" },
20 },
21 apdl = {
22 ext = { "%.ans$", "%.inp$", "%.mac$" },
23 },
24 apl = {
25 ext = { "%.apl$" }
26 },
27 applescript = {
28 ext = { "%.applescript$" },
29 },
30 asciidoc = {
31 ext = { "%.adoc$" },
32 },
33 asm = {
34 ext = { "%.asm$", "%.ASM$", "%.s$", "%.S$" },
35 },
36 asp = {
37 ext = { "%.asa$", "%.asp$", "%.hta$" },
38 },
39 autoit = {
40 ext = { "%.au3$", "%.a3x$" },
41 },
42 awk = {
43 hashbang = { "^/usr/bin/[mng]awk%s+%-f" },
44 utility = { "^[mgn]?awk$", "^goawk$" },
45 ext = { "%.awk$" },
46 },
47 bash = {
48 utility = { "^[db]ash$", "^sh$","^t?csh$","^zsh$" },
49 ext = { "%.bash$", "%.csh$", "%.sh$", "%.zsh$" ,"^APKBUILD$", "%.ebuild$", "^.bashrc$", "^.bash_profile$" },
50 mime = { "text/x-shellscript", "application/x-shellscript" },
51 },
52 batch = {
53 ext = { "%.bat$", "%.cmd$" },
54 },
55 bibtex = {
56 ext = { "%.bib$" },
57 },
58 boo = {
59 ext = { "%.boo$" },
60 },
61 caml = {
62 ext = { "%.caml$", "%.ml$", "%.mli$", "%.mll$", "%.mly$" },
63 },
64 chuck = {
65 ext = { "%.ck$" },
66 },
67 clojure = {
68 ext = { "%.clj$", "%.cljc$", "%.cljs$", "%.edn$" }
69 },
70 cmake = {
71 ext = { "%.cmake$", "%.cmake.in$", "%.ctest$", "%.ctest.in$" },
72 },
73 coffeescript = {
74 ext = { "%.coffee$" },
75 mime = { "text/x-coffee" },
76 },
77 cpp = {
78 ext = { "%.cpp$", "%.cxx$", "%.c++$", "%.cc$", "%.hh$", "%.hpp$", "%.hxx$", "%.h++$" },
79 mime = { "text/x-c++" },
80 },
81 crontab = {
82 ext = { "^crontab.*$" },
83 cmd = { "set savemethod inplace" },
84 },
85 crystal = {
86 ext = { "%.cr$" },
87 },
88 csharp = {
89 ext = { "%.cs$" },
90 },
91 css = {
92 ext = { "%.css$" },
93 mime = { "text/x-css" },
94 },
95 cuda = {
96 ext = { "%.cu$", "%.cuh$" },
97 },
98 dart = {
99 ext = { "%.dart$" },
100 },
101 desktop = {
102 ext = { "%.desktop$" },
103 },
104 diff = {
105 ext = { "%.diff$", "%.patch$", "%.rej$" },
106 },
107 d = {
108 ext = { "%.d$", "%.di$" },
109 },
110 dockerfile = {
111 ext = { "^Dockerfile$", "%.Dockerfile$" },
112 },
113 dot = {
114 ext = { "%.dot$" },
115 },
116 dsv = {
117 ext = { "^group$", "^gshadow$", "^passwd$", "^shadow$" },
118 },
119 eiffel = {
120 ext = { "%.e$", "%.eif$" },
121 },
122 elixir = {
123 ext = { "%.ex$", "%.exs$" },
124 },
125 elm = {
126 ext = { "%.elm$" },
127 },
128 mail = {
129 ext = { "%.eml$" },
130 },
131 erlang = {
132 ext = { "%.erl$", "%.hrl$" },
133 },
134 fantom = {
135 ext = { "%.fan$" },
136 },
137 faust = {
138 ext = { "%.dsp$" },
139 },
140 fennel = {
141 ext = { "%.fnl$" },
142 },
143 fish = {
144 utility = { "^fish$" },
145 ext = { "%.fish$" },
146 },
147 factor = {
148 ext = { "%.factor" },
149 },
150 forth = {
151 ext = { "%.forth$", "%.frt$", "%.fs$", "%.fth$" },
152 },
153 fortran = {
154 ext = { "%.f$", "%.for$", "%.ftn$", "%.fpp$", "%.f77$", "%.f90$", "%.f95$", "%.f03$", "%.f08$" },
155 },
156 fsharp = {
157 ext = { "%.fs$" },
158 },
159 fstab = {
160 ext = { "^fstab$" },
161 },
162 gap = {
163 ext = { "%.g$", "%.gd$", "%.gi$", "%.gap$" },
164 },
165 gemini = {
166 ext = { "%.gmi" },
167 mime = { "text/gemini" },
168 },
169 gettext = {
170 ext = { "%.po$", "%.pot$" },
171 },
172 gherkin = {
173 ext = { "%.feature$" },
174 },
175 ['git-commit'] = {
176 alt_name = "diff",
177 ext = { "^COMMIT_EDITMSG$" },
178 cmd = { "set colorcolumn 72" },
179 },
180 ['git-rebase'] = {
181 ext = { "git%-rebase%-todo" },
182 },
183 gleam = {
184 ext = { "%.gleam$" },
185 },
186 glsl = {
187 ext = { "%.glsl[fv]?$" },
188 },
189 gnuplot = {
190 ext = { "%.dem$", "%.plt$" },
191 },
192 go = {
193 ext = { "%.go$" },
194 },
195 groovy = {
196 ext = { "%.groovy$", "%.gvy$", "^Jenkinsfile$" },
197 },
198 gtkrc = {
199 ext = { "%.gtkrc$" },
200 },
201 hare = {
202 ext = { "%.ha$" }
203 },
204 haskell = {
205 ext = { "%.hs$" },
206 mime = { "text/x-haskell" },
207 },
208 html = {
209 ext = { "%.[sx]?htm[l]?$" },
210 mime = { "text/x-html" },
211 },
212 icon = {
213 ext = { "%.icn$" },
214 },
215 idl = {
216 ext = { "%.idl$", "%.odl$" },
217 },
218 inform = {
219 ext = { "%.inf$", "%.ni$" },
220 },
221 ini = {
222 ext = { "%.cfg$", "%.cnf$", "%.conf$", "%.inf$", "%.ini$", "%.reg$" },
223 },
224 io_lang = {
225 ext = { "%.io$" },
226 },
227 java = {
228 ext = { "%.bsh$", "%.java$" },
229 },
230 javascript = {
231 ext = { "%.cjs$", "%.js$", "%.jsfl$", "%.mjs$", "%.jsx$" },
232 },
233 jq = {
234 ext = { "%.jq$" },
235 },
236 json = {
237 ext = { "%.json$" },
238 mime = { "text/x-json" },
239 },
240 jsp = {
241 ext = { "%.jsp$" },
242 },
243 julia = {
244 ext = { "%.jl$" },
245 },
246 latex = {
247 ext = { "%.bbl$", "%.cls$", "%.dtx$", "%.ins$", "%.ltx$", "%.tex$", "%.sty$" },
248 mime = { "text/x-tex" },
249 },
250 ledger = {
251 ext = { "%.ledger$", "%.journal$" },
252 },
253 less = {
254 ext = { "%.less$" },
255 },
256 lilypond = {
257 ext = { "%.ily$", "%.ly$" },
258 },
259 lisp = {
260 ext = { "%.cl$", "%.el$", "%.lisp$", "%.lsp$" },
261 mime = { "text/x-lisp" },
262 },
263 litcoffee = {
264 ext = { "%.litcoffee$" },
265 },
266 logtalk = {
267 ext = { "%.lgt$" },
268 },
269 lua = {
270 utility = {"^lua%-?5?%d?$", "^lua%-?5%.%d$" },
271 ext = { "%.lua$" },
272 mime = { "text/x-lua" },
273 },
274 makefile = {
275 hashbang = {"^#!/usr/bin/make"},
276 utility = {"^make$"},
277 ext = { "%.iface$", "%.mak$", "%.mk$", "^GNUmakefile$", "^makefile$", "^Makefile$" },
278 mime = { "text/x-makefile" },
279 },
280 man = {
281 ext = { "%.[1-9][xp]?$", "%.ms$", "%.me$", "%.mom$", "%.mm$", "%.tmac$" },
282 },
283 markdown = {
284 ext = { "%.md$", "%.markdown$" },
285 mime = { "text/x-markdown" },
286 },
287 mediawiki = {
288 ext = { "%.wiki$" },
289 },
290 meson = {
291 ext = { "^meson%.build$", "%.meson$", "^meson_options%.txt$", "^meson%.options$" },
292 },
293 modula2 = {
294 ext = { "%.mod$", "%.def$" },
295 },
296 modula3 = {
297 ext = { "%.mg$", "%.ig$", "%.i3$", "%.m3$" },
298 },
299 moonscript = {
300 ext = { "%.moon$" },
301 mime = { "text/x-moon" },
302 },
303 myrddin = {
304 ext = { "%.myr$" },
305 },
306 nemerle = {
307 ext = { "%.n$" },
308 },
309 networkd = {
310 ext = { "%.link$", "%.network$", "%.netdev$" },
311 },
312 nim = {
313 ext = { "%.nim$" },
314 },
315 nix = {
316 ext = { "%.nix$" },
317 },
318 nsis = {
319 ext = { "%.nsh$", "%.nsi$", "%.nsis$" },
320 },
321 objective_c = {
322 ext = { "%.m$", "%.mm$", "%.objc$" },
323 mime = { "text/x-objc" },
324 },
325 org = {
326 ext = { "%.org$" },
327 },
328 pascal = {
329 ext = { "%.dpk$", "%.dpr$", "%.p$", "%.pas$" },
330 },
331 perl = {
332 ext = { "%.al$", "%.perl$", "%.pl$", "%.pm$", "%.pod$" },
333 mime = { "text/x-perl" },
334 },
335 php = {
336 ext = { "%.inc$", "%.php$", "%.php3$", "%.php4$", "%.phtml$" },
337 },
338 pico8 = {
339 ext = { "%.p8$" },
340 },
341 pike = {
342 ext = { "%.pike$", "%.pmod$" },
343 },
344 pkgbuild = {
345 ext = { "^PKGBUILD$", "%.PKGBUILD$" },
346 },
347 pony = {
348 ext = { "%.pony$" },
349 },
350 powershell = {
351 ext = { "%.ps1$", "%.psm1$" },
352 },
353 prolog = {
354 ext = { "%.pl$", "%.pro$", "%.prolog$" },
355 },
356 props = {
357 ext = { "%.props$", "%.properties$" },
358 },
359 protobuf = {
360 ext = { "%.proto$" },
361 },
362 ps = {
363 ext = { "%.eps$", "%.ps$" },
364 },
365 pure = {
366 ext = { "%.pure$" },
367 },
368 python = {
369 utility = { "^python%d?" },
370 ext = { "%.sc$", "%.py[iwx]?$" },
371 mime = { "text/x-python", "text/x-script.python" },
372 },
373 reason = {
374 ext = { "%.re$" },
375 },
376 rc = {
377 utility = {"^rc$"},
378 ext = { "%.rc$", "%.es$" },
379 },
380 rebol = {
381 ext = { "%.r$", "%.reb$" },
382 },
383 rest = {
384 ext = { "%.rst$" },
385 },
386 rexx = {
387 ext = { "%.orx$", "%.rex$" },
388 },
389 rhtml = {
390 ext = { "%.erb$", "%.rhtml$" },
391 },
392 routeros = {
393 ext = { "%.rsc" },
394 detect = function(_, data)
395 return data:match("^#.* by RouterOS")
396 end
397 },
398 rpmspec = {
399 ext = { "%.spec$" },
400 },
401 r = {
402 ext = { "%.R$", "%.Rout$", "%.Rhistory$", "%.Rt$", "Rout.save", "Rout.fail" },
403 },
404 ruby = {
405 ext = { "%.Rakefile$", "%.rake$", "%.rb$", "%.rbw$", "^Vagrantfile$" },
406 mime = { "text/x-ruby" },
407 },
408 rust = {
409 ext = { "%.rs$" },
410 mime = { "text/x-rust" },
411 },
412 sass = {
413 ext = { "%.sass$", "%.scss$" },
414 mime = { "text/x-sass", "text/x-scss" },
415 },
416 scala = {
417 ext = { "%.scala$" },
418 mime = { "text/x-scala" },
419 },
420 scheme = {
421 ext = { "%.rkt$", "%.sch$", "%.scm$", "%.sld$", "%.sls$", "%.ss$" },
422 },
423 smalltalk = {
424 ext = { "%.changes$", "%.st$", "%.sources$" },
425 },
426 sml = {
427 ext = { "%.sml$", "%.fun$", "%.sig$" },
428 },
429 snobol4 = {
430 ext = { "%.sno$", "%.SNO$" },
431 },
432 spin = {
433 ext = { "%.spin$" }
434 },
435 sql= {
436 ext = { "%.ddl$", "%.sql$" },
437 },
438 strace = {
439 detect = function(_, data)
440 return data:match("^execve%(")
441 end
442 },
443 systemd = {
444 ext = {
445 "%.automount$", "%.container$", "%.device$", "%.mount$",
446 "%.path$", "%.scope$", "%.service$", "%.slice$", "%.socket$",
447 "%.swap$", "%.target$", "%.timer$"
448 },
449 },
450 taskpaper = {
451 ext = { "%.taskpaper$" },
452 },
453 tcl = {
454 utility = {"^tclsh$", "^jimsh$" },
455 ext = { "%.tcl$", "%.tk$" },
456 },
457 texinfo = {
458 ext = { "%.texi$" },
459 },
460 text = {
461 ext = { "%.txt$" },
462 -- Do *not* list mime "text/plain" here, it is covered below,
463 -- see 'try text lexer as a last resort'
464 },
465 toml = {
466 ext = { "%.toml$" },
467 },
468 typescript = {
469 ext = { "%.ts$", "%.tsx$" },
470 },
471 typst = {
472 ext = { "%.typ$", "%.typst$" },
473 },
474 vala = {
475 ext = { "%.vala$" }
476 },
477 usfm = {
478 ext = { "%.usfm$" }
479 },
480 vb = {
481 ext = {
482 "%.asa$", "%.bas$", "%.ctl$", "%.dob$",
483 "%.dsm$", "%.dsr$", "%.frm$", "%.pag$", "%.vb$",
484 "%.vba$", "%.vbs$"
485 },
486 },
487 vcard = {
488 ext = { "%.vcf$", "%.vcard$" },
489 },
490 verilog = {
491 ext = { "%.v$", "%.ver$", "%.sv$" },
492 },
493 vhdl = {
494 ext = { "%.vh$", "%.vhd$", "%.vhdl$" },
495 },
496 wsf = {
497 ext = { "%.wsf$" },
498 },
499 xs = {
500 ext = { "%.xs$", "^%.xsin$", "^%.xsrc$" },
501 },
502 xml = {
503 ext = {
504 "%.dtd$", "%.glif$", "%.plist$", "%.svg$", "%.xml$",
505 "%.xsd$", "%.xsl$", "%.xslt$", "%.xul$"
506 },
507 mime = { "text/xml" },
508 },
509 xtend = {
510 ext = {"%.xtend$" },
511 },
512 yaml = {
513 ext = { "%.yaml$", "%.yml$" },
514 mime = { "text/x-yaml" },
515 },
516 zig = {
517 ext = { "%.zig$" },
518 },
519 }
520
521 vis.events.subscribe(vis.events.WIN_OPEN, function(win)
522
523 local set_filetype = function(syntax, filetype)
524 for _, cmd in pairs(filetype.cmd or {}) do
525 vis:command(cmd)
526 end
527 win:set_syntax(filetype.alt_name or syntax)
528 end
529
530 local path = win.file.name -- filepath
531 local mime
532
533 if path and #path > 0 then
534 local name = path:match("[^/]+$") -- filename
535 if name then
536 local unchanged
537 while #name > 0 and name ~= unchanged do
538 unchanged = name
539 for _, pattern in ipairs(vis.ftdetect.ignoresuffixes) do
540 name = name:gsub(pattern, "")
541 end
542 end
543 end
544
545 if name and #name > 0 then
546 -- detect filetype by filename ending with a configured extension
547 for lang, ft in pairs(vis.ftdetect.filetypes) do
548 for _, pattern in pairs(ft.ext or {}) do
549 if name:match(pattern) then
550 set_filetype(lang, ft)
551 return
552 end
553 end
554 end
555 end
556
557 -- run file(1) to determine mime type
558 local file = io.popen(string.format("file -bL --mime-type -- '%s'", path:gsub("'", "'\\''")))
559 if file then
560 mime = file:read('*all')
561 file:close()
562 if mime then
563 mime = mime:gsub('%s*$', '')
564 end
565 if mime and #mime > 0 then
566 for lang, ft in pairs(vis.ftdetect.filetypes) do
567 for _, ft_mime in pairs(ft.mime or {}) do
568 if mime == ft_mime then
569 set_filetype(lang, ft)
570 return
571 end
572 end
573 end
574 end
575 end
576 end
577
578 -- pass first few bytes of file to custom file type detector functions
579 local file = win.file
580 local data = file:content(0, 256)
581 if data and #data > 0 then
582 for lang, ft in pairs(vis.ftdetect.filetypes) do
583 if type(ft.detect) == 'function' and ft.detect(file, data) then
584 set_filetype(lang, ft)
585 return
586 end
587 end
588
589 --[[ hashbang check
590 hashbangs only have command <SPACE> argument
591 if /env, find utility in args
592 discard first arg if /-[^S]*S/; and all subsequent /=/
593 NOTE: this means you can't have a command with /^-|=/
594 return first field, which should be the utility.
595 NOTE: long-options unsupported
596 --]]
597 local fullhb, utility = data:match"^#![ \t]*(/+[^/\n]+[^\n]*)"
598 if fullhb then
599 local i, field = 1, {}
600 for m in fullhb:gmatch"%g+" do field[i],i = m,i+1 end
601 -- NOTE: executables should not have a space (or =, see below)
602 if field[1]:match"/env$" then
603 table.remove(field,1)
604 -- it is assumed that the first argument are short options, with -S inside
605 if string.match(field[1] or "", "^%-[^S-]*S") then -- -S found
606 table.remove(field,1)
607 -- skip all name=value
608 while string.match(field[1] or "","=") do
609 table.remove(field,1)
610 end
611 -- (hopefully) whatever is left in field[1] should be the utility or nil
612 end
613 end
614 utility = string.match(field[1] or "", "[^/]+$") -- remove filepath
615 end
616
617 local function searcher(tbl, subject)
618 for _, pattern in ipairs(tbl or {}) do
619 if string.match(subject, pattern) then
620 return true
621 end
622 end
623 return false
624 end
625
626 if utility or fullhb then
627 for lang, ft in pairs(vis.ftdetect.filetypes) do
628 if
629 utility and searcher(ft.utility, utility)
630 or
631 fullhb and searcher(ft.hashbang, fullhb)
632 then
633 set_filetype(lang, ft)
634 return
635 end
636 end
637 end
638 end
639
640 -- try text lexer as a last resort
641 if (mime or 'text/plain'):match('^text/.+$') then
642 set_filetype('text', vis.ftdetect.filetypes.text)
643 return
644 end
645
646 win:set_syntax(nil)
647 end)
648