22-- A lightweight, feature-rich plugin manager with support for lazy loading,
33-- dependencies, and asynchronous operations.
44
5- local api , uv , if_nil , Iter , ffi = vim .api , vim .uv , vim .F .if_nil , vim . iter , require (' ffi' )
5+ local api , uv , if_nil , ffi = vim .api , vim .uv , vim .F .if_nil , require (' ffi' )
66
77-- =====================================================================
88-- 1. Configuration and Constants
@@ -41,6 +41,10 @@ local STATUS = {
4141 ERROR = ' error' ,
4242}
4343
44+ local function isdir (dir )
45+ return (uv .fs_stat (dir ) or {}).type == ' directory'
46+ end
47+
4448-- =====================================================================
4549-- 2. Async Utilities
4650-- =====================================================================
@@ -146,7 +150,7 @@ function Async.await(promise)
146150 end
147151
148152 promise (function (result )
149- vim . schedule (function ()
153+ Async . safe_schedule (function ()
150154 local ok = coroutine.resume (co , result )
151155 if not ok then
152156 vim .notify (debug.traceback (co ), vim .log .levels .ERROR )
@@ -172,7 +176,7 @@ function Async.try_await(promise)
172176 end
173177
174178 promise (function (result )
175- vim . schedule (function ()
179+ Async . safe_schedule (function ()
176180 local ok = coroutine.resume (co , result )
177181 if not ok then
178182 vim .notify (debug.traceback (co ), vim .log .levels .ERROR )
@@ -193,7 +197,7 @@ function Async.async(func)
193197 end )
194198
195199 if not status then
196- vim . schedule (function ()
200+ Async . safe_schedule (function ()
197201 vim .notify (' Async error: ' .. tostring (result ), vim .log .levels .ERROR )
198202 end )
199203 end
@@ -204,7 +208,7 @@ function Async.async(func)
204208 local function step (...)
205209 local ok , err = coroutine.resume (co , ... )
206210 if not ok then
207- vim . schedule (function ()
211+ Async . safe_schedule (function ()
208212 vim .notify (' Coroutine error: ' .. debug.traceback (co , err ), vim .log .levels .ERROR )
209213 end )
210214 end
@@ -301,6 +305,14 @@ function Async.scandir(dir)
301305 end
302306end
303307
308+ function Async .safe_schedule (callback )
309+ if not vim .in_fast_event () then
310+ callback ()
311+ return
312+ end
313+ vim .schedule (callback )
314+ end
315+
304316-- =====================================================================
305317-- 3. Task Queue
306318-- =====================================================================
@@ -483,7 +495,7 @@ function ProgressWindow:update_entry(plugin_name, status, message)
483495
484496 if self .visible then
485497 -- Schedule UI updates to run in the main event loop
486- vim . schedule (function ()
498+ Async . safe_schedule (function ()
487499 self :refresh ()
488500 end )
489501 end
@@ -630,12 +642,10 @@ function Plugin:is_installed()
630642end
631643
632644local function load_opts (opt )
633- if opt then
634- if type (opt ) == ' string' then
635- vim .cmd (opt )
636- elseif type (opt ) == ' function' then
637- opt ()
638- end
645+ if type (opt ) == ' string' then
646+ vim .cmd (opt )
647+ elseif type (opt ) == ' function' then
648+ opt ()
639649 end
640650end
641651
@@ -651,7 +661,7 @@ function Plugin:packadd()
651661end
652662
653663-- Load a plugin and its dependencies
654- function Plugin :load ()
664+ function Plugin :load (opts )
655665 if self .loaded then
656666 return true
657667 end
@@ -667,30 +677,37 @@ function Plugin:load()
667677 self .loaded = true
668678 vim .g .strive_loaded = vim .g .strive_loaded + 1
669679
670- load_opts (self .init_opts )
680+ if self .init_opts then
681+ load_opts (self .init_opts )
682+ end
671683
672684 self :packadd ()
673- self :load_scripts ()
685+ self :load_scripts (opts and opts . script_cb or nil )
674686
675687 self :call_setup ()
676688
677- Iter (self .dependencies ):map (function (d )
678- if not d .loaded then
679- d :load ()
689+ if # self .dependencies > 0 then
690+ for _ , dep in ipairs (self .dependencies ) do
691+ if not dep .loaded then
692+ dep :load ()
693+ end
680694 end
681- end )
682-
683- load_opts (self .config_opts )
695+ end
696+ if self .config_opts then
697+ load_opts (self .config_opts )
698+ end
684699 -- Update status
685700 self .status = STATUS .LOADED
686701
702+ pcall (api .nvim_del_augroup_by_name , ' strive_' .. self .plugin_name )
687703 return true
688704end
689705
690706-- Set up lazy loading on specific events
691707function Plugin :on (events )
692708 self .is_lazy = true
693709 self .events = type (events ) ~= ' table' and { events } or events
710+ local group = api .nvim_create_augroup (' strive_' .. self .plugin_name , { clear = true })
694711
695712 -- Create autocmds for each event
696713 for _ , event in ipairs (self .events ) do
@@ -700,10 +717,7 @@ function Plugin:on(events)
700717 event , pattern = t [1 ], t [2 ]
701718 end
702719 api .nvim_create_autocmd (event , {
703- group = api .nvim_create_augroup (
704- ' strive_' .. self .plugin_name .. ' _' .. event ,
705- { clear = true }
706- ),
720+ group = group ,
707721 pattern = pattern ,
708722 once = true ,
709723 callback = function (args )
@@ -714,12 +728,10 @@ function Plugin:on(events)
714728 local event_data = args .data and vim .deepcopy (args .data ) or {}
715729
716730 -- Schedule the event emission to avoid nesting too deep
717- -- vim.schedule(function()
718731 api .nvim_exec_autocmds (event , {
719732 modeline = false ,
720733 data = event_data ,
721734 })
722- -- end)
723735 end
724736 end ,
725737 })
@@ -733,7 +745,7 @@ function Plugin:ft(filetypes)
733745 self .is_lazy = true
734746 self .filetypes = type (filetypes ) ~= ' table' and { filetypes } or filetypes
735747 api .nvim_create_autocmd (' FileType' , {
736- group = api .nvim_create_augroup (' strive_' .. self .plugin_name .. ' _ft ' , { clear = true }),
748+ group = api .nvim_create_augroup (' strive_' .. self .plugin_name , { clear = true }),
737749 pattern = self .filetypes ,
738750 once = true ,
739751 callback = function (args )
@@ -750,25 +762,33 @@ function Plugin:ft(filetypes)
750762 return self
751763end
752764
753- function Plugin :load_scripts ()
765+ function Plugin :load_scripts (callback )
754766 Async .async (function ()
755767 local plugin_path = self :get_path ()
756768 local plugin_dir = vim .fs .joinpath (plugin_path , ' plugin' )
769+ if not isdir (plugin_dir ) then
770+ return
771+ end
757772
758773 local result = Async .try_await (Async .scandir (plugin_dir ))
759- if not result .success or not result .value or not result . value [ 2 ] then
774+ if not result .success or not result .value then
760775 M .log (' debug' , string.format (' Plugin directory not found: %s' , plugin_dir ))
761776 return
762777 end
763778
764779 while true do
765- local name , type = uv .fs_scandir_next (result .value [ 2 ] )
780+ local name , type = uv .fs_scandir_next (result .value )
766781 if not name then
767782 break
768783 end
769784 if type == ' file' and (name :match (' %.lua$' ) or name :match (' %.vim$' )) then
770785 local file_path = vim .fs .joinpath (plugin_dir , name )
771- vim .cmd (' source ' .. vim .fn .fnameescape (file_path ))
786+ Async .safe_schedule (function ()
787+ vim .cmd (' source ' .. vim .fn .fnameescape (file_path ))
788+ if callback then
789+ callback ()
790+ end
791+ end )
772792 end
773793 end
774794 end )()
@@ -784,16 +804,17 @@ function Plugin:cmd(commands)
784804 pcall (api .nvim_del_user_command , cmd_name )
785805 local args = cmd_args .args ~= ' ' and ' ' .. cmd_args .args or ' '
786806 local bang = cmd_args .bang and ' !' or ' '
787- self :load ()
788- vim .schedule (function ()
789- if vim .fn .exists (' :' .. cmd_name ) == 2 then
790- --- @diagnostic disable-next-line : param-type-mismatch
791- local ok , err = pcall (vim .cmd , cmd_name .. bang .. args )
792- if not ok then
793- vim .notify (string.format (' execute %s wrong: %s' , cmd_name , err ), vim .log .levels .ERROR )
807+ self :load ({
808+ script_cb = function ()
809+ if vim .fn .exists (' :' .. cmd_name ) == 2 then
810+ --- @diagnostic disable-next-line : param-type-mismatch
811+ local ok , err = pcall (vim .cmd , cmd_name .. bang .. args )
812+ if not ok then
813+ vim .notify (string.format (' execute %s wrong: %s' , cmd_name , err ), vim .log .levels .ERROR )
814+ end
794815 end
795- end
796- end )
816+ end ,
817+ } )
797818 end , {
798819 nargs = ' *' ,
799820 bang = true ,
@@ -898,7 +919,7 @@ function Plugin:theme(name)
898919 Async .async (function ()
899920 local installed = Async .await (self :is_installed ())
900921 if installed then
901- vim . schedule (function ()
922+ Async . safe_schedule (function ()
902923 vim .opt .rtp :append (vim .fs .joinpath (START_DIR , self .plugin_name ))
903924 vim .cmd .colorscheme (self .colorscheme )
904925 end )
@@ -974,7 +995,7 @@ function Plugin:install()
974995 lines = vim .split (lines , ' \n ' , { trimempty = true })
975996
976997 if # lines > 0 then
977- vim . schedule (function ()
998+ Async . safe_schedule (function ()
978999 ui :update_entry (self .name , self .status , lines [# lines ])
9791000 end )
9801001 end
@@ -1105,7 +1126,7 @@ function Plugin:update()
11051126 lines = vim .split (lines , ' \n ' , { trimempty = true })
11061127
11071128 if # lines > 0 then
1108- vim . schedule (function ()
1129+ Async . safe_schedule (function ()
11091130 ui :update_entry (self .name , self .status , lines [# lines ])
11101131 end )
11111132 end
@@ -1149,9 +1170,6 @@ function Plugin:install_with_retry()
11491170 local url = (' https://github.com/%s' ):format (self .name )
11501171 local cmd = { ' git' , ' clone' , ' --progress' , url , path }
11511172
1152- -- Ensure parent directory exists
1153- vim .fn .mkdir (vim .fs .dirname (path ), ' p' )
1154-
11551173 -- Use retry with the system command (3 retries with exponential backoff)
11561174 local result = Async .try_await (Async .retry (function ()
11571175 return Async .system (cmd , {
@@ -1163,7 +1181,7 @@ function Plugin:install_with_retry()
11631181 lines = vim .split (lines , ' \n ' , { trimempty = true })
11641182
11651183 if # lines > 0 then
1166- vim . schedule (function ()
1184+ Async . safe_schedule (function ()
11671185 ui :update_entry (self .name , self .status , lines [# lines ])
11681186 end )
11691187 end
@@ -1522,7 +1540,7 @@ local function setup_auto_install()
15221540 -- When using strive in plugin folder
15231541 if vim .v .vim_did_enter == 1 then
15241542 -- UI has already initialized, schedule installation directly
1525- vim . schedule (function ()
1543+ Async . safe_schedule (function ()
15261544 M .log (' debug' , ' UI already initialized, installing plugins now' )
15271545 M .install ()
15281546 end )
@@ -1534,7 +1552,7 @@ local function setup_auto_install()
15341552 api .nvim_create_autocmd (' UIEnter' , {
15351553 group = api .nvim_create_augroup (' strive_auto_install' , { clear = true }),
15361554 callback = function ()
1537- vim . schedule (function ()
1555+ Async . safe_schedule (function ()
15381556 M .log (' debug' , ' UIEnter triggered, installing plugins' )
15391557 M .install ()
15401558 end )
0 commit comments