Initial commit

This commit is contained in:
2024-10-02 14:54:32 +02:00
commit 14af275c5c
27 changed files with 1871 additions and 0 deletions

7
.stylua.toml Normal file
View File

@@ -0,0 +1,7 @@
column_width = 110
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferDouble"
call_parentheses = "None"
collapse_simple_statement = "Always"

75
lua/cave/config.lua Normal file
View File

@@ -0,0 +1,75 @@
local Meta = require "cave.meta"
local Python = require "cave.python"
local Context = require "cave.context"
local Map = Meta.Map
local Optional = Meta.Optional
local Str = Meta.String
local validate = Meta.validate
local Table = Meta.Table
---@class cave.Config
---@field python_interpreters table<string,cave.Python.Interpreter>
local Config = Meta.derive "Config"
---@return overseer.TemplateDefinition[]
function Config:templates()
local templates = {}
for _, python_interpreter in pairs(self.python_interpreters) do
vim.list_extend(templates, python_interpreter:templates())
end
return templates
end
---@class cave.Config.Factory : cave.Context
---@field python_interpreters_ cave.Python.Interpreter.Factory[]
---@field context_ cave.Context
local Factory = Meta.derive("Config.Factory", Context)
function Factory:init(context)
Context.init(self, context.dir, context.name, context.uuid)
self.python_interpreters_ = {}
self.context_ = context
end
---@param context cave.Context
---@return cave.Config.Factory
function Factory.new(context)
validate { context = { context, Context } }
local factory = setmetatable({}, Factory)
factory:init(context)
return factory
end
---@return cave.Python.Interpreter.Factory[]
function Factory:get_python_interpreters() return self.python_interpreters_ end
---@return cave.Python.Interpreter.Factory
function Factory:python()
local python_interpreter = Python.Interpreter.Factory.new(self.context_)
table.insert(self.python_interpreters_, python_interpreter)
return python_interpreter
end
---@param config cave.Config
function Factory:init_config(config)
config.python_interpreters = {}
for _, python_interpreter_factory in pairs(self:get_python_interpreters()) do
local python_interpreter = python_interpreter_factory:build()
assert(config.python_interpreters[python_interpreter.id] == nil)
config.python_interpreters[python_interpreter.id] = python_interpreter
end
end
---@return cave.Config
function Factory:build()
local config = setmetatable({}, Config)
self:init_config(config)
return config
end
---@alias cave.Config.Builder fun(config: cave.Config.Factory)
Config.Factory = Factory
return Config

30
lua/cave/context.lua Normal file
View File

@@ -0,0 +1,30 @@
local Meta = require "cave.meta"
local Path = require "cave.path"
local Str = Meta.String
local validate = Meta.validate
---@class cave.Context
---@field dir cave.Path
---@field name string
---@field uuid string
local Context = Meta.derive "Context"
---@param dir cave.Path
---@param name string
---@param uuid string
function Context:init(dir, name, uuid)
validate { dir = { dir, Path }, uuid = { uuid, Str }, name = { name, Str } }
self.dir = dir
self.name = name
self.uuid = uuid
end
---@return string
function Context:tostring()
return ("Context(dir=%q, name=%q, uuid=%q)"):format(self.dir, self.name, self.uuid)
end
Context.__tostring = Context.tostring
return Context

23
lua/cave/enum.lua Normal file
View File

@@ -0,0 +1,23 @@
local Meta = require "cave.meta"
local validate = Meta.validate
local Map = Meta.Map
local Str = Meta.String
---@class cave.Enum
local Enum = {}
Enum.__index = Enum
---@param tbl table<string, string>
---@param name string
function Enum.new(tbl, name)
validate {
tbl = { tbl, Map(Str, Str) },
name = { name, Str },
}
for key, value in pairs(tbl) do
assert(key == value)
end
setmetatable(tbl, { __meta = Meta.Enum(tbl, name) })
end
return Enum

62
lua/cave/env.lua Normal file
View File

@@ -0,0 +1,62 @@
local Meta = require "cave.meta"
local Util = require "cave.util"
local Context = require "cave.context"
local ClassLogger = require "cave.log.class_logger"
local validate = Meta.validate
local Optional = Meta.Optional
---@class cave.Env
---@field vars table<string, string>
---@field on_update_cbs function[]
local Env = Meta.derive "Env"
Env.log = ClassLogger.new(Env)
---@param context cave.Context?
---@return cave.Env
function Env:new(context)
local env = setmetatable({}, Env)
env.vars = {}
local dir, name, uuid
if context then
dir, name, uuid = tostring(context.dir), context.name, context.uuid
else
dir, name, uuid = nil, nil, nil
end
vim.fn.setenv("NVIM_PROJECT_DIR", dir)
vim.fn.setenv("NVIM_PROJECT_NAME", name)
vim.fn.setenv("NVIM_PROJECT_UUID", uuid)
for _, entry in ipairs(vim.fn.systemlist { "nvim_env" }) do
local split_idx = entry:find "="
assert(split_idx ~= nil)
env.vars[entry:sub(1, split_idx - 1)] = entry:sub(split_idx + 1, -1)
end
return env
end
---@param context cave.Context?
function Env:update(context)
validate { context = { context, Optional(Context) } }
local log = Env.log:call("%s", context)
local new_env = Env:new(context)
local env_var_diff = Util.tbl_diff(self.vars, new_env.vars)
for name, value in pairs(env_var_diff.added) do
vim.fn.setenv(name, value)
end
for name, values in pairs(env_var_diff.modified) do
vim.fn.setenv(name, values.new)
end
for name, _ in pairs(env_var_diff.removed) do
vim.fn.setenv(name, nil)
end
self.vars = new_env.vars
-- vim.g.python3_host_prog = get_python3_host_prog()
log:ok("environment variables changed:\n%s", vim.inspect(env_var_diff))
end
return Env

51
lua/cave/init.lua Normal file
View File

