local async = require 'cord.core.async'
local activities = require 'cord.plugin.activity'
local ws_utils = require 'cord.plugin.fs.workspace'
local config_utils = require 'cord.plugin.config.util'
local hooks = require 'cord.plugin.activity.hooks'
local config = require 'cord.plugin.config'
local uv = vim.loop or vim.uv
local ActivityManager = {}
local mt = { __index = ActivityManager }
ActivityManager.new = async.wrap(function(opts)
local self = setmetatable({
tx = opts.tx,
is_focused = true,
is_paused = false,
is_force_idle = false,
events_enabled = true,
workspace_cache = {},
}, mt)
if config.hooks then
for event, hook in pairs(config.hooks) do
if type(hook) == 'function' then
hooks.register(event, hook, 200)
elseif type(hook) == 'table' then
hooks.register(event, hook[1] or hook.fun, hook.priority or 200)
end
end
end
local rawdir = vim.fn.expand '%:p:h'
local dir = ws_utils.find(rawdir):get() or vim.fn.getcwd()
self.workspace_dir = dir
local cache = {
dir = dir,
name = vim.fn.fnamemodify(dir, ':t'),
}
local repo_url = ws_utils.find_git_repository(dir):get()
if repo_url then
self.repo_url = repo_url
cache.repo_url = repo_url
end
self.workspace_cache[rawdir] = cache
local err = require('cord.api.plugin').init()
if err then error(err, 0) end
return self
end)
function ActivityManager:run()
self.workspace = vim.fn.fnamemodify(self.workspace_dir, ':t')
self.last_updated = uv.now()
hooks.run('ready', self)
self:queue_update(true)
if config.advanced.plugin.autocmds then self.setup_autocmds() end
if config.idle.enabled then
self.idle_timer = uv.new_timer()
self.idle_timer:start(
0,
config.idle.timeout,
vim.schedule_wrap(function() self:check_idle() end)
)
end
end
function ActivityManager:cleanup()
self:clear_autocmds()
if self.idle_timer then
self.idle_timer:stop()
if not self.idle_timer:is_closing() then self.idle_timer:close() end
end
end
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
function ActivityManager:queue_update(force_update)
if not self.events_enabled then return end
self.opts = self:build_opts()
if not self.is_force_idle and (force_update or self:should_update()) then
if self.is_idle then hooks.run('idle_leave', self.opts) end
self:update_activity()
end
end
function ActivityManager:check_idle()
if not self.events_enabled then return end
if not config.idle.enabled and not self.is_force_idle then return end
if self.is_idle then return end
local time_elapsed = uv.now() - self.last_updated
if
self.is_force_idle
or (time_elapsed >= config.idle.timeout and (config.idle.ignore_focus or not self.is_focused))
then
self:update_idle_activity()
else
local time_remaining = config.idle.timeout - time_elapsed
self.idle_timer:stop()
self.idle_timer:start(
time_remaining,
0,
vim.schedule_wrap(function()
self.idle_timer:start(
0,
config.idle.timeout,
vim.schedule_wrap(function() self:check_idle() end)
)
end)
)
end
end
function ActivityManager:update_idle_activity()
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 config.idle.show_status then
local buttons = config_utils.get_buttons(self.opts)
self.opts.buttons = buttons
if config.timestamp.enabled and config.timestamp.reset_on_idle then
self.opts.timestamp = os.time()
end
local activity = activities.build_idle_activity(self.opts)
hooks.run('post_activity', self.opts, activity)
if self.should_skip_update then
self.should_skip_update = false
return
end
self.tx:update_activity(activity)
hooks.run('idle_enter', self.opts)
else
hooks.run('post_activity', self.opts)
if self.should_skip_update then
self.should_skip_update = false
return
end
self.tx:clear_activity()
hooks.run('idle_enter', self.opts)
end
end
function ActivityManager:update_activity()
self.is_idle = false
self.is_force_idle = false
self.last_opts = self.opts
self.last_updated = uv.now()
self.opts.is_idle = false
hooks.run('pre_activity', self.opts)
local activity = activities.build_activity(self.opts)
if activity == true then return end
if activity == false then return self:clear_activity() end
hooks.run('post_activity', self.opts, activity)
if self.should_skip_update then
self.should_skip_update = false
return
end
self.tx:update_activity(activity)
end
function ActivityManager:skip_update() self.should_skip_update = true end
function ActivityManager:pause()
if self.is_paused then return end
self:pause_events()
if self.idle_timer then self.idle_timer:stop() end
self.is_paused = true
end
function ActivityManager:resume()
if not self.is_paused then return end
self:resume_events()
if self.idle_timer then
self.idle_timer:stop()
self.idle_timer:start(
0,
config.idle.timeout,
vim.schedule_wrap(function() self:check_idle() end)
)
end
self.is_paused = false
end
function ActivityManager:pause_events() self.events_enabled = false end
function ActivityManager:resume_events()
self.events_enabled = true
self:queue_update(true)
end
function ActivityManager:idle() self:update_idle_activity() end
function ActivityManager:force_idle()
self.is_force_idle = true
self:update_idle_activity()
end
function ActivityManager:unidle()
self.is_force_idle = false
self:queue_update(true)
end
function ActivityManager:toggle_idle()
if self.is_force_idle or self.is_idle then
self:unidle()
else
self:idle()
end
end
function ActivityManager:hide()
self:pause()
self:clear_activity(true)
end
function ActivityManager:suppress()
self:pause()
self:clear_activity()
end
function ActivityManager:toggle()
if self.is_paused then
self:resume()
else
self:hide()
end
end
function ActivityManager:toggle_suppress()
if self.is_paused then
self:resume()
else
self:suppress()
end
end
function ActivityManager.setup_autocmds()
vim.cmd [[
augroup CordActivityManager
autocmd!
autocmd BufEnter * lua require'cord.server'.manager:on_buf_enter()
autocmd FocusGained * lua require'cord.server'.manager:on_focus_gained()
autocmd FocusLost * lua require'cord.server'.manager:on_focus_lost()
augroup END
]]
if config.advanced.plugin.cursor_update == 'on_hold' then
vim.cmd [[
augroup CordActivityManager
autocmd CursorHold,CursorHoldI * lua require'cord.server'.manager:on_cursor_update()
augroup END
]]
elseif config.advanced.plugin.cursor_update == 'on_move' then
vim.cmd [[
augroup CordActivityManager
autocmd CursorMoved,CursorMovedI * lua require'cord.server'.manager:on_cursor_update()
augroup END
]]
end
end
function ActivityManager.clear_autocmds()
vim.cmd [[
augroup CordActivityManager
autocmd!
augroup END
]]
end
function ActivityManager:set_activity(activity) self.tx:update_activity(activity) end
function ActivityManager:clear_activity(force) self.tx:clear_activity(force) end
function ActivityManager:should_update_time()
return config.timestamp.enabled
and (config.timestamp.reset_on_change or config.timestamp.reset_on_idle and self.is_idle)
or false
end
function ActivityManager:on_buf_enter()
local rawdir = vim.fn.expand '%:p:h'
local cached = self.workspace_cache[rawdir]
if cached then
if cached.dir ~= self.workspace_dir then
self.workspace_dir = cached.dir
self.workspace = cached.name
self.repo_url = cached.repo_url
self.opts.workspace_dir = self.workspace_dir
self.opts.workspace_name = self.workspace
self.opts.workspace = self.workspace
self.opts.repo_url = self.repo_url
hooks.run('workspace_change', self.opts)
end
self:queue_update()
return
elseif cached == false then
if self.workspace_dir then
self.workspace_dir = nil
self.workspace = nil
self.repo_url = nil
self.opts.workspace_dir = nil
self.opts.workspace_name = nil
self.opts.workspace = nil
self.opts.repo_url = nil
hooks.run('workspace_change', self.opts)
end
self:queue_update()
return
end
async.run(function()
local ws_utils = require 'cord.plugin.fs.workspace'
local dir = ws_utils.find(vim.fn.expand '%:p:h'):get() or vim.fn.getcwd()
if not dir then
self.workspace_cache[rawdir] = false
self:queue_update()
return
end
self.workspace_dir = dir
self.workspace = vim.fn.fnamemodify(self.workspace_dir, ':t')
self.opts.workspace_dir = self.workspace_dir
self.opts.workspace_name = self.workspace
self.opts.workspace = self.workspace
local repo_url = ws_utils.find_git_repository(self.workspace_dir):get()
self.repo_url = repo_url
self.opts.repo_url = repo_url
self.workspace_cache[rawdir] = {
dir = self.workspace_dir,
name = self.workspace,
repo_url = self.repo_url,
}
hooks.run('workspace_change', self.opts)
self:queue_update()
end)
end
function ActivityManager:on_focus_gained()
if not self.events_enabled then return end
self.is_focused = true
self.opts.is_focused = true
if config.idle.unidle_on_focus then self:queue_update(true) end
end
function ActivityManager:on_focus_lost()
if not self.events_enabled then return end
self.is_focused = false
self.opts.is_focused = false
end
function ActivityManager:on_cursor_update()
if not self.events_enabled then return end
self:queue_update()
end
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,
buftype = vim.bo.buftype,
is_read_only = vim.bo.readonly or not vim.bo.modifiable,
cursor_line = cursor_position[1],
cursor_char = cursor_position[2],
workspace_dir = self.workspace_dir,
workspace_name = self.workspace,
workspace = self.workspace,
repo_url = self.repo_url,
is_focused = self.is_focused,
is_idle = self.is_idle,
}
if config.timestamp.enabled then
if self.last_opts and self.last_opts.timestamp then
opts.timestamp = self.last_opts.timestamp
else
opts.timestamp = os.time()
end
end
if self:should_update_time() then opts.timestamp = os.time() end
local buttons = config_utils.get_buttons(opts)
opts.buttons = buttons
return opts
end
return ActivityManager