192 lines
6.6 KiB
Lua
Executable file
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
|