@@ -0,0 +1,51 @@
---@class cave
local cave = {}
cave.Path = require "cave.path"
cave.Util = require "cave.util"
cave.manager = require("cave.manager").new()
function cave.load_project_from_cwd()
local manager = cave.manager
local workspaces = require "workspaces"
local Path = cave.Path
local cwd = Path.cwd()
for project_name, project in pairs(manager:get_projects()) do
if project.dir:samefile(cwd) then workspaces.open(project_name) end
end
end
function cave.setup()
vim.api.nvim_create_user_command(
"ProjectAdd",
function(cmd_opts) cave.manager:add_project(unpack(cmd_opts.fargs)) end,
{
desc = "Add a project (dir?, name?)",
nargs = "*",
complete = "file",
}
)
vim.api.nvim_create_user_command(
"ProjectRemove",
function(cmd_opts) cave.manager:remove_project(unpack(cmd_opts.fargs)) end,
{
desc = "Remove a project (name?)",
nargs = 1,
complete = function(lead) return cave.manager:project_name_complete(lead) end,
}
)
vim.api.nvim_create_user_command(
"ProjectRename",
function(cmd_opts) cave.manager:rename_project(unpack(cmd_opts.fargs)) end,
{
desc = "Rename a project. (old_name, new_name)",
nargs = "+",
complete = function(lead) return cave.manager:project_name_complete(lead) end,
}
)
vim.schedule(cave.load_project_from_cwd)
end
return cave

View File

@@ -0,0 +1,46 @@
local Logger = require "cave.log.logger"
local FunctionLogger = require "cave.log.function_logger"
local Meta = require "cave.meta"
local validate = Meta.validate
local Table = Meta.Table
---@class cave.ClassLogger : cave.Logger
local ClassLogger = Meta.derive("ClassLogger", Logger)
---@param cls_name string
function ClassLogger:init(cls_name)
local prefix = cls_name
Logger.init(self, prefix)
end
---@param cls_mt table
---@return cave.ClassLogger
function ClassLogger.new(cls_mt)
validate { cls_mt = { cls_mt, Table } }
local cls_name = Meta.like(cls_mt).repr
local logger = setmetatable({}, ClassLogger)
logger:init(cls_name)
return logger
end
---@param args_fmt string?
---@param ... any
---@return cave.FunctionLogger
function ClassLogger:call(args_fmt, ...)
local dbg_info
local fn_name
for l = 2, 1, -1 do
dbg_info = debug.getinfo(l, "n")
fn_name = dbg_info.name
if fn_name then break end
end
if dbg_info.namewhat == "method" then
fn_name = self.prefix .. ":" .. fn_name
elseif dbg_info.namewhat == "field" then
fn_name = self.prefix .. "." .. fn_name
end
return FunctionLogger.new(fn_name, args_fmt, ...):call()
end
return ClassLogger

View File

@@ -0,0 +1,65 @@
local Log = require "cave.log"
local Meta = require "cave.meta"
local Str = Meta.String
local validate = Meta.validate
local Optional = Meta.Optional
---@class cave.FunctionLogger
---@field fn_repr string
local FunctionLogger = Meta.derive "FuncLogger"
---@param fn_name string
---@param args_fmt string?
---@param ... any
function FunctionLogger:init(fn_name, args_fmt, ...)
local args_repr = (args_fmt and args_fmt:format(...)) or ""
self.fn_repr = ("%s(%s)"):format(fn_name, args_repr)
end
---@param fn_name string
---@param args_fmt string?
---@param ... any
---@return cave.FunctionLogger
function FunctionLogger.new(fn_name, args_fmt, ...)
validate { fn_name = { fn_name, Str }, args_fmt = { args_fmt, Optional(Str) } }
local logger = setmetatable({}, FunctionLogger)
logger:init(fn_name, args_fmt, ...)
return logger
end
---@return cave.FunctionLogger
function FunctionLogger:call()
Log.dbg("> %s", self.fn_repr)
return self
end
---@param err_fmt string?
---@param ... any
function FunctionLogger:err(err_fmt, ...)
validate { err = { err_fmt, Optional(Str) } }
local res_msg = "err"
if err_fmt ~= nil then res_msg = res_msg .. " " .. err_fmt:format(...) end
Log.err(("< %s - %s"):format(self.fn_repr, res_msg))
end
--
---@param warn_fmt string?
---@param ... any
function FunctionLogger:warn(warn_fmt, ...)
validate { err = { warn_fmt, Optional(Str) } }
local res_msg = "warn"
if warn_fmt ~= nil then res_msg = res_msg .. " " .. warn_fmt:format(...) end
Log.err(("~ %s - %s"):format(self.fn_repr, res_msg))
end
---@param res_fmt string?
---@param ... any
function FunctionLogger:ok(res_fmt, ...)
validate { results = { res_fmt, Optional(Str) } }
local res_msg = "ok"
if res_fmt ~= nil then res_msg = res_msg .. " " .. res_fmt:format(...) end
Log.dbg(("< %s - %s"):format(self.fn_repr, res_msg))
end
return FunctionLogger

25
lua/cave/log/init.lua Normal file
View File

@@ -0,0 +1,25 @@
---@class cave.Log
local Log = {}
---@param msg string
---@param level integer
---@param ...? any
local function notify(msg, level, ...) vim.notify(msg:format(...), level, { title = "cave.nvim" }) end
---@param msg string
---@param ...? any
function Log.dbg(msg, ...) notify(msg, vim.log.levels.DEBUG, ...) end
---@param msg string
---@param ...? any
function Log.err(msg, ...) notify(msg, vim.log.levels.ERROR, ...) end
---@param msg string
---@param ...? any
function Log.inf(msg, ...) notify(msg, vim.log.levels.INFO, ...) end
---@param msg string
---@param ...? any
function Log.warn(msg, ...) notify(msg, vim.log.levels.WARN, ...) end
return Log

39
lua/cave/log/logger.lua Normal file
View File

@@ -0,0 +1,39 @@
local Log = require "cave.log"
local Meta = require "cave.meta"
local validate = Meta.validate
local Str = Meta.String
---@class cave.Logger
---@field prefix string
local Logger = Meta.derive "Logger"
---@param prefix string
function Logger:init(prefix) self.prefix = prefix end
---@param prefix string
---@return cave.Logger
function Logger.new(prefix)
validate { prefix = { prefix, Str } }
local logger = setmetatable({}, Logger)
logger:init(prefix)
return logger
end
---@param msg string
---@param ...? any
function Logger:dbg(msg, ...) Log.dbg(self.prefix .. msg, ...) end
---@param msg string
---@param ...? any
function Logger:err(msg, ...) Log.err(self.prefix .. msg, ...) end
---@param msg string
---@param ...? any
function Logger:inf(msg, ...) Log.inf(self.prefix .. msg, ...) end
---@param msg string
---@param ...? any
function Logger:warn(msg, ...) Log.warn(self.prefix .. msg, ...) end
return Logger

188
lua/cave/manager.lua Normal file
View File

