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 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