// JobFunction is a representation of a job (this data structure is what is loaded
// into the jobs channel)
type JobFunction struct {
- Function func(string, ...string)
+ Function func(string, ...interface{})
Output string
- Args []string
+ Args []interface{}
}
// A CallbackFile is the data structure that makes it possible to catch stderr and stdout write events
type CallbackFile struct {
io.Writer
- callback func(string, ...string)
- args []string
+ callback func(string, ...interface{})
+ args []interface{}
}
func (f *CallbackFile) Write(data []byte) (int, error) {
// JobStart starts a shell command in the background with the given callbacks
// It returns an *exec.Cmd as the job id
-func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
+func JobStart(cmd string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
return JobSpawn("sh", []string{"-c", cmd}, onStdout, onStderr, onExit, userargs...)
}
// JobSpawn starts a process with args in the background with the given callbacks
// It returns an *exec.Cmd as the job id
-func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...string) *exec.Cmd {
+func JobSpawn(cmdName string, cmdArgs []string, onStdout, onStderr, onExit string, userargs ...interface{}) *exec.Cmd {
// Set up everything correctly if the functions have been provided
proc := exec.Command(cmdName, cmdArgs...)
var outbuf bytes.Buffer
// luaFunctionJob returns a function that will call the given lua function
// structured as a job call i.e. the job output and arguments are provided
// to the lua function
-func luaFunctionJob(fn string) func(string, ...string) {
+func luaFunctionJob(fn string) func(string, ...interface{}) {
luaFn := strings.Split(fn, ".")
plName, plFn := luaFn[0], luaFn[1]
pl := config.FindPlugin(plName)
- return func(output string, args ...string) {
+ return func(output string, args ...interface{}) {
var luaArgs []lua.LValue
+ luaArgs = append(luaArgs, luar.New(ulua.L, output))
for _, v := range args {
luaArgs = append(luaArgs, luar.New(ulua.L, v))
}
}
}
}
-
-func unpack(old []string) []interface{} {
- new := make([]interface{}, len(old))
- for i, v := range old {
- new[i] = v
- }
- return new
-}
-if GetOption("linter") == nil then
- AddOption("linter", true)
-end
+local runtime = import("runtime")
+local filepath = import("path/filepath")
+local shell = import("micro/shell")
+local buffer = import("micro/buffer")
+local micro = import("micro")
-MakeCommand("lint", "linter.lintCommand", 0)
+local linters = {}
-function lintCommand()
- CurView():Save(false)
- runLinter()
+-- creates a linter entry, call from within an initialization function, not
+-- directly at initial load time
+--
+-- name: name of the linter
+-- filetype: filetype to check for to use linter
+-- cmd: main linter process that is executed
+-- args: arguments to pass to the linter process
+-- use %f to refer to the current file name
+-- use %d to refer to the current directory name
+-- errorformat: how to parse the linter/compiler process output
+-- %f: file, %l: line number, %m: error/warning message
+-- os: list of OSs this linter is supported or unsupported on
+-- optional param, default: []
+-- whitelist: should the OS list be a blacklist (do not run the linter for these OSs)
+-- or a whitelist (only run the linter for these OSs)
+-- optional param, default: false (should blacklist)
+-- domatch: should the filetype be interpreted as a lua pattern to match with
+-- the actual filetype, or should the linter only activate on an exact match
+-- optional param, default: false (require exact match)
+function makeLinter(name, filetype, cmd, args, errorformat, os, whitelist, domatch)
+ if linters[name] == nil then
+ linters[name] = {}
+ linters[name].filetype = filetype
+ linters[name].cmd = cmd
+ linters[name].args = args
+ linters[name].errorformat = errorformat
+ linters[name].os = os or {}
+ linters[name].whitelist = whitelist or false
+ linters[name].domatch = domatch or false
+ end
end
-function runLinter()
- local ft = CurView().Buf:FileType()
- local file = CurView().Buf.Path
- local dir = DirectoryName(file)
- if OS == "windows" then
+function removeLinter(name)
+ linters[name] = nil
+end
+
+function init()
+ local devnull = "/dev/null"
+ if runtime.GOOS == "windows" then
devnull = "NUL"
- else
- devnull = "/dev/null"
end
- if ft == "c" then
- lint("gcc", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
- elseif ft == "c++" then
- lint("gcc", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
- elseif ft == "d" then
- lint("dmd", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", file}, "%f%(%l%):.+: %m")
- elseif ft == "go" then
- lint("gobuild", "go", {"build", "-o", devnull}, "%f:%l: %m")
- lint("golint", "golint", {file}, "%f:%l:%d+: %m")
- elseif ft == "java" then
- lint("javac", "javac", {"-d", dir, file}, "%f:%l: error: %m")
- elseif ft == "javascript" then
- lint("jshint", "jshint", {file}, "%f: line %l,.+, %m")
- elseif string.match(ft, "literate") then
- lint("literate", "lit", {"-c", file}, "%f:%l:%m")
- elseif ft == "lua" then
- lint("luacheck", "luacheck", {"--no-color", file}, "%f:%l:%d+: %m")
- elseif ft == "nim" then
- lint("nim", "nim", {"check", "--listFullPaths", "--stdout", "--hints:off", file}, "%f.%l, %d+. %m")
- elseif ft == "Objective-C" then
- lint("clang", "xcrun", {"clang", "-fsyntax-only", "-Wall", "-Wextra", file}, "%f:%l:%d+:.+: %m")
- elseif ft == "python" then
- lint("pyflakes", "pyflakes", {file}, "%f:%l:.-:? %m")
- lint("mypy", "mypy", {file}, "%f:%l: %m")
- lint("pylint", "pylint", {"--output-format=parseable", "--reports=no", file}, "%f:%l: %m")
- elseif ft == "shell" then
- lint("shfmt", "shfmt", {file}, "%f:%l:%d+: %m")
- elseif ft == "swift" and OS == "darwin" then
- lint("switfc", "xcrun", {"swiftc", file}, "%f:%l:%d+:.+: %m")
- elseif ft == "swift" and OS == "linux" then
- lint("switfc", "swiftc", {file}, "%f:%l:%d+:.+: %m")
- elseif ft == "yaml" then
- lint("yaml", "yamllint", {"--format", "parsable", file}, "%f:%l:%d+:.+ %m")
+ makeLinter("gcc", "c", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%d+:.+: %m")
+ makeLinter("gcc", "c++", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", "%f"}, "%f:%l:%d+:.+: %m")
+ makeLinter("dmd", "d", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", "%f"}, "%f%(%l%):.+: %m")
+ makeLinter("gobuild", "go", "go", {"build", "-o", devnull}, "%f:%l: %m")
+ makeLinter("golint", "go", "golint", {"%f"}, "%f:%l:%d+: %m")
+ makeLinter("javac", "java", "javac", {"-d", "%d", "%f"}, "%f:%l: error: %m")
+ makeLinter("jshint", "javascript", "jshint", {"%f"}, "%f: line %l,.+, %m")
+ makeLinter("literate", "literate", "lit", {"-c", "%f"}, "%f:%l:%m", {}, false, true)
+ makeLinter("luacheck", "lua", "luacheck", {"--no-color", "%f"}, "%f:%l:%d+: %m")
+ makeLinter("nim", "nim", "nim", {"check", "--listFullPaths", "--stdout", "--hints:off", "%f"}, "%f.%l, %d+. %m")
+ makeLinter("clang", "objective-c", "xcrun", {"clang", "-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%d+:.+: %m")
+ makeLinter("pyflakes", "python", "pyflakes", {"%f"}, "%f:%l:.-:? %m")
+ makeLinter("mypy", "python", "mypy", {"%f"}, "%f:%l: %m")
+ makeLinter("pylint", "python", "pylint", {"--output-format=parseable", "--reports=no", "%f"}, "%f:%l: %m")
+ makeLinter("shfmt", "shell", "shfmt", {"%f"}, "%f:%l:%d+: %m")
+ makeLinter("switfc", "swift", "xcrun", {"swiftc", "%f"}, "%f:%l:%d+:.+: %m", {"darwin"}, true)
+ makeLinter("switfc", "swiftc", {"%f"}, "%f:%l:%d+:.+: %m", {"linux"}, true)
+ makeLinter("yaml", "yaml", "yamllint", {"--format", "parsable", "%f"}, "%f:%l:%d+:.+ %m")
+end
+
+function contains(list, element)
+ for k, v in pairs(list) do
+ if v == element then
+ return true
+ end
end
+ return false
end
-function onSave(view)
- if GetOption("linter") then
- runLinter()
- else
- CurView():ClearAllGutterMessages()
+function runLinter(buf)
+ local ft = buf:FileType()
+ local file = buf.Path
+ local dir = filepath.Dir(file)
+
+ for k, v in pairs(linters) do
+ local ftmatch = ft == v.filetype
+ if v.domatch then
+ ftmatch = string.match(ft, v.filetype)
+ end
+
+ local hasOS = contains(v.os, runtime.GOOS)
+ if not hasOS and v.whitelist then
+ ftmatch = false
+ end
+ if hasOS and not v.whitelist then
+ ftmatch = false
+ end
+
+ for k, arg in pairs(v.args) do
+ v.args[k] = arg:gsub("%%f", file):gsub("%%d", dir)
+ end
+
+ if ftmatch then
+ lint(buf, k, v.cmd, v.args, v.errorformat)
+ end
end
end
-function lint(linter, cmd, args, errorformat)
- CurView():ClearGutterMessages(linter)
+function onSave(bp)
+ micro.Log("SAVE")
+ runLinter(bp.Buf)
+ return false
+end
+
+function lint(buf, linter, cmd, args, errorformat)
+ buf:ClearMessages("linter")
- JobSpawn(cmd, args, "", "", "linter.onExit", linter, errorformat)
+ shell.JobSpawn(cmd, args, "", "", "linter.onExit", buf, linter, errorformat)
end
-function onExit(output, linter, errorformat)
+function onExit(output, buf, linter, errorformat)
+ micro.Log("ONEXIT")
+ micro.Log(output)
local lines = split(output, "\n")
local regex = errorformat:gsub("%%f", "(..-)"):gsub("%%l", "(%d+)"):gsub("%%m", "(.+)")
line = line:match("^%s*(.+)%s*$")
if string.find(line, regex) then
local file, line, msg = string.match(line, regex)
- if basename(CurView().Buf.Path) == basename(file) then
- CurView():GutterMessage(linter, tonumber(line), msg, 2)
+ if basename(buf.Path) == basename(file) then
+ local bmsg = buffer.NewMessageAtLine("linter", msg, tonumber(line), buffer.MTError)
+ buf:AddMessage(bmsg)
end
end
end
function basename(file)
local sep = "/"
- if OS == "windows" then
+ if runtime.GOOS == "windows" then
sep = "\\"
end
local name = string.gsub(file, "(.*" .. sep .. ")(.*)", "%2")