@@ -0,0 +1,188 @@
local Env = require "cave.env"
local Meta = require "cave.meta"
local Path = require "cave.path"
local Project = require "cave.project"
local Util = require "cave.util"
local ClassLogger = require "cave.log.class_logger"
local TemplateProvider = require "cave.template_provider"
local workspaces = require "workspaces"
local overseer = require "overseer"
local Str = Meta.String
local validate = Meta.validate
local Optional = Meta.Optional
---@class cave.Manager
---@field project? cave.Project
---@field env cave.Env
---@field template_provider cave.TemplateProvider
local Manager = Meta.derive "Manager"
Manager.log = ClassLogger.new(Manager)
function Manager:init()
self.env = Env:new()
---@return overseer.TemplateDefinition[]
local function project_templates() return self.project and self.project.templates or {} end
self.template_provider = TemplateProvider.new("cave.nvim", project_templates)
overseer.register_template(self.template_provider)
---@param task_defn overseer.TaskDefinition
---@param _ overseer.TaskUtil
local function on_template_run(task_defn, _)
local name = task_defn.name
if name == nil or self.project == nil then return end
self.project:on_template_run(name)
end
overseer.add_template_hook(nil, on_template_run)
end
---@return cave.Manager
function Manager.new()
local manager = setmetatable({}, Manager)
manager:init()
return manager
end
---@return table<string, cave.Project>
function Manager:get_projects()
local projects = {}
if self.project ~= nil then projects[self.project.name] = self.project end
for _, ws in pairs(workspaces.get()) do
local project = Project.new(Path.new(ws.path), ws.name, ws.custom)
if projects[project.name] == nil then projects[project.name] = project end
end
return projects
end
---@param lead string
---@return string[]
function Manager:project_name_complete(lead)
validate { lead = { lead, Str } }
local projects = self:get_projects()
local project_names = {}
for project_name, _ in pairs(projects) do
if vim.startswith(project_name, lead) then table.insert(project_names, project_name) end
end
return project_names
end
---@param lead string
---@return string[]
function Manager:project_dir_complete(lead)
validate { lead = { lead, Str } }
local projects = self:get_projects()
local project_dirs = {}
for _, project in pairs(projects) do
local project_dir = tostring(project.dir)
if vim.startswith(project_dir, lead) then table.insert(project_dirs, project_dir) end
end
return project_dirs
end
---@param dir string?
---@param name string?
function Manager:add_project(dir, name)
validate { dir = { dir, Optional(Str) }, name = { name, Optional(Str) } }
local log = Manager.log:call("dir=%q, name=%s", dir, name and ("%q"):format(name) or "nil")
local project_dir = Path.new(dir or Path.cwd())
assert(project_dir:is_absolute())
local project_name = name or project_dir:basename()
local project_uuid = Util.generate_uuid()
if not project_dir:is_dir() then return log:err("%q is not a directory", project_dir) end
local projects = self:get_projects()
local project_exists = projects[project_name] ~= nil
if project_exists then return log:err("project with name %q already exists", project_name) end
local project = Project.new(project_dir, project_name, project_uuid)
workspaces.add(tostring(project.dir), project.name)
workspaces.set_custom(project.name, project.uuid)
log:ok()
end
---@param name string
function Manager:remove_project(name)
validate { name = { name, Str } }
local log = Manager.log:call("name=%q", name)
local projects = self:get_projects()
local project = projects[name]
if project == nil then return log:err "projectdoesn't exist" end
if project == self.project then self:close_project() end
workspaces.remove(name)
log:ok()
end
---@param old_name string
---@param new_name string
function Manager:rename_project(old_name, new_name)
validate { new_name = { new_name, Str }, old_name = { old_name, Str } }
local log = Manager.log:call("old_name=%q, new_name=%q", old_name, new_name)
if new_name == old_name then return log:ok() end
local projects = self:get_projects()
local project = projects[old_name]
if project == nil then return log:err "project with old name doesn't exist" end
if projects[new_name] ~= nil then return log:err "project with new name already exists" end
project:rename(new_name)
if project == self.project then self.env:update() end
workspaces.rename(old_name, new_name)
log:ok()
end
---@param name string
function Manager:open_project(name)
validate { name = { name, Str } }
local log = Manager.log:call("name=%q", name)
if self.project ~= nil and self.project.name == name then return log:ok() end
local projects = self:get_projects()
local project = projects[name]
if project == nil then return log:err "project doesn't exist" end
self:close_project()
self.project = project
self.env:update(self.project)
project:open()
log:ok()
end
function Manager:open_project_config()
local log = Manager.log:call()
if self.project == nil then return log:ok() end
vim.cmd.edit(tostring(self.project.config_script))
log:ok()
end
function Manager:close_project()
local log = Manager.log:call()
if self.project == nil then return log:ok() end
self.project:close()
self.project = nil
self.env:update()
log:ok()
end
return Manager

238
lua/cave/meta.lua Normal file
View File

