@@ -664,7 +664,6 @@ describe('opencode LSP completion', function()
664664
665665 describe (' CompleteDonePre autocmd' , function ()
666666 local completion
667- local augroup_id
668667
669668 before_each (function ()
670669 package.loaded [' opencode.ui.completion' ] = nil
@@ -674,69 +673,38 @@ describe('opencode LSP completion', function()
674673 package.loaded [' opencode.lsp.opencode_ls' ] = nil
675674 ls = require (' opencode.lsp.opencode_ls' )
676675
677- -- Use a scratch buffer so CompleteDonePre autocmds don't accumulate on buffer 0
678- augroup_id = vim .api .nvim_create_augroup (' test_completedone' , { clear = true })
676+ ls .start (0 )
679677 end )
680678
681679 after_each (function ()
682- -- Clear the autocmds registered by ls.start to avoid cross-test pollution
683680 vim .api .nvim_clear_autocmds ({ event = ' CompleteDonePre' , buffer = 0 })
684- vim .api .nvim_del_augroup_by_id (augroup_id )
685681 end )
686682
687- -- Fire CompleteDonePre with a nested user_data table.
688- -- vim.v.completed_item only accepts a dict value, so we build it via nvim_input
689- -- simulation: set via a temporary vimscript assignment.
690- local function fire_complete_done_pre_with_data (user_data )
691- -- We can't directly assign a nested Lua table to vim.v.completed_item because
692- -- Neovim validates the type. Instead we invoke the autocmd callback directly
693- -- by temporarily patching vim.v.completed_item through nvim_exec.
694- vim .api .nvim_exec2 (' let v:completed_item = {}' , { output = false })
695- -- Trigger the autocmd so the registered callback reads vim.v.completed_item.
696- -- To pass user_data we call the callback stored in the module directly.
697- local autocmds = vim .api .nvim_get_autocmds ({ event = ' CompleteDonePre' , buffer = 0 })
698- for _ , au in ipairs (autocmds ) do
699- if au .callback then
700- -- Temporarily make completed_item look like it has user_data by monkeypatching
701- local orig = vim .v .completed_item
702- -- We simulate the callback environment
703- local saved = vim .v
704- -- Instead of fighting vim.v, call the callback with a fake environment:
705- -- patch the module's read of vim.v.completed_item
706- local real_completed_item = vim .v .completed_item
707- -- We patch getters at the module level isn't possible, so we call the
708- -- callback directly after storing data into a local and overriding access.
709- au .callback ()
710- end
711- end
712- end
713-
714- -- Invoke the CompleteDonePre callback directly, bypassing vim.v assignment restrictions.
715- -- We reach into the registered autocmd and call its Lua callback after patching
716- -- vim.v.completed_item to the simplest valid value (empty dict), then override the
717- -- module's behavior by injecting the item through the module's internal state.
718- local function invoke_autocmd_callback ()
719- local autocmds = vim .api .nvim_get_autocmds ({ event = ' CompleteDonePre' , buffer = 0 })
720- for _ , au in ipairs (autocmds ) do
721- if au .callback then
722- au .callback ()
723- end
724- end
683+ local function fire_autocmd (user_data )
684+ vim .v .completed_item = { user_data = user_data }
685+ vim .api .nvim_exec_autocmds (' CompleteDonePre' , { buffer = 0 })
725686 end
726687
727688 it (' sets _completion_done_handled to true when fired' , function ()
728- ls .start (0 )
729689 ls ._completion_done_handled = false
730690
731- -- Fire the autocmd with an empty completed_item (no user_data)
732- vim .api .nvim_exec_autocmds (' CompleteDonePre' , { buffer = 0 })
691+ fire_autocmd ({
692+ nvim = {
693+ lsp = {
694+ completion_item = {
695+ data = { _opencode_item = { source_name = ' test' , label = ' test' , data = {} } },
696+ },
697+ },
698+ },
699+ })
733700
734701 assert .is_true (ls ._completion_done_handled )
735702 end )
736703
737704 it (' calls on_completion_done via nvim lsp user_data path' , function ()
738705 local on_complete_called = false
739706 local received_item = nil
707+ local original_item = { label = ' AutoItem' , source_name = ' autocmd_source' , data = {} }
740708
741709 completion .register_source ({
742710 name = ' autocmd_source' ,
@@ -748,28 +716,15 @@ describe('opencode LSP completion', function()
748716 end ,
749717 })
750718
751- -- Directly invoke the CompleteDonePre logic by calling the module's internal
752- -- handler with simulated completed_item data, since vim.v.completed_item
753- -- cannot hold a nested Lua table. We test the handler function directly.
754- local original_item = { label = ' AutoItem' , source_name = ' autocmd_source' , data = {} }
755-
756- -- Simulate what the autocmd callback does when user_data is present:
757- -- (mirrors the logic in opencode_ls.lua lines 232-238)
758- local fake_user_data = {
719+ fire_autocmd ({
759720 nvim = {
760721 lsp = {
761722 completion_item = {
762723 data = { _opencode_item = original_item },
763724 },
764725 },
765726 },
766- }
767- local data = vim .tbl_get (fake_user_data , ' nvim' , ' lsp' , ' completion_item' , ' data' )
768- or vim .tbl_get (fake_user_data , ' lsp' , ' item' , ' data' )
769- local item = data and data ._opencode_item
770- if item then
771- completion .on_completion_done (item )
772- end
727+ })
773728
774729 assert .is_true (on_complete_called )
775730 assert .are .same (original_item , received_item )
@@ -778,6 +733,7 @@ describe('opencode LSP completion', function()
778733 it (' calls on_completion_done via lsp.item user_data path' , function ()
779734 local on_complete_called = false
780735 local received_item = nil
736+ local original_item = { label = ' AutoItem2' , source_name = ' autocmd_source2' , data = {} }
781737
782738 completion .register_source ({
783739 name = ' autocmd_source2' ,
@@ -789,50 +745,33 @@ describe('opencode LSP completion', function()
789745 end ,
790746 })
791747
792- local original_item = { label = ' AutoItem2' , source_name = ' autocmd_source2' , data = {} }
793-
794- -- Simulate the autocmd handler using the lsp.item path
795- local fake_user_data = {
748+ fire_autocmd ({
796749 lsp = {
797750 item = {
798751 data = { _opencode_item = original_item },
799752 },
800753 },
801- }
802- local data = vim .tbl_get (fake_user_data , ' nvim' , ' lsp' , ' completion_item' , ' data' )
803- or vim .tbl_get (fake_user_data , ' lsp' , ' item' , ' data' )
804- local item = data and data ._opencode_item
805- if item then
806- completion .on_completion_done (item )
807- end
754+ })
808755
809756 assert .is_true (on_complete_called )
810757 assert .are .same (original_item , received_item )
811758 end )
812759
813760 it (' does not error when user_data has no _opencode_item' , function ()
814761 assert .has_no .errors (function ()
815- local fake_user_data = { nvim = { lsp = { completion_item = { data = {} } } } }
816- local data = vim .tbl_get (fake_user_data , ' nvim' , ' lsp' , ' completion_item' , ' data' )
817- or vim .tbl_get (fake_user_data , ' lsp' , ' item' , ' data' )
818- local item = data and data ._opencode_item
819- if item then
820- completion .on_completion_done (item )
821- end
762+ fire_autocmd ({ nvim = { lsp = { completion_item = { data = {} } } } })
822763 end )
823764 end )
824765
825766 it (' does not error when completed_item has no user_data' , function ()
826- ls .start (0 )
827-
828767 assert .has_no .errors (function ()
829- -- Fire the autocmd; vim.v.completed_item defaults to {} with no user_data key
830768 vim .api .nvim_exec_autocmds (' CompleteDonePre' , { buffer = 0 })
831769 end )
832770 end )
833771
834772 it (' prevents executeCommand from firing on_completion_done a second time' , function ()
835773 local call_count = 0
774+ local original_item = { label = ' DedupItem' , source_name = ' dedup_autocmd' , data = {} }
836775
837776 completion .register_source ({
838777 name = ' dedup_autocmd' ,
@@ -843,17 +782,21 @@ describe('opencode LSP completion', function()
843782 end ,
844783 })
845784
846- -- Simulate CompleteDonePre path: set the flag and call on_completion_done once
847- local original_item = { label = ' DedupItem' , source_name = ' dedup_autocmd' , data = {} }
848- ls ._completion_done_handled = false
849- ls ._completion_done_handled = true
850- completion .on_completion_done (original_item )
785+ -- Fire CompleteDonePre; this sets _completion_done_handled and calls on_complete once
786+ fire_autocmd ({
787+ nvim = {
788+ lsp = {
789+ completion_item = {
790+ data = { _opencode_item = original_item },
791+ },
792+ },
793+ },
794+ })
851795
852796 assert .are .equal (1 , call_count )
853797
854- -- Simulate the completion engine then sending workspace/executeCommand
855- -- Because _completion_done_handled is true, it should skip the second call
856- ls .start (0 )
798+ -- Simulate the completion engine then sending workspace/executeCommand.
799+ -- Because _completion_done_handled is true, it should skip the second call.
857800 local config_obj = ls .create_config ()
858801 local server = config_obj .cmd ({}, {})
859802 server .request (' workspace/executeCommand' , {
0 commit comments