dippin-dotfiles/.config/hilbish/syntax.lua
2025-02-04 21:06:01 -07:00

192 lines
6.6 KiB
Lua
Executable file

-- Syntax highlighting implementation for Hilbish
local colors = require "lunacolors"
local hilbish = require "hilbish"
local fs = require "fs"
local utils = require ".utils"
local M = {}
local SQ = 0x27 -- U+0027 APORSTROPHE
local DQ = 0x22 -- U+0022 QUOTATION MARK
local SP = 0x20 -- U+0020 SPACE
local HT = 0x09 -- U+0009 CHARACTER TABULATION
local LF = 0x0A -- U+000A LINE FEED (LF)
local CR = 0x0D -- U+000D CARRIAGE RETURN (CR)
local BS = 0x5C -- U+005C BACKSLASH
-- sh builtins
local sh_builtins = {
"admin", "aliasar", "asa", "at", "awk", "basename", "batch", "bc", "bg", "c99", "cal", "cat", "cd", "cflow", "chgrp",
"chmod",
"chown", "cksum", "cmp", "comm", "command", "compress", "cp", "crontab", "csplit", "ctags", "cut", "cxref", "date",
"dd",
"delta", "df", "diff", "dirname", "du", "echo", "ed", "env", "ex", "expand", "expr", "false", "fc", "fg", "file",
"find", "fold",
"fort77", "fuser", "gencat", "get", "getconf", "getopts", "grep", "hash", "head", "iconv", "id", "ipcrm", "ipcs",
"jobs",
"join", "kill", "lex", "link", "ln", "locale", "localedef", "logger", "logname", "lp", "ls", "m4", "mailx", "make",
"man",
"mesg", "mkdir", "mkfifo", "more", "mv", "newgrp", "nice", "nl", "nm", "nohup", "od", "paste", "patch", "pathchk",
"pax",
"pr", "printf", "prs", "ps", "pwd", "qalter", "qdel", "qhold", "qmove", "qmsg", "qrerun", "qrls", "qselect", "qsig",
"qstat", "qsub", "read", "renice", "rm", "rmdel", "rmdir", "sact", "sccs", "sed", "sh", "sleep", "sort", "split",
"strings", "strip", "stty", "tabs", "tail", "talk", "tee", "test", "time", "touch", "tput", "tr", "true", "tsort",
"tty", "type", "ulimit", "umask", "unalias", "uname", "uncompress", "unexpand", "unget", "uniq", "unlink",
"uucp", "uudecode", "uuencode", "uustat", "uux", "val", "vi", "wait", "wc", "what", "who", "write", "xargs", "yacc",
"zcat"
}
-- sh keywords
local sh_keywords = {
"if", "then", "else", "elif", "fi", "case", "esac", "for", "select", "while", "until", "do", "done", "in", "function",
"time", "coproc"
}
-- Determine if this string is a significant keyword, and if so, what it is
function M.is_cmd(str)
if utils.contains(sh_builtins, str) then
return "builtin"
elseif utils.contains(sh_keywords, str) then
return "keyword"
elseif hilbish.which(str) ~= nil then
return "cmd"
else
-- Try to use this as a filesystem glob
-- This may fail with an error, so we need to make sure that
-- doesn't happen first.
str = str:gsub("~", os.getenv("HOME"))
if pcall(fs.glob, str) then
local globs = fs.glob(str)
if #globs > 0 then
return "file"
end
end
end
return nil
end
-- Crude sh lexer
function M.lex_sh(s)
local state = nil
local escape = false
local result = {}
local point = 0
local index = 0
-- Go through the string
for i = 1, #s do
index = i
local c = s:byte(i)
local v = string.char(c)
-- Single quoted string state
if state == SQ then
if c == SQ then
state = nil
result[#result + 1] = { string.sub(s, point, i), "string" }
point = i + 1
end
-- Double quoted string state
elseif state == DQ then
if escape then
escape = false
else
if c == DQ then
state = nil
result[#result + 1] = { string.sub(s, point, i), "string" }
point = i + 1
elseif c == BS then
escape = true
end
end
-- Default state
else
if escape then
escape = false
elseif c == SP or c == HT or c == LF or c == CR then
if point ~= i then
result[#result + 1] = { string.sub(s, point, i - 1), "other" }
end
result[#result + 1] = { v, "whitespace" }
point = i + 1
elseif c == DQ then
state = DQ
if point ~= i then
result[#result + 1] = { string.sub(s, point, i - 1), "other" }
end
point = i
elseif c == SQ then
state = SQ
if point ~= i then
result[#result + 1] = { string.sub(s, point, i - 1), "other" }
end
point = i
elseif c == BS then
escape = true
end
end
end
-- If we are still in a string then say as such
if state ~= nil then
result[#result + 1] = { string.sub(s, point, index), "string" }
-- Otherwise, return the rest as a regular token
else
if point ~= index then
result[#result + 1] = { string.sub(s, point, index), "other" }
else
local c = s:byte(index)
-- If the final character isn't a space, render it anyway
-- This is a side effect of the space handling code. TODO: fix.
if c ~= SP and c ~= HT and c ~= LF and c ~= CR then
result[#result + 1] = { string.sub(s, point, index), "other" }
end
end
end
return result
end
-- Sort the table's key pairs
function M.pairsByKeys(t, f)
local a = {}
for n in pairs(t) do table.insert(a, n) end
table.sort(a, f)
local i = 0
local iter = function()
i = i + 1
if a[i] == nil then
return nil
else
return a[i], t[a[i]]
end
end
return iter
end
-- sh highlighter
function M.sh(str)
-- Parse the string
local table = M.lex_sh(str)
-- Construct the format string
local format_str = ""
for _, v in M.pairsByKeys(table) do
if v[2] == "string" then
format_str = format_str .. "{red}" .. v[1]
else
local cmd = M.is_cmd(v[1])
if cmd == "cmd" then
format_str = format_str .. "{bold}{brightGreen}" .. v[1] .. "{reset}"
elseif cmd == "builtin" then
format_str = format_str .. "{bold}{brightBlue}" .. v[1] .. "{reset}"
elseif cmd == "keyword" then
format_str = format_str .. "{bold}{brightMagenta}" .. v[1] .. "{reset}"
elseif cmd == "file" then
format_str = format_str .. "{bold}{brightYellow}" .. v[1] .. "{reset}"
else
format_str = format_str .. "{brightWhite}" .. v[1]
end
end
end
return colors.format(format_str)
end
return M