@@ -0,0 +1,238 @@
---@alias cave.Validator fun(v: any) : boolean
---@class cave.Meta
---@field valid cave.Validator
---@field repr string
local Meta = {}
Meta.__index = Meta
---@alias cave.MetaLikeItem string|table|cave.Meta
---@alias cave.MetaLike cave.MetaLikeItem|cave.MetaLikeItem[]
---@param repr string
---@param valid cave.Validator
---@return cave.Meta
function Meta.new(repr, valid)
vim.validate {
repr = { repr, "string" },
valid = { valid, "function" },
}
local meta = setmetatable({}, Meta)
meta.valid = valid
meta.repr = repr
return meta
end
---@param ... cave.MetaLikeItem
---@return cave.Meta
function Meta.Union(...)
local value_valids = {}
local value_reprs = {}
for idx, value_meta_like in ipairs { ... } do
local value_meta = Meta.like(value_meta_like)
value_valids[idx] = value_meta.valid
value_reprs[idx] = value_meta.repr
end
local repr = ("Union<%s>"):format(table.concat(value_reprs, ","))
local function valid(v)
for _, value_valid in pairs(value_valids) do
if value_valid(v) then return true end
end
return false
end
return Meta.new(repr, valid)
end
---@param value_meta_like cave.MetaLike
---@return cave.Meta
function Meta.Optional(value_meta_like)
local value_meta = Meta.like(value_meta_like)
local repr = ("Optional<%s>"):format(value_meta.repr)
local function valid(v) return v == nil or value_meta.valid(v) end
return Meta.new(repr, valid)
end
---@param mt table
---@param name string
---@param valid cave.Validator?
---@return cave.Meta
function Meta.Class(mt, name, valid)
assert(type(mt) == "table")
local repr = name
---@param v any
---@return boolean
local function default_valid(v)
if type(v) ~= "table" then return false end
local v_mt = getmetatable(v)
while v_mt ~= nil do
if v_mt == mt then return true end
v_mt = getmetatable(v_mt)
end
return false
end
valid = valid or default_valid
return Meta.new(repr, valid)
end
---@param tbl table<string,string>
---@param name string
---@return cave.Meta
function Meta.Enum(tbl, name)
assert(type(tbl) == "table")
local repr = ("Enum<%s>(%s)"):format(name, table.concat(vim.tbl_values(tbl), "|"))
local function valid(v) return type(v) == "string" and tbl[v] == v end
return Meta.new(repr, valid)
end
---@param value_meta_like cave.MetaLike
---@return cave.Meta
function Meta.List(value_meta_like)
local value_meta = Meta.like(value_meta_like)
local repr = ("List<%s>"):format(value_meta.repr)
local function valid(vs)
if not vim.tbl_islist(vs) then return false end
for _, v in ipairs(vs) do
if not value_meta.valid(v) then return false end
end
return true
end
return Meta.new(repr, valid)
end
---@param key_meta_like cave.MetaLike
---@param value_meta_like cave.MetaLike
---@return cave.Meta
function Meta.Map(key_meta_like, value_meta_like)
local key_meta = Meta.like(key_meta_like)
local value_meta = Meta.like(value_meta_like)
local repr = ("Map<%s,%s>"):format(key_meta.repr, value_meta.repr)
local function valid(vs)
if type(vs) ~= "table" then return false end
for k, v in ipairs(vs) do
if not key_meta.valid(k) or value_meta.valid(v) then return false end
end
return true
end
return Meta.new(repr, valid)
end
---@param meta_like_tbl table<string, cave.MetaLike>
---@param name string
---@return cave.Meta
function Meta.MapLiteral(meta_like_tbl, name)
---@type table<string, cave.Meta>
local metas = {}
for key, meta_like in meta_like_tbl do
metas[key] = Meta.like(meta_like)
end
---@param vs any
---@return boolean
local function valid(vs)
if type(vs) ~= "table" then return false end
for key, meta in pairs(metas) do
local value = vs[key]
if not meta.valid(value) then return false end
end
for key, _ in pairs(vs) do
if metas[key] == nil then return false end
end
return true
end
return Meta.new(name, valid)
end
Meta.Bool = Meta.new("boolean", function(v) return type(v) == "boolean" end)
Meta.Function = Meta.new("function", function(v) return type(v) == "function" end)
Meta.Number = Meta.new("number", function(v) return type(v) == "number" end)
Meta.String = Meta.new("string", function(v) return type(v) == "string" end)
Meta.Table = Meta.new("table", function(v) return type(v) == "table" end)
Meta.Nil = Meta.new("nil", function(v) return v == nil end)
local meta_aliases = {
["b"] = Meta.Bool,
["bool"] = Meta.Bool,
["boolean"] = Meta.Bool,
["f"] = Meta.Function,
["fn"] = Meta.Function,
["function"] = Meta.Function,
["n"] = Meta.Number,
["num"] = Meta.Number,
["number"] = Meta.Number,
["nil"] = Meta.Nil,
["s"] = Meta.String,
["str"] = Meta.String,
["string"] = Meta.String,
["t"] = Meta.Table,
["tbl"] = Meta.Table,
["table"] = Meta.Table,
}
---@param meta_like cave.MetaLike
---@return cave.Meta
function Meta.like(meta_like)
local meta_like_type = type(meta_like)
local meta
if meta_like_type == "string" then
meta = meta_aliases[meta_like]
else
assert(meta_like_type == "table")
local mt = getmetatable(meta_like)
if mt == Meta then
meta = meta_like
elseif meta_like.__meta ~= nil then
meta = meta_like.__meta
elseif vim.tbl_islist(meta_like) then
meta = Meta.Union(unpack(meta_like))
else
assert(false)
end
end
assert(getmetatable(meta) == Meta)
return meta
end
---@return cave.Validator
---@return string
function Meta:check() return self.valid, self.repr end
---@param params table<string,table>
function Meta.validate(params)
local vim_params = {}
for param_name, param_specs in pairs(params) do
assert(type(param_name) == "string", "param name is not a string")
assert(type(param_specs) == "table", "param spec for '" .. param_name .. "' is not a table")
assert(#param_specs == 2, "param spec length for '" .. param_name .. "' is not 2")
local param_value = param_specs[1] --[[@as any]]
local param_meta_like = param_specs[2] --[[@as cave.MetaLike]]
local param_meta = Meta.like(param_meta_like)
vim_params[param_name] = { param_value, param_meta:check() }
end
vim.validate(vim_params)
end
---@param name string
---@param base_mt table?
---@return table
function Meta.derive(name, base_mt)
Meta.validate { name = { name, Meta.String }, base_mt = { base_mt, Meta.Optional(Meta.Table) } }
local mt = {}
mt.__index = mt
mt.__meta = Meta.Class(mt, name)
return (base_mt and setmetatable(mt, base_mt)) or mt
end
---@param obj table
---@return string
function Meta.get_repr(obj)
Meta.validate { obj = { obj, Meta.Table } }
local mt = getmetatable(obj)
Meta.validate { mt = { mt, Meta.Table } }
local meta = mt.__meta
Meta.validate { meta = { meta, Meta.Table } }
local meta_mt = getmetatable(obj)
assert(meta_mt == Meta)
---@cast meta cave.Meta
return meta.repr
end
return Meta

13
lua/cave/option.lua Normal file
View File

@@ -0,0 +1,13 @@
---@class cave.Option
local Option
---@generic ValueType, ResultType
---@param opt ValueType?
---@param fn fun(opt: ValueType): ResultType
---@return ResultType?
function Option.map(opt, fn)
if opt == nil then return nil end
return fn(opt)
end
return Option

92
lua/cave/path.lua Normal file
View File

@@ -0,0 +1,92 @@
local Meta = require "cave.meta"
local PosixPath = require "pathlib.posix" --[[@as PathlibPosixPath]]
local List = Meta.List
local Str = Meta.String
local validate = Meta.validate
---@alias cave.PathLikeItem string|cave.Path
---@alias cave.PathLike cave.PathLikeItem|cave.PathLikeItem[]
---@class cave.Path : PathlibPosixPath
---@operator div(cave.PathLikeItem): cave.Path
---@operator concat(cave.PathLikeItem): string
local Path = Meta.derive("Path", PosixPath)
require("pathlib.utils.paths").link_dunders(Path, PosixPath)
Path.LikeItem = { Str, Path }
Path.LikeItemList = List { Str, Path }
Path.Like = { Str, Path, Path.LikeItemList }
---@param path_like cave.PathLike
---@return cave.Path
function Path.like(path_like)
validate { path_like = { path_like, Path.Like } }
local self = Path.new_empty()
local path_like_type = type(path_like)
if path_like_type == "string" or not vim.tbl_islist(path_like) then
self:_init(path_like)
else
self:_init(unpack(path_like))
end
return self
end
---@param path cave.Path
---@return cave.Path
function Path:copy_all_from(path)
---@type cave.Path
return PosixPath.copy_all_from(self, path)
end
---@return cave.Path
function Path:copy() return Path.new_empty():copy_all_from(self) end
---@param ... cave.PathLikeItem
---@return cave.Path
function Path.new(...)
validate { ["..."] = { { ... }, Path.LikeItemList } }
local self = Path.new_empty()
self:_init(...)
return self
end
---@return cave.Path
function Path.new_empty()
local self = setmetatable({}, Path)
self:to_empty()
return self
end
---@return cave.Path
function Path.cwd() return Path.new(vim.fn.getcwd()) end
---@return cave.Path
function Path.home() return Path.new(vim.loop.os_homedir()) end
---@param what string
---@param ... cave.PathLikeItem
---@return cave.Path
function Path.stdpath(what, ...)
validate { what = { what, Str }, ["..."] = { { ... }, Path.LikeItemList } }
return Path.new(vim.fn.stdpath(what), ...)
end
-- ---@return cave.Path
-- function Path:to_absolute(cwd) return PosixPath.to_absolute(self, cwd) end
---@param name cave.PathLikeItem
---@return cave.Path
function Path.executable(name)
validate { name = { name, { Str, Path } } }
if type(name) ~= "string" then name = tostring(name) end
local s = vim.fn.exepath(name)
assert(#s > 0, ("'%s' is not executable"):format(name))
return Path.new(s)
end
---@return boolean
function Path:is_executable() return vim.fn.executable(self:tostring()) == 1 end
return Path

235
lua/cave/project.lua Normal file
View File

@@ -0,0 +1,235 @@
local Path = require "cave.path"
local Util = require "cave.util"
local Meta = require "cave.meta"
local Context = require "cave.context"
local Config = require "cave.config"
local Log = require "cave.log"
local ClassLogger = require "cave.log.class_logger"
local resession = require "resession"
local validate = Meta.validate
local Str = Meta.String
local CONFIG_SCRIPT_TEMPLATE = [[
---@type cave.Config.Builder
local function configure(config)
end
return configure
]]
---@class cave.Project : cave.Context
---@field config_dir cave.Path
---@field config_script cave.Path
---@field config_dir_watch number
---@field config cave.Config?
---@field templates overseer.TemplateDefinition[]
---@field template_order table<string,number>
local Project = Meta.derive("Project", Context)
Project.log = ClassLogger.new(Project)
---@param dir cave.Path
---@param name string
---@param uuid string
function Project:init(dir, name, uuid)
Context.init(self, dir, name, uuid)
self.config_dir = Path.stdpath("data", "cave", "project", uuid):to_absolute() --[[@as cave.Path]]
self.config_script = self.config_dir / "config.lua"
self.templates = {}
self.template_order = {}
end
---@param dir cave.Path
---@param name string
---@param uuid string
---@return cave.Project
function Project.new(dir, name, uuid)
validate { dir = { dir, Path }, name = { name, Str }, uuid = { uuid, Str } }
local project = setmetatable({}, Project)
project:init(dir, name, uuid)
return project
end
---@return boolean
function Project:session_exists()
for _, session in pairs(resession.list()) do
if session == self.uuid then return true end
end
return false
end
---@return boolean
function Project:session_active() return resession.get_current() == self.uuid end
function Project:load_session()
local log = Project.log:call()
if self:session_exists() then
resession.load(self.uuid, { silence_errors = true, notify = false })
else
Util.close_all_buffers()
self:save_session()
end
if not self:session_active() then log:warn "project session activation failed" end
log:ok()
end
function Project:save_session()
local log = Project.log:call()
resession.save(self.uuid, { attach = true, notify = false })
if not self:session_exists() then return log:err "session wasn't created" end
log:ok()
end
function Project:delete_session()
local log = Project.log:call()
if not self:session_exists() then return log:ok() end
resession.delete(self.uuid)
if self:session_exists() then log:err "session wasn't deleted" end
log:ok()
end
function Project:init_config_dir()
local log = Project.log:call()
if not self.config_dir:is_dir() and not self.config_dir:mkdir(Path.const.o755, true) then
return log:err("config dir (%q) creation failed\n%s", self.config_dir, self.config_dir.error_msg)
end
if not self.config_script:exists() and not self.config_script:io_write(CONFIG_SCRIPT_TEMPLATE) then
return log:err("config file (%q) creation failed\n%s", self.config_script, self.config_script.error_msg)
end
log:ok()
end
function Project:load_config()
local log = Project.log:call()
self.config = nil
if not self.config_script:is_file() then
return log:err("config script (%q) missing", self.config_script)
end
local chunk, err = loadfile(self.config_script:tostring())
if chunk == nil then return log:err("config script loading failed\n%s", err) end
local chunk_ok, chunk_res = pcall(chunk)
if not chunk_ok then return log:err("config script failed\n%s", chunk_res) end
local res_type = type(chunk_res)
if res_type ~= "function" then return log:err("config script returned %s instead of function", res_type) end
local config_factory = Config.Factory.new(self)
local configure_ok, configure_res = pcall(chunk_res --[[@as cave.Config.Builder]], config_factory)
if not configure_ok then return log:err("configuring failed\n%s", configure_res) end
local build_config_ok, build_config_res = pcall(Config.Factory.build, config_factory)
if not build_config_ok then return log:err("building config failed\n%s", configure_res) end
self.config = build_config_res
log:ok()
end
function Project:load_templates()
local log = Project.log:call()
self.templates = self.config and self.config:templates() or {}
local task_order = {}
for _, template in ipairs(self.templates) do
local priority = self.template_order[template.name]
if priority ~= nil then
template.priority = priority
task_order[template.name] = priority
end
end
self.template_order = task_order
log:ok()
end
---@param name string
function Project:on_template_run(name)
validate { name = { name, Str } }
local log = Project.log:call()
local priority = -os.clock()
for _, template in ipairs(self.templates) do
if template.name == name then
template.priority = priority
self.template_order[name] = priority
break
end
end
log:ok()
end
function Project:watch_config_dir()
local log = Project.log:call()
if self.config_dir_watch == nil then
self.config_dir_watch = vim.api.nvim_create_autocmd("BufWritePost", {
callback = vim.schedule_wrap(function(event)
if self.config_dir_watch == nil then return end
if Path.new(event.match):to_absolute():is_relative_to(self.config_dir) then self:reload() end
end),
})
end
log:ok()
end
function Project:unwatch_config_dir()
local log = Project.log:call()
if self.config_dir_watch then
vim.api.nvim_del_autocmd(self.config_dir_watch)
self.config_dir_watch = nil
end
log:ok()
end
function Project:reload()
self:load_config()
self:load_templates()
end
function Project:open()
local log = Project.log:call()
self:load_session()
self:init_config_dir()
self:reload()
self:watch_config_dir()
log:ok()
end
function Project:close()
local log = Project.log:call()
self:unwatch_config_dir()
Util.save_all_buffers()
self:save_session()
log:ok()
end
---@param new_name string
function Project:rename(new_name)
validate { new_name = { new_name, Str } }
local log = Project.log:call("%q", new_name)
if self.name == new_name then return log:ok() end
self.name = new_name
if self.config ~= nil then self:reload() end
log:ok()
end
return Project

9
lua/cave/python/init.lua Normal file
View File

@@ -0,0 +1,9 @@
---@class cave.Python
local Python = {
Interpreter = require "cave.python.interpreter",
Module = require "cave.python.module",
Runnable = require "cave.python.runnable",
Script = require "cave.python.script",
}
return Python

View File

@@ -0,0 +1,236 @@
local Context = require "cave.context"
local Meta = require "cave.meta"
local Module = require "cave.python.module"
local Path = require "cave.path"
local Runnable = require "cave.python.runnable"
local Script = require "cave.python.script"
local Task = require "cave.task"
local List = Meta.List
local Map = Meta.Map
local Str = Meta.String
local validate = Meta.validate
---@class cave.Python.Interpreter
---@field id string
---@field path cave.Path
---@field args string[]
---@field env table<string, string>
---@field runnables table<string, cave.Python.Runnable>
---@field context cave.Context
local Interpreter = Meta.derive "Python.Interpreter"
---@return string
function Interpreter:valid_path() return self.path:executable():tostring() end
---@return overseer.TemplateDefinition[]
function Interpreter:templates()
local templates = {}
for _, runnable in pairs(self.runnables) do
vim.list_extend(templates, { self:run_template(runnable), self:debug_template(runnable) })
end
return templates
end
---@param runnable cave.Python.Runnable
---@return overseer.TemplateDefinition
function Interpreter:run_template(runnable)
local name = ("python.%s.run.%s"):format(self.id, runnable.id)
local script, module = runnable:concrete()
local args = vim.list_extend({}, self.args)
vim.list_extend(
args,
(script and { script:valid_file() }) or (module and { "-m", module.name }) or error "Unsupported runnable"
)
vim.list_extend(args, runnable.args)
local template_definition = {
name = name,
builder = function()
---@type overseer.TaskDefinition
local task_definition = {
name = name,
cmd = { self:valid_path() },
args = args,
env = vim.tbl_extend("keep", runnable.env, self.env),
cwd = runnable:valid_cwd(),
components = { { "defaults" } },
}
return task_definition
end,
params = {},
tags = { Task.Tag.Run },
}
return template_definition
end
---@param runnable cave.Python.Runnable
---@return overseer.TemplateDefinition
function Interpreter:debug_template(runnable)
local name = ("python.%s.debug.%s"):format(self.id, runnable.id)
local template_definition = {}
template_definition.builder = function()
local script, module = runnable:concrete()
---@type DebugpyLaunchConfig
local dap_config = {
name = name,
type = "python",
request = "launch",
module = module and module.name,
program = script and script:valid_file(),
cwd = runnable:valid_cwd(),
args = runnable.args,
env = vim.tbl_extend("keep", runnable.env, self.env),
python = vim.list_extend({ self:valid_path() }, self.args),
}
if vim.tbl_isempty(dap_config.env) then dap_config.env = nil end
---@type overseer.TaskDefinition
local task_definition = {
name = name,
cmd = {},
components = { { "defaults" }, { "dap", config = dap_config } },
}
return task_definition
end
template_definition.params = {}
template_definition.tags = { Task.Tag.Debug }
template_definition.name = name
return template_definition
end
---@class cave.Python.Interpreter.Factory
---@field id_ string?
---@field path_ cave.Path?
---@field args_ string[]
---@field env_ table<string, string>
---@field runnables_ cave.Python.Runnable.Factory[]
---@field context_ cave.Context
local Factory = Meta.derive "Python.Interpreter.Factory"
---@param context cave.Context
function Factory:init(context)
self.args_ = {}
self.env_ = {}
self.runnables_ = {}
self.context_ = context
end
---@param context cave.Context
---@return cave.Python.Interpreter.Factory
function Factory.new(context)
validate { context = { context, Context } }
local factory = setmetatable({}, Factory)
factory:init(context)
return factory
end
---@return string
function Factory:get_id() return self.id_ or "python" end
---@return cave.Path
function Factory:get_path() return self.path_ or Path.new "python" end
---@return string[]
function Factory:get_args() return self.args_ end
---@return table<string, string>
function Factory:get_env() return self.env_ end
---@return cave.Python.Runnable.Factory[]
function Factory:get_runnables() return self.runnables_ end
---@param id string
---@return cave.Python.Interpreter.Factory
function Factory:id(id)
validate { id = { id, Str } }
self.id_ = id
return self
end
---@param path_like cave.PathLike
---@return cave.Python.Interpreter.Factory
function Factory:path(path_like)
validate { path_like = { path_like, Path.Like } }
self.path_ = Path.like(path_like)
return self
end
---@param args string[]
---@return cave.Python.Interpreter.Factory
function Factory:args(args)
validate { args = { args, List(Str) } }
vim.list_extend(self.args_, args)
return self
end
---@param arg string
---@return cave.Python.Interpreter.Factory
function Factory:arg(arg) return self:args { arg } end
---@param env table<string, string>
---@return cave.Python.Interpreter.Factory
function Factory:env(env)
validate { env = { env, Map(Str, Str) } }
vim.tbl_extend("error", self.env_, env)
return self
end
---@param name string
---@return cave.Python.Module.Factory
function Factory:module(name)
validate { name = { name, Str } }
local module_factory = Module.Factory.new(name, self.context_)
table.insert(self.runnables_, module_factory)
return module_factory
end
---@param file_path_like cave.PathLike
---@return cave.Python.Script.Factory
function Factory:script(file_path_like)
validate { file = { file_path_like, Path.Like } }
local file = Path.like(file_path_like)
local script_factory = Script.Factory.new(file, self.context_)
table.insert(self.runnables_, script_factory)
return script_factory
end
---@param runnables cave.Python.Runnable.Factory[]
---@return cave.Python.Interpreter.Factory
function Factory:runnables(runnables)
validate { runnables = { runnables, List(Runnable.Factory) } }
for _, runnable in ipairs(runnables) do
table.insert(self.runnables_, runnable:copy())
end
return self
end
---@param runnable cave.Python.Runnable.Factory
---@return cave.Python.Interpreter.Factory
function Factory:runnable(runnable) return self:runnables { runnable } end
---@param interpreter cave.Python.Interpreter
function Factory:init_interpreter(interpreter)
interpreter.id = self:get_id()
interpreter.path = self:get_path()
interpreter.args = self:get_args()
interpreter.env = self:get_env()
interpreter.runnables = {}
for _, runnable_factory in pairs(self:get_runnables()) do
local runnable = runnable_factory:build()
assert(interpreter.runnables[runnable.id] == nil)
interpreter.runnables[runnable.id] = runnable
end
end
---@return cave.Python.Interpreter
function Factory:build()
local interpreter = setmetatable({}, Interpreter)
self:init_interpreter(interpreter)
return interpreter
end
Interpreter.Factory = Factory
return Interpreter

View File

@@ -0,0 +1,69 @@
local Runnable = require "cave.python.runnable"
local Context = require "cave.context"
local Meta = require "cave.meta"
local Str = Meta.String
local validate = Meta.validate
---@class cave.Python.Module : cave.Python.Runnable
---@field name string
local Module = Meta.derive("Python.Module", Runnable)
function Module:module() return self end
---@class cave.Python.Module.Factory : cave.Python.Runnable.Factory
---@field name_ string
local Factory = Meta.derive("Python.Module.Factory", Runnable.Factory)
---@param name string
---@param context cave.Context
function Factory:init(name, context)
Runnable.Factory.init(self, context)
self.name_ = name
end
---@param other cave.Python.Module.Factory
function Factory:copy_from(other)
Runnable.Factory.copy_from(self, other)
self.name_ = other.name_
end
---@param name string
---@param context cave.Context
---@return cave.Python.Module.Factory
function Factory.new(name, context)
validate { name = { name, Str }, context = { context, Context } }
local factory = setmetatable({}, Factory)
factory:init(name, context)
return factory
end
---@return cave.Python.Module.Factory
function Factory:copy()
local factory = setmetatable({}, Factory)
factory:copy_from(self)
return factory
end
---@return string
function Factory:get_id() return self.id_ or self:get_name() end
---@return string
function Factory:get_name() return self.name_ end
---@param module cave.Python.Module
function Factory:init_module(module)
self:init_runnable(module)
module.name = self:get_name()
end
---@return cave.Python.Module
function Factory:build()
local module = setmetatable({}, Module)
self:init_module(module)
return module
end
Module.Factory = Factory
return Module

View File

@@ -0,0 +1,31 @@
local Meta = require "cave.meta"
local Task = require "cave.task"
---@class cave.Python.Runnable : cave.Task
local Runnable = Meta.derive("Python.Runnable", Task)
---@return cave.Python.Script?
---@return cave.Python.Module?
function Runnable:concrete() return self:script(), self:module() end
---@return cave.Python.Script?
function Runnable:script() end
---@return cave.Python.Module?
function Runnable:module() end
---@class cave.Python.Runnable.Factory : cave.Task.Factory
local Factory = Meta.derive("Python.Runnable.Factory", Task.Factory)
---@return cave.Python.Runnable
function Factory:build() error "Not implemented" end
---@return cave.Python.Runnable
function Factory:copy() error "Not implemented" end
---@param runnable cave.Python.Runnable
function Factory:init_runnable(runnable) self:init_task(runnable) end
Runnable.Factory = Factory
return Runnable

View File

@@ -0,0 +1,76 @@
local Runnable = require "cave.python.runnable"
local Meta = require "cave.meta"
local Path = require "cave.path"
local Context = require "cave.context"
local validate = Meta.validate
---@class cave.Python.Script : cave.Python.Runnable
---@field file cave.Path
local Script = Meta.derive("Python.Script", Runnable)
---@return cave.Python.Script
function Script:script() return self end
---@return string
function Script:valid_file()
assert(self.file:is_file())
return self.file:tostring()
end
---@class cave.Python.Script.Factory : cave.Python.Runnable.Factory
---@field file_ cave.Path
local Factory = Meta.derive("Python.Script.Factory", Runnable.Factory)
---@param file cave.Path
---@param context cave.Context
function Factory:init(file, context)
Runnable.Factory.init(self, context)
self.file_ = file
end
---@param other cave.Python.Script.Factory
function Factory:copy_from(other)
Runnable.Factory.copy_from(self, other)
self.file_ = other.file_:copy()
end
---@param file cave.Path
---@param context cave.Context
---@return cave.Python.Script.Factory
function Factory.new(file, context)
validate { file = { file, Path }, context = { context, Context } }
local factory = setmetatable({}, Factory)
factory:init(file, context)
return factory
end
---@return cave.Python.Script.Factory
function Factory:copy()
local factory = setmetatable({}, Factory)
factory:copy_from(self)
return factory
end
---@return string
function Factory:get_id() return self.id_ or ("[%s]"):format(self:get_file()) end
---@return cave.Path
function Factory:get_file() return self.file_ end
---@param script cave.Python.Script
function Factory:init_script(script)
self:init_runnable(script)
script.file = self:get_file()
end
---@return cave.Python.Script
function Factory:build()
local script = setmetatable({}, Script)
self:init_script(script)
return script
end
Script.Factory = Factory
return Script

124
lua/cave/task.lua Normal file
View File

@@ -0,0 +1,124 @@
local Path = require "cave.path"
local Meta = require "cave.meta"
local Enum = require "cave.enum"
local List = Meta.List
local Map = Meta.Map
local Optional = Meta.Optional
local Str = Meta.String
local validate = Meta.validate
---@enum cave.Task.Tag
local Tag = {
Run = "Run",
Debug = "Debug",
}
Enum.new(Tag, "Task.Tag")
---@class cave.Task
---@field id string
---@field args string[]
---@field cwd cave.Path
---@field env table<string, string>
local Task = Meta.derive "Task"
Task.Tag = Tag
---@return string
function Task:valid_cwd()
assert(self.cwd:is_dir())
return self.cwd:tostring()
end
---@class cave.Task.Factory
---@field id_ string?
---@field args_ string[]
---@field cwd_ cave.Path?
---@field env_ table<string, string>
---@field context_ cave.Context
local Factory = Meta.derive "Task.Factory"
---@param context cave.Context
function Factory:init(context)
self.args_ = {}
self.env_ = {}
self.context_ = context
end
---@param other cave.Task.Factory
function Factory:copy_from(other)
self.id_ = other.id_
self.args_ = vim.deepcopy(other.args_)
self.cwd_ = other.cwd_ and other.cwd_:copy()
self.env_ = vim.deepcopy(other.env_)
self.context_ = other.context_
end
---@return string
function Factory:get_id()
assert(self.id_)
return self.id_
end
---@return string[]
function Factory:get_args() return self.args_ end
---@return cave.Path
function Factory:get_cwd() return self.cwd_ or Path.new(self.context_.dir) end
---@return table<string, string>
function Factory:get_env() return self.env_ end
---@param id string
---@return cave.Task.Factory
function Factory:id(id)
validate { id = { id, Str } }
self.id_ = id
return self
end
---@param args string[]
---@return cave.Task.Factory
function Factory:args(args)
validate { args = { args, List(Str) } }
vim.list_extend(self.args_, args)
return self
end
---@param arg string
---@return cave.Task.Factory
function Factory:arg(arg) return self:args { arg } end
---@param cwd_path_like cave.PathLike
---@return cave.Task.Factory
function Factory:cwd(cwd_path_like)
validate { cwd_path_like = { cwd_path_like, Optional(Path.Like) } }
self.cwd_ = Path.like(cwd_path_like)
return self
end
---@param env table<string, string>
---@return cave.Task.Factory
function Factory:env(env)
validate { env = { env, Map(Str, Str) } }
vim.tbl_extend("error", self.env_, env)
return self
end
---@param task cave.Task
function Factory:init_task(task)
task.id = self:get_id()
task.args = self:get_args()
task.cwd = self:get_cwd()
task.env = self:get_env()
end
---@return cave.Task
function Factory:build() error "Not implemented" end
---@return cave.Task
function Factory:copy() error "Not implemented" end
Task.Factory = Factory
return Task

0
lua/cave/template.lua Normal file
View File

View File

@@ -0,0 +1,23 @@
local Meta = require "cave.meta"
local ClassLogger = require "cave.log.class_logger"
---@class cave.TemplateProvider : overseer.TemplateProvider
---@field get_templates fun(): overseer.TemplateDefinition[]
local TemplateProvider = Meta.derive "TemplateProvider"
TemplateProvider.log = ClassLogger.new(TemplateProvider)
function TemplateProvider:init(name, get_templates_fn)
self.name = name
self.get_templates = get_templates_fn
---@type fun(opts: overseer.SearchParams, cb: fun(tmpls: overseer.TemplateDefinition[]))
self.generator = function(_, cb) cb(self.get_templates()) end
end
---@return cave.TemplateProvider
function TemplateProvider.new(name, get_templates_fn)
local provider = setmetatable({}, TemplateProvider)
provider:init(name, get_templates_fn)
return provider
end
return TemplateProvider

84
lua/cave/util.lua Normal file
View File

@@ -0,0 +1,84 @@
local Path = require "cave.path"
local Meta = require "cave.meta"
local buffers = require "astrocore.buffer"
local Table = Meta.Table
local validate = Meta.validate
local List = Meta.List
local Str = Meta.String
local Util = {}
---@param old table
---@param new table
---@return table
function Util.tbl_diff(old, new)
validate { old = { old, Table }, new = { new, Table } }
local diff = {
added = {},
removed = {},
modified = {},
}
for old_name, old_value in pairs(old) do
local new_value = new[old_name]
if new_value == nil then
diff.removed[old_name] = old_value
elseif type(old_value) ~= type(new_value) or old_value ~= new_value then
diff.modified[old_name] = { old = old_value, new = new_value }
end
end
for new_name, new_value in pairs(new) do
local old_value = old[new_name]
if old_value == nil then
diff.added[new_name] = new_value
elseif type(old_value) ~= type(new_value) or old_value ~= new_value then
assert(diff.modified[new_name].old == old_value and diff.modified[new_name].new == new_value)
end
end
return diff
end
function Util.save_all_buffers()
for _, buf in ipairs(vim.t.bufs) do
local buf_valid = buffers.is_valid(buf)
local buf_modified = vim.api.nvim_buf_get_option(buf, "modified")
if not buf_valid or not buf_modified then goto continue end
local buf_name = vim.fn.fnamemodify(vim.api.nvim_buf_get_name(buf), "%")
if buf_name == "" then goto continue end
local confirm = vim.fn.confirm(('Save changes to "%s"?'):format(buf_name), "&Yes\n&No", 1, "Question")
if confirm == 1 then vim.api.nvim_buf_call(buf, vim.cmd.write) end
::continue::
end
end
function Util.close_all_buffers() buffers.close_all() end
---@return string
function Util.generate_uuid()
local uuidgen_path = Path.new "uuidgen"
assert(uuidgen_path:is_executable())
local cmd = { tostring(uuidgen_path), "-t" }
local output = vim.fn.systemlist(cmd)
validate { output = { output, List(Str) } }
assert(#output == 1)
return output[1]
end
---@param t any
---@return any
function Util.plain(t)
if type(t) ~= "table" then return t end
local mt = getmetatable(t)
if mt ~= nil and (rawget(mt, "__tostring") ~= nil or rawget(mt, "__name") ~= nil) then
return tostring(t)
end
setmetatable(t, nil)
for key, value in pairs(t) do
t[key] = Util.plain(value)
end
return t
end
return Util

View File

@@ -0,0 +1,30 @@
---@type overseer.ComponentFileDefinition
return {
desc = "DAP",
constructor = function(params)
---@type Configuration
local config = params.config
---@type overseer.ComponentSkeleton
return {
on_init = function(_, task)
task.cmd = { vim.fn.exepath "true" }
task:remove_component "on_complete_notify"
task:add_component "on_complete_dispose"
local ToggleTermStrategy = require "overseer.strategy.toggleterm"
task.strategy = ToggleTermStrategy.new {
auto_scroll = false,
close_on_exit = false,
quit_on_exit = "never",
hidden = true,
use_shell = false,
open_on_start = false,
}
end,
on_start = function()
local dap = require "dap"
dap.run(config, { new = true })
end,
}
end,
params = { config = { type = "opaque" } },
}

Binary file not shown.

0
mod/spawner.py Normal file
View File