From 277b5b0517c9dfcb2a6887820005a8f3df068a14 Mon Sep 17 00:00:00 2001 From: vyfor Date: Sat, 21 Dec 2024 16:31:07 +0500 Subject: [PATCH] fix!: bugfixes related to opts handling, workspace detection callback, button validation as well as slight changes in configuration --- lua/cord/activity/manager.lua | 173 +++++++++++++++++----------------- lua/cord/event/sender.lua | 5 +- lua/cord/util/config.lua | 69 +++++++++----- lua/cord/util/workspace.lua | 14 ++- 4 files changed, 150 insertions(+), 111 deletions(-) diff --git a/lua/cord/activity/manager.lua b/lua/cord/activity/manager.lua index 496c5095..c335dbe2 100644 --- a/lua/cord/activity/manager.lua +++ b/lua/cord/activity/manager.lua @@ -12,7 +12,8 @@ local uv = vim.loop or vim.uv ---@field is_force_idle boolean Whether idle state is forced ---@field events_enabled boolean Whether events are enabled ---@field last_activity Activity Last activity state ----@field last_opts CordOpts Last options passed to activity update +---@field opts CordOpts Latest options passed to activity updates +---@field last_opts? CordOpts Previous options passed to activity updates, used to detect changes ---@field workspace_dir string Current workspace directory ---@field repo_url string|nil Current Git repository URL local ActivityManager = {} @@ -85,86 +86,60 @@ function ActivityManager.new(opts, callback) return self end ----Queue an activity update ----@param force_update? boolean Whether to force the update regardless of conditions -function ActivityManager:queue_update(force_update) - if not self.events_enabled then return end - vim.schedule(function() - if self.should_skip_update then - self.should_skip_update = false - return - end - - local cursor_position = vim.api.nvim_win_get_cursor(0) - local opts = { - manager = self, - filename = vim.fn.expand '%:t', - filetype = vim.bo.filetype, - is_read_only = vim.bo.readonly, - cursor_line = cursor_position[1], - cursor_char = cursor_position[2], - timestamp = self.timestamp, - workspace_dir = self.workspace_dir, - workspace_name = self.workspace_name, - repo_url = self.repo_url, - is_focused = self.is_focused, - is_idle = self.is_idle, - } - local buttons = config_utils:get_buttons(opts) - opts.buttons = buttons - - if - not self.is_force_idle and (force_update or self:should_update(opts)) - then - self:update_activity(opts) - elseif not self.is_idle then - self:check_idle(opts) - end - end) -end - ----Check if an activity update is needed ----@param opts CordOpts Options to check against ----@return boolean Whether an update is needed -function ActivityManager:should_update(opts) - local should_update = not self.last_opts - or opts.filename ~= self.last_opts.filename - or opts.filetype ~= self.last_opts.filetype - or opts.is_read_only ~= self.last_opts.is_read_only - or opts.cursor_line ~= self.last_opts.cursor_line - or opts.cursor_char ~= self.last_opts.cursor_char - or opts.is_focused ~= self.last_opts.is_focused - - return should_update -end - ---Run the activity manager ---@return nil function ActivityManager:run() self.workspace_name = vim.fn.fnamemodify(self.workspace_dir, ':t') self.last_updated = uv.now() + if self.config.timestamp.enabled then self.timestamp = os.time() end + if self.config.advanced.plugin.usercmds then self:setup_usercmds() end + if self.config.hooks.on_ready then self.config.hooks.on_ready(self) end + if self.config.advanced.plugin.autocmds then self:setup_autocmds() end + self:queue_update(true) if self.config.idle.enabled then self.idle_timer = uv.new_timer() self.idle_timer:start( - 0, + self.config.idle.timeout, self.config.idle.timeout, vim.schedule_wrap(function() self:check_idle() end) ) end +end - self:setup_autocmds() - if self.config.usercmds then self:setup_usercmds() end - self:queue_update(true) +---Check if an activity update is needed +---@return boolean Whether an update is needed +function ActivityManager:should_update() + local should_update = not self.last_opts + or self.opts.filename ~= self.last_opts.filename + or self.opts.filetype ~= self.last_opts.filetype + or self.opts.is_read_only ~= self.last_opts.is_read_only + or self.opts.cursor_line ~= self.last_opts.cursor_line + or self.opts.cursor_char ~= self.last_opts.cursor_char + or self.opts.is_focused ~= self.last_opts.is_focused + + return should_update +end + +---Queue an activity update +---@param force_update? boolean Whether to force the update regardless of conditions +function ActivityManager:queue_update(force_update) + if not self.events_enabled then return end + if self.should_skip_update then + self.should_skip_update = false + return + end - if self.config.hooks.on_ready then self.config.hooks.on_ready() end + self.opts = self:build_opts() + if not self.is_force_idle and (force_update or self:should_update()) then + self:update_activity() + end end ---Check if the activity should be updated to idle ----@param opts? CordOpts Options to check against ---@return nil -function ActivityManager:check_idle(opts) +function ActivityManager:check_idle() if not self.events_enabled then return end if not self.config.idle.enabled and not self.is_force_idle then return end if self.is_idle then return end @@ -181,50 +156,57 @@ function ActivityManager:check_idle(opts) and (self.config.idle.ignore_focus or not self.is_focused) ) then - self:update_idle_activity(opts or self.last_opts) + self:update_idle_activity() end end ---Update the activity to idle ----@param opts CordOpts Options to update with ---@return nil -function ActivityManager:update_idle_activity(opts) +function ActivityManager:update_idle_activity() + print('is opts null?', self.opts == nil) + if not self.opts then self.opts = self:build_opts() end + self.opts.is_idle = true self.is_idle = true self.last_updated = uv.now() if self.config.idle.show_status then - if self.config.hooks.on_update then self.config.hooks.on_update(opts) end + local buttons = config_utils:get_buttons(self.opts) + self.opts.buttons = buttons + + if self.config.hooks.on_update then + self.config.hooks.on_update(self.opts) + end - local activity = activities.build_idle_activity(self.config, opts) + local activity = activities.build_idle_activity(self.config, self.opts) if self.config.hooks.on_idle then - self.config.hooks.on_idle(opts, activity) + self.config.hooks.on_idle(self.opts, activity) end self.tx:update_activity(activity) else - if self.config.hooks.on_idle then self.config.hooks.on_idle(opts) end + if self.config.hooks.on_idle then self.config.hooks.on_idle(self.opts) end self.tx:clear_activity() end end ---Update the activity ----@param opts CordOpts Options to update with ---@return nil -function ActivityManager:update_activity(opts) +function ActivityManager:update_activity() if self:should_update_time() then self.timestamp = os.time() end self.is_idle = false self.is_force_idle = false - self.last_opts = opts + self.last_opts = self.opts self.last_updated = uv.now() + self.opts.is_idle = false - if self.config.hooks.on_update then self.config.hooks.on_update(opts) end + if self.config.hooks.on_update then self.config.hooks.on_update(self.opts) end - local activity = activities.build_activity(self.config, opts) + local activity = activities.build_activity(self.config, self.opts) if self.config.hooks.on_activity then - self.config.hooks.on_activity(opts, activity) + self.config.hooks.on_activity(self.opts, activity) end self.tx:update_activity(activity) @@ -248,7 +230,7 @@ function ActivityManager:resume() self:resume_events() if self.idle_timer then self.idle_timer:start( - 0, + self.config.idle.timeout, self.config.idle.timeout, vim.schedule_wrap(function() self:check_idle() end) ) @@ -271,7 +253,7 @@ end ---@return nil function ActivityManager:force_idle() self.is_force_idle = true - self:queue_update() + self:update_idle_activity() end ---Unforce idle state @@ -311,8 +293,6 @@ end ---Setup user commands ---@return nil function ActivityManager:setup_usercmds() - if not self.config.usercmds then return end - vim.cmd [[ command! CordShowPresence lua require'cord'.manager:resume() command! CordHidePresence lua require'cord'.manager:hide() @@ -349,8 +329,6 @@ function ActivityManager:setup_autocmds() augroup END ]] end - - self:queue_update(true) end ---Clear autocmds @@ -395,10 +373,11 @@ function ActivityManager:on_buf_enter() self.workspace_name = vim.fn.fnamemodify(self.workspace_dir, ':t') if self.config.hooks.on_workspace_change then - local opts = self.last_opts or {} - opts.workspace_dir = self.workspace_dir - opts.workspace_name = self.workspace_name - self.config.hooks.on_workspace_change(opts) + if not self.opts then self.opts = self:build_opts() end + self.opts.workspace_dir = self.workspace_dir + self.opts.workspace_name = self.workspace_name + + self.config.hooks.on_workspace_change(self.opts) end ::update:: @@ -412,6 +391,7 @@ end function ActivityManager:on_focus_gained() if not self.events_enabled then return end self.is_focused = true + self.opts.is_focused = true self:queue_update() end @@ -420,6 +400,7 @@ end function ActivityManager:on_focus_lost() if not self.events_enabled then return end self.is_focused = false + self.opts.is_focused = false self:queue_update() end @@ -430,4 +411,28 @@ function ActivityManager:on_cursor_update() self:queue_update() end +---Build options +---@return CordOpts +function ActivityManager:build_opts() + local cursor_position = vim.api.nvim_win_get_cursor(0) + local opts = { + manager = self, + filename = vim.fn.expand '%:t', + filetype = vim.bo.filetype, + is_read_only = vim.bo.readonly, + cursor_line = cursor_position[1], + cursor_char = cursor_position[2], + timestamp = self.timestamp, + workspace_dir = self.workspace_dir, + workspace_name = self.workspace_name, + repo_url = self.repo_url, + is_focused = self.is_focused, + is_idle = self.is_idle, + } + local buttons = config_utils:get_buttons(opts) + opts.buttons = buttons + + return opts +end + return ActivityManager diff --git a/lua/cord/event/sender.lua b/lua/cord/event/sender.lua index a7b930e9..8b6deb0d 100644 --- a/lua/cord/event/sender.lua +++ b/lua/cord/event/sender.lua @@ -14,7 +14,10 @@ function Producer:send_event(type, data) end function Producer:initialize(config) - self:send_event('initialize', { log_level = config.log_level }) + self:send_event( + 'initialize', + { log_level = config.advanced.plugin.log_level } + ) end function Producer:update_activity(activity) diff --git a/lua/cord/util/config.lua b/lua/cord/util/config.lua index f3a77df9..239fa6e1 100644 --- a/lua/cord/util/config.lua +++ b/lua/cord/util/config.lua @@ -36,8 +36,8 @@ ---@field dashboard? string|fun(opts: CordOpts):string Text for dashboard activity ---@class CordButtonConfig ----@field label string|fun(opts: CordOpts):string Button label ----@field url string|fun(opts: CordOpts):string Button URL +---@field label string|fun(opts: CordOpts):string? Button label +---@field url string|fun(opts: CordOpts):string? Button URL ---@class CordAssetConfig ---@field name? string|fun(opts: CordOpts):string Asset name @@ -47,7 +47,7 @@ ---@field type? string|fun(opts: CordOpts):string Asset type ---@class CordHooksConfig ----@field on_ready? fun():nil +---@field on_ready? fun(manager: ActivityManager):nil ---@field on_update? fun(opts: CordOpts):nil ---@field on_activity? fun(opts: CordOpts, activity: Activity):nil ---@field on_idle? fun(opts: CordOpts, activity: Activity?):nil @@ -55,10 +55,16 @@ ---@field on_disconnect? fun():nil ---@class CordAdvancedConfig +---@field plugin? CordAdvancedPluginConfig configuration ---@field server? CordAdvancedServerConfig configuration ---@field cursor_update_mode? string Cursor update mode ---@field variables_in_functions? boolean Whether to use variables in functions +---@class CordAdvancedPluginConfig +---@field log_level? integer Logging level (from `vim.log.levels`) +---@field autocmds? boolean Whether to enable autocmds +---@field usercmds? boolean Whether to enable user commands + ---@class CordAdvancedServerConfig ---@field pipe_path? string Path to the server's pipe ---@field executable_path? string Path to the server's executable @@ -67,11 +73,9 @@ ---@alias CordVariablesConfig { [string]: string|fun(opts: CordOpts):string } ---@class CordConfig ----@field usercmds? boolean Whether to create user commands ----@field log_level? integer Logging level (from vim.log.levels) ----@field timestamp? CordTimestampConfig Timestamp configuration ---@field editor? CordEditorConfig Editor configuration ---@field display? CordDisplayConfig Display configuration +---@field timestamp? CordTimestampConfig Timestamp configuration ---@field idle? CordIdleConfig Idle configuration ---@field text? CordTextConfig Text configuration ---@field buttons? CordButtonConfig[] Buttons configuration @@ -88,13 +92,6 @@ local M = {} ---@type CordConfig M.values = { - usercmds = true, - log_level = vim.log.levels.INFO, - timestamp = { - enabled = true, - reset_on_idle = false, - reset_on_change = false, - }, editor = { client = 'neovim', tooltip = 'The Superior Text Editor', @@ -105,6 +102,11 @@ M.values = { swap_fields = false, swap_icons = false, }, + timestamp = { + enabled = true, + reset_on_idle = false, + reset_on_change = false, + }, idle = { enabled = true, timeout = 300000, @@ -141,6 +143,11 @@ M.values = { on_disconnect = nil, }, advanced = { + plugin = { + log_level = vim.log.levels.INFO, + autocmds = true, + usercmds = true, + }, server = { pipe_path = nil, executable_path = nil, @@ -153,7 +160,7 @@ M.values = { function M:validate(user_config) local config = vim.tbl_deep_extend('force', self.values, user_config) - logger.set_level(config.log_level) + logger.set_level(config.advanced.plugin.log_level) icons.set_theme(config.display.theme) if config.buttons and #config.buttons > 2 then @@ -217,20 +224,38 @@ function M.get(option, args) end function M:get_buttons(opts) - local buttons = self.values.buttons - if not buttons then return end - - for i = 1, #buttons do - local button = buttons[i] + if not self.values.buttons then return {} end + + local buttons = {} + for i = 1, #self.values.buttons do + local sourcebtn = self.values.buttons[i] + local button = {} + + if type(sourcebtn.label) == 'function' then + local label = sourcebtn.label(opts) + if not label then goto continue end + button.label = label + else + if not sourcebtn.label then goto continue end + button.label = sourcebtn.label + end - if type(button.label) == 'function' then - buttons[i].label = button.label(opts) + if type(sourcebtn.url) == 'function' then + local url = sourcebtn.url(opts) + if not url then goto continue end + button.url = url + else + if not sourcebtn.url then goto continue end + button.url = sourcebtn.url end - if type(button.url) == 'function' then buttons[i].url = button.url(opts) end + buttons[#buttons + 1] = button + + ::continue:: end return buttons end + return M diff --git a/lua/cord/util/workspace.lua b/lua/cord/util/workspace.lua index 64674ac5..b3f57801 100644 --- a/lua/cord/util/workspace.lua +++ b/lua/cord/util/workspace.lua @@ -15,14 +15,20 @@ M.find = function(initial_path, callback) local curr_dir = initial_path local function check_marker(curr_dir) + local pending = #VCS_MARKERS + local found = false + for _, marker in ipairs(VCS_MARKERS) do local marker_path = curr_dir .. '/' .. marker uv.fs_stat(marker_path, function(err, stat) - if err then return callback(nil) end + pending = pending - 1 - if stat and stat.type == 'directory' then - callback(curr_dir) - else + if not err and stat and stat.type == 'directory' then + if not found then + found = true + callback(curr_dir) + end + elseif pending == 0 and not found then local parent = vim.fn.fnamemodify(curr_dir, ':h') if parent == curr_dir then callback(initial_path)