@@ -671,6 +671,153 @@ local function get_augroup(bufnr)
671671return string.format (' nvim.lsp.completion_%d' ,bufnr )
672672end
673673
674+ --- @param winid integer
675+ --- @param bufnr integer
676+ --- @param ft ? string
677+ local function update_popup_window (winid ,bufnr ,ft )
678+ if winid and api .nvim_win_is_valid (winid )and bufnr and api .nvim_buf_is_valid (bufnr )then
679+ vim .wo [winid ].conceallevel = 2
680+ if ft then
681+ local lang = vim .treesitter .language .get_lang (ft )
682+ if lang then
683+ local ok = pcall (vim .treesitter .get_parser ,bufnr ,lang )
684+ if ok then
685+ vim .treesitter .start (bufnr ,lang )
686+ end
687+ else
688+ vim .wo [winid ].eventignorewin = ' OptionSet'
689+ vim .bo [bufnr ].filetype = ft
690+ end
691+ end
692+ local all = api .nvim_win_text_height (winid , {}).all
693+ api .nvim_win_set_height (winid ,all )
694+ end
695+ end
696+
697+ --- @return function
698+ local function cmpitem_resolve ()
699+ local doc_rtt_ms = 100
700+ local doc_compute_new_average = exp_avg (10 ,5 )
701+
702+ local infoContext = {
703+ timer = nil ,-- [[uv_time_t]]
704+ request_ids = {},--- @type table<integer , integer>
705+ bufnr = nil ,
706+ word = nil ,
707+ last_request_time = nil ,
708+ }
709+
710+ local function next_doc_debounce ()
711+ if not infoContext .last_request_time then
712+ return doc_rtt_ms
713+ end
714+
715+ local ms_since_request = (vim .uv .hrtime ()- infoContext .last_request_time )* ns_to_ms
716+ return math.max ((ms_since_request - doc_rtt_ms )* - 1 ,0 )
717+ end
718+
719+ local function cancel_pending ()
720+ for client_id ,request_id in pairs (infoContext .request_ids )do
721+ local client = lsp .get_client_by_id (client_id )
722+ if client then
723+ client :cancel_request (request_id )
724+ end
725+ end
726+ infoContext .request_ids = {}
727+ end
728+
729+ local function cleanup ()
730+ if infoContext .timer and not infoContext .timer :is_closing ()then
731+ infoContext .timer :stop ()
732+ infoContext .timer :close ()
733+ infoContext .timer = nil
734+ end
735+ cancel_pending ()
736+ end
737+
738+ --- @return boolean , table
739+ local function is_valid ()
740+ local cmp_info = vim .fn .complete_info ({' selected' ,' completed' })
741+ return api .nvim_buf_is_valid (infoContext .bufnr )
742+ and api .nvim_get_current_buf ()== infoContext .bufnr
743+ and vim .startswith (api .nvim_get_mode ().mode ,' i' )
744+ and tonumber (vim .fn .pumvisible ())== 1
745+ and (vim .tbl_get (cmp_info ,' completed' ,' word' )or ' ' )== infoContext .word ,
746+ cmp_info
747+ end
748+
749+ return function (bufnr ,param ,select_word )
750+ cleanup ()
751+
752+ infoContext .bufnr = bufnr
753+ infoContext .word = select_word
754+ local debounce_time = next_doc_debounce ()
755+
756+ infoContext .timer = assert (vim .uv .new_timer ())
757+ infoContext .timer :start (
758+ debounce_time ,
759+ 0 ,
760+ vim .schedule_wrap (function ()
761+ local valid ,cmp_info = is_valid ()
762+ if not valid then
763+ cleanup ()
764+ return
765+ end
766+
767+ cancel_pending ()
768+ local client_id = vim .tbl_get (cmp_info .completed ,' user_data' ,' nvim' ,' lsp' ,' client_id' )
769+ local client = client_id and lsp .get_client_by_id (client_id )
770+ if not client then
771+ return
772+ end
773+
774+ local start_time = vim .uv .hrtime ()
775+ infoContext .last_request_time = start_time
776+
777+ local ok ,request_id = client :request (
778+ vim .lsp .protocol .Methods .completionItem_resolve ,
779+ param ,
780+ function (err ,result )
781+ local end_time = vim .uv .hrtime ()
782+ local response_time = (end_time - start_time )* ns_to_ms
783+ doc_rtt_ms = doc_compute_new_average (response_time )
784+
785+ if err or not result or next (result )== nil then
786+ if err then
787+ vim .notify (err .message ,vim .log .levels .WARN )
788+ end
789+ return
790+ end
791+
792+ valid ,cmp_info = is_valid ()
793+ if not valid then
794+ return
795+ end
796+
797+ local value = vim .tbl_get (result ,' documentation' ,' value' )
798+ if not value then
799+ return
800+ end
801+
802+ local windata = api .nvim__complete_set (cmp_info .selected , {
803+ info = value ,
804+ })
805+ local kind = vim .tbl_get (result ,' documentation' ,' kind' )
806+ update_popup_window (windata .winid ,windata .bufnr ,kind )
807+ end ,
808+ bufnr
809+ )
810+
811+ if ok and request_id then
812+ infoContext .request_ids [client .id ]= request_id
813+ end
814+ end )
815+ )
816+ end
817+ end
818+
819+ local debounce_info_request = cmpitem_resolve ()
820+
674821--- @class vim.lsp.completion.BufferOpts
675822--- @field autotrigger ?boolean Default : false When true , completion triggers automatically based on the server ' s `triggerCharacters`.
676823--- @field convert ?fun ( item : lsp.CompletionItem ): table Transforms an LSP CompletionItem to | complete-items | .
@@ -706,6 +853,33 @@ local function enable_completions(client_id, bufnr, opts)
706853end
707854end ,
708855 })
856+
857+ if vim .o .completeopt :find (' popup' )then
858+ api .nvim_create_autocmd (' completechanged' , {
859+ group = group ,
860+ buffer = bufnr ,
861+ callback = function (args )
862+ local completed_item = api .nvim_get_vvar (' event' ).completed_item or {}
863+ if (completed_item .info or ' ' )~= ' ' then
864+ local data = vim .fn .complete_info ({' selected' })
865+ update_popup_window (data .preview_winid ,data .preview_bufnr )
866+ return
867+ end
868+
869+ local _client_id = vim .tbl_get (completed_item ,' user_data' ,' nvim' ,' lsp' ,' client_id' )
870+ if not lsp .get_client_by_id (client_id )then
871+ return
872+ end
873+
874+ local param = vim .tbl_get (completed_item ,' user_data' ,' nvim' ,' lsp' ,' completion_item' )
875+ if param then
876+ debounce_info_request (args .buf ,param ,completed_item .word )
877+ end
878+ end ,
879+ desc = ' Request and display LSP completion item documentation via completionItem/resolve' ,
880+ })
881+ end
882+
709883if opts .autotrigger then
710884api .nvim_create_autocmd (' InsertCharPre' , {
711885group = group ,