Skip to content

Commit 8236c3b

Browse files
committed
async plugin load
1 parent 19f43cc commit 8236c3b

File tree

1 file changed

+152
-149
lines changed

1 file changed

+152
-149
lines changed

lua/strive/init.lua

Lines changed: 152 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -499,14 +499,9 @@ function ProgressWindow:update_entry(plugin_name, status, message)
499499
}
500500

501501
if self.visible then
502-
-- Only schedule if we're not already in main thread
503-
if vim.in_fast_event() then
504-
vim.schedule(function()
505-
self:refresh()
506-
end)
507-
else
502+
Async.safe_schedule(function()
508503
self:refresh()
509-
end
504+
end)
510505
end
511506

512507
return self
@@ -653,89 +648,127 @@ local function load_opts(opt)
653648
return type(opt) == 'string' and vim.cmd(opt) or opt()
654649
end
655650

651+
-- return a Promise
652+
function Plugin:load_scripts()
653+
return function(callback)
654+
local plugin_path = self:get_path()
655+
local plugin_dir = vim.fs.joinpath(plugin_path, 'plugin')
656+
657+
if not isdir(plugin_dir) then
658+
callback(Result.success(false))
659+
return
660+
end
661+
662+
Async.scandir(plugin_dir)(function(result)
663+
if not result.success or not result.value then
664+
M.log('debug', string.format('Plugin directory not found: %s', plugin_dir))
665+
callback(Result.success(false))
666+
return
667+
end
668+
669+
local scripts = {}
670+
while true do
671+
local name, type = uv.fs_scandir_next(result.value)
672+
if not name then
673+
break
674+
end
675+
if type == 'file' and (name:match('%.lua$') or name:match('%.vim$')) then
676+
scripts[#scripts + 1] = vim.fs.joinpath(plugin_dir, name)
677+
end
678+
end
679+
680+
if #scripts > 0 then
681+
Async.safe_schedule(function()
682+
for _, file_path in ipairs(scripts) do
683+
vim.cmd.source(vim.fn.fnameescape(file_path))
684+
end
685+
callback(Result.success(true))
686+
end)
687+
else
688+
callback(Result.success(false))
689+
end
690+
end)
691+
end
692+
end
693+
656694
-- Load a plugin and its dependencies
657-
function Plugin:load(opts)
695+
function Plugin:load()
658696
if self.loaded then
659697
return true
660698
end
661699

662-
-- Check if plugin exists
663-
local stat = uv.fs_stat(self:get_path())
664-
if not stat or stat.type ~= 'directory' then
665-
return false
666-
end
700+
Async.async(function()
701+
local plugin_path = self:get_path()
702+
local stat = uv.fs_stat(plugin_path)
703+
if not stat or stat.type ~= 'directory' then
704+
self.status = STATUS.ERROR
705+
return false
706+
end
667707

668-
-- Set loaded flag to prevent recursive loading
669-
self.loaded = true
670-
vim.g.strive_loaded = vim.g.strive_loaded + 1
708+
self.loaded = true
709+
vim.g.strive_loaded = vim.g.strive_loaded + 1
671710

672-
if self.init_opts then
673-
load_opts(self.init_opts)
674-
end
711+
if self.init_opts then
712+
load_opts(self.init_opts)
713+
end
675714

676-
if self.is_local then
677-
-- For local plugins, add directly to runtimepath
678-
local plugin_path = self:get_path()
679-
vim.opt.rtp:append(plugin_path)
715+
if self.is_local then
716+
vim.opt.rtp:append(plugin_path)
717+
718+
local after_path = vim.fs.joinpath(plugin_path, 'after')
719+
if isdir(after_path) then
720+
vim.opt.rtp:append(after_path)
721+
end
680722

681-
-- Also check for and add the 'after' directory
682-
local after_path = vim.fs.joinpath(plugin_path, 'after')
683-
if isdir(after_path) then
684-
vim.opt.rtp:append(after_path)
723+
local result = Async.try_await(self:load_scripts())
724+
if result.error then
725+
M.log(
726+
'error',
727+
string.format('Failed to load scripts for %s: %s', self.name, tostring(result.error))
728+
)
729+
return
730+
end
731+
elseif self.is_lazy then
732+
vim.cmd.packadd(self.plugin_name)
685733
end
686-
self:load_scripts((opts and opts.script_cb) and opts.script_cb or nil)
687-
elseif self.is_lazy then
688-
-- For non-local lazy plugins, use packadd
689-
vim.cmd.packadd(self.plugin_name)
690-
end
691734

692-
self:call_setup()
735+
self:call_setup()
693736

694-
self.status = STATUS.LOADED
695-
if self.group_ids then
696-
for _, id in ipairs(self.group_ids) do
697-
api.nvim_del_augroup_by_id(id)
737+
self.status = STATUS.LOADED
738+
if self.group_ids and #self.group_ids > 0 then
739+
for _, id in ipairs(self.group_ids) do
740+
api.nvim_del_augroup_by_id(id)
741+
end
742+
self.group_ids = {}
698743
end
699-
end
700744

701-
local deps_count = #self.dependencies
702-
-- Load dependencies in parallel
703-
if deps_count > 0 then
704-
Async.async(function()
705-
-- Pre-allocate the array with exact size
706-
local dependency_promises = {}
707-
local promise_count = 0
708-
709-
-- Avoid creating unnecessary closures
710-
for i = 1, deps_count do
711-
local dep = self.dependencies[i]
712-
if not dep.loaded then
713-
promise_count = promise_count + 1
714-
dependency_promises[promise_count] = function(cb)
715-
-- Reuse the same async function for all dependencies
716-
Async.async(function()
717-
cb(Result.success(dep:load()))
718-
end)()
719-
end
720-
end
745+
local deps_to_load = {}
746+
for _, dep in ipairs(self.dependencies) do
747+
if not dep.loaded then
748+
table.insert(deps_to_load, dep)
721749
end
750+
end
722751

723-
-- Only await if we have promises
724-
if promise_count > 0 then
725-
Async.await(Async.all(dependency_promises))
752+
if #deps_to_load > 0 then
753+
local promises = {}
754+
for _, dep in ipairs(deps_to_load) do
755+
table.insert(promises, function(cb)
756+
Async.async(function()
757+
dep:load()
758+
cb(Result.success(true))
759+
end)()
760+
end)
726761
end
727762

728-
-- Run config after dependencies are loaded
729-
if self.config_opts then
730-
load_opts(self.config_opts)
731-
end
732-
end)()
733-
else
734-
-- No dependencies, run config immediately
763+
Async.await(Async.all(promises))
764+
end
765+
735766
if self.config_opts then
736767
load_opts(self.config_opts)
737768
end
738-
end
769+
770+
return true
771+
end)()
739772

740773
return true
741774
end
@@ -799,45 +832,6 @@ function Plugin:ft(filetypes)
799832
return self
800833
end
801834

802-
function Plugin:load_scripts(callback)
803-
Async.async(function()
804-
local plugin_path = self:get_path()
805-
local plugin_dir = vim.fs.joinpath(plugin_path, 'plugin')
806-
if not isdir(plugin_dir) then
807-
return
808-
end
809-
810-
local result = Async.try_await(Async.scandir(plugin_dir))
811-
if not result.success or not result.value then
812-
M.log('debug', string.format('Plugin directory not found: %s', plugin_dir))
813-
return
814-
end
815-
816-
-- Collect all scripts first
817-
local scripts = {}
818-
while true do
819-
local name, type = uv.fs_scandir_next(result.value)
820-
if not name then
821-
break
822-
end
823-
if type == 'file' and (name:match('%.lua$') or name:match('%.vim$')) then
824-
scripts[#scripts + 1] = vim.fs.joinpath(plugin_dir, name)
825-
end
826-
end
827-
828-
if #scripts > 0 then
829-
Async.safe_schedule(function()
830-
for _, file_path in ipairs(scripts) do
831-
vim.cmd.source(vim.fn.fnameescape(file_path))
832-
end
833-
if callback then
834-
callback()
835-
end
836-
end)
837-
end
838-
end)()
839-
end
840-
841835
-- Set up lazy loading for specific commands
842836
function Plugin:cmd(commands)
843837
self.is_lazy = true
@@ -863,16 +857,12 @@ function Plugin:cmd(commands)
863857
local args = opts.args ~= '' and (' ' .. opts.args) or ''
864858
local bang = opts.bang
865859

866-
if self.is_local then
867-
self:load({
868-
script_cb = function()
869-
execute(name, bang, args)
870-
end,
871-
})
872-
return
873-
end
874-
self:load()
875-
execute(name, bang, args)
860+
Async.async(function()
861+
self:load()
862+
Async.safe_schedule(function()
863+
execute(name, bang, args)
864+
end)
865+
end)()
876866
end, {
877867
nargs = '*',
878868
bang = true,
@@ -1563,44 +1553,57 @@ function M.clean()
15631553
table.insert(to_remove, { name = name, dir = dir })
15641554
end
15651555
end
1556+
if #to_remove == 0 then
1557+
vim.notify('[Strive]: no plugins to remove')
1558+
end
15661559

15671560
-- Show plugins that will be removed
1568-
if #to_remove > 0 then
1569-
M.log('info', string.format('Found %d unused plugins to clean:', #to_remove))
1570-
for _, item in ipairs(to_remove) do
1571-
local path = vim.fs.joinpath(item.dir, item.name)
1572-
M.log('info', string.format('Will remove: %s', path))
1573-
end
1574-
1575-
-- Perform the actual deletion
1576-
M.log('info', 'Starting deletion process...')
1577-
1578-
-- Process deletions one by one to ensure completion
1579-
for _, item in ipairs(to_remove) do
1580-
local path = vim.fs.joinpath(item.dir, item.name)
1581-
M.log('info', string.format('Removing %s', path))
1561+
M.log('info', string.format('Found %d unused plugins to clean:', #to_remove))
1562+
ui:open()
1563+
for _, item in ipairs(to_remove) do
1564+
local path = vim.fs.joinpath(item.dir, item.name)
1565+
M.log('info', string.format('Will remove: %s', path))
1566+
ui:update_entry(item.name, 'PENDING', 'Marked to removal')
1567+
end
15821568

1583-
-- Use vim.fn.delete synchronously to ensure completion
1584-
local ok, result_or_err = pcall(function()
1585-
return vim.fn.delete(path, 'rf')
1586-
end)
1569+
-- Perform the actual deletion
1570+
M.log('info', 'Starting deletion process...')
1571+
1572+
vim.ui.select(
1573+
{ 'Yes', 'No' },
1574+
{ prompt = string.format('Remove %d unused plugins?', #to_remove) },
1575+
function(choice)
1576+
if choice and choice:lower():match('^y') then
1577+
-- Process deletions through TaskQueue
1578+
local task_queue = TaskQueue.new(DEFAULT_SETTINGS.max_concurrent_tasks)
1579+
1580+
for _, item in ipairs(to_remove) do
1581+
task_queue:enqueue(function(done)
1582+
Async.async(function()
1583+
local path = vim.fs.joinpath(item.dir, item.name)
1584+
ui:update_entry(item.name, 'CLEANING', 'Removing...')
1585+
1586+
-- Delete and handle errors
1587+
local ok, result = pcall(vim.fn.delete, path, 'rf')
1588+
if not ok or result ~= 0 then
1589+
ui:update_entry(item.name, 'ERROR', 'Failed to remove')
1590+
else
1591+
ui:update_entry(item.name, 'REMOVED', 'Successfully removed')
1592+
end
1593+
done()
1594+
end)()
1595+
end)
1596+
end
15871597

1588-
if not ok then
1589-
M.log('error', string.format('Error deleting %s: %s', path, tostring(result_or_err)))
1590-
elseif result_or_err ~= 0 then
1591-
M.log('error', string.format('Failed to delete %s, return code: %d', path, result_or_err))
1598+
task_queue:on_complete(function()
1599+
Async.await(Async.delay(2000))
1600+
ui:close()
1601+
end)
15921602
else
1593-
M.log('info', string.format('Successfully removed %s', path))
1603+
ui:close()
15941604
end
1595-
1596-
-- Add a small delay to ensure file system operations complete
1597-
Async.await(Async.delay(100))
15981605
end
1599-
1600-
M.log('info', 'Clean operation complete.')
1601-
else
1602-
M.log('info', 'No unused plugins to clean.')
1603-
end
1606+
)
16041607
end)()
16051608
end
16061609

0 commit comments

Comments
 (0)