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