Initial commit
This commit is contained in:
7
.stylua.toml
Normal file
7
.stylua.toml
Normal 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
75
lua/cave/config.lua
Normal 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
30
lua/cave/context.lua
Normal 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
23
lua/cave/enum.lua
Normal 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
62
lua/cave/env.lua
Normal 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
51
lua/cave/init.lua
Normal 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
|
||||||
46
lua/cave/log/class_logger.lua
Normal file
46
lua/cave/log/class_logger.lua
Normal 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
|
||||||
65
lua/cave/log/function_logger.lua
Normal file
65
lua/cave/log/function_logger.lua
Normal 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
25
lua/cave/log/init.lua
Normal 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
39
lua/cave/log/logger.lua
Normal 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
188
lua/cave/manager.lua
Normal 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
238
lua/cave/meta.lua
Normal 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
13
lua/cave/option.lua
Normal 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
92
lua/cave/path.lua
Normal 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
235
lua/cave/project.lua
Normal 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
9
lua/cave/python/init.lua
Normal 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
|
||||||
236
lua/cave/python/interpreter.lua
Normal file
236
lua/cave/python/interpreter.lua
Normal 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
|
||||||
69
lua/cave/python/module.lua
Normal file
69
lua/cave/python/module.lua
Normal 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
|
||||||
31
lua/cave/python/runnable.lua
Normal file
31
lua/cave/python/runnable.lua
Normal 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
|
||||||
76
lua/cave/python/script.lua
Normal file
76
lua/cave/python/script.lua
Normal 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
124
lua/cave/task.lua
Normal 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
0
lua/cave/template.lua
Normal file
23
lua/cave/template_provider.lua
Normal file
23
lua/cave/template_provider.lua
Normal 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
84
lua/cave/util.lua
Normal 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
|
||||||
30
lua/overseer/component/dap.lua
Normal file
30
lua/overseer/component/dap.lua
Normal 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" } },
|
||||||
|
}
|
||||||
BIN
mod/__pycache__/spawner.cpython-312.pyc
Normal file
BIN
mod/__pycache__/spawner.cpython-312.pyc
Normal file
Binary file not shown.
0
mod/spawner.py
Normal file
0
mod/spawner.py
Normal file
Reference in New Issue
Block a user