@@ -23,6 +23,7 @@ local plugin_name = 'CopilotChat.nvim'
2323--- @field diff CopilotChat.Overlay ?
2424--- @field system_prompt CopilotChat.Overlay ?
2525--- @field user_selection CopilotChat.Overlay ?
26+ --- @field help CopilotChat.Overlay ?
2627local state = {
2728 copilot = nil ,
2829 chat = nil ,
@@ -40,6 +41,7 @@ local state = {
4041 diff = nil ,
4142 system_prompt = nil ,
4243 user_selection = nil ,
44+ help = nil ,
4345}
4446
4547local function blend_color_with_neovim_bg (color_name , blend )
213215
214216--- Get the info for a key.
215217--- @param name string
216- --- @param key CopilotChat.config.mapping
218+ --- @param key CopilotChat.config.mapping ?
217219--- @return string
218220local function key_to_info (name , key )
221+ if not key then
222+ return ' '
223+ end
224+
219225 local out = ' '
220226 if key .normal and key .normal ~= ' ' then
221227 out = out .. " '" .. key .normal .. " ' in normal mode"
@@ -625,14 +631,8 @@ function M.setup(config)
625631 { link = ' @punctuation.special.markdown' , default = true }
626632 )
627633
628- local overlay_help = ' '
629- if M .config .mappings .close then
630- overlay_help = key_to_info (' close' , M .config .mappings .close )
631- end
632- local diff_help = ' '
633- if M .config .mappings .accept_diff then
634- diff_help = key_to_info (' accept_diff' , M .config .mappings .accept_diff )
635- end
634+ local overlay_help = key_to_info (' close' , M .config .mappings .close )
635+ local diff_help = key_to_info (' accept_diff' , M .config .mappings .accept_diff )
636636 if overlay_help ~= ' ' and diff_help ~= ' ' then
637637 diff_help = diff_help .. ' \n ' .. overlay_help
638638 end
@@ -688,160 +688,174 @@ function M.setup(config)
688688 end )
689689 end )
690690
691- local chat_help = ' '
692- if M .config .show_help then
693- local chat_keys = vim .tbl_keys (M .config .mappings )
694- table.sort (chat_keys , function (a , b )
695- a = M .config .mappings [a ]
696- a = a .normal or a .insert
697- b = M .config .mappings [b ]
698- b = b .normal or b .insert
699- return a < b
700- end )
701-
702- for _ , name in ipairs (chat_keys ) do
703- local key = M .config .mappings [name ]
704- chat_help = chat_help .. key_to_info (name , key ) .. ' \n '
705- end
691+ if state .help then
692+ state .help :delete ()
706693 end
694+ state .help = Overlay (' copilot-help' , hl_ns , overlay_help , function (bufnr )
695+ map_key (M .config .mappings .close , bufnr , function ()
696+ state .help :restore (state .chat .winnr , state .chat .bufnr )
697+ end )
698+ end )
707699
708700 if state .chat then
709701 state .chat :close (state .source and state .source .bufnr or nil )
710702 state .chat :delete ()
711703 end
712- state .chat = Chat (chat_help , M .config .auto_insert_mode , function (bufnr )
713- map_key (M .config .mappings .complete , bufnr , complete )
714- map_key (M .config .mappings .reset , bufnr , M .reset )
715- map_key (M .config .mappings .close , bufnr , M .close )
716-
717- map_key (M .config .mappings .submit_prompt , bufnr , function ()
718- local chat_lines = vim .api .nvim_buf_get_lines (bufnr , 0 , - 1 , false )
719- local lines , start_line , end_line , line_count =
720- find_lines_between_separator (chat_lines , M .config .separator .. ' $' )
721- local input = vim .trim (table.concat (lines , ' \n ' ))
722- if input ~= ' ' then
723- -- If we are entering the input at the end, replace it
724- if line_count == end_line then
725- vim .api .nvim_buf_set_lines (bufnr , start_line , end_line , false , { ' ' })
704+ state .chat = Chat (
705+ M .config .show_help and key_to_info (' show_help' , M .config .mappings .show_help ),
706+ M .config .auto_insert_mode ,
707+ function (bufnr )
708+ map_key (M .config .mappings .show_help , bufnr , function ()
709+ local chat_help = ' '
710+ local chat_keys = vim .tbl_keys (M .config .mappings )
711+ table.sort (chat_keys , function (a , b )
712+ a = M .config .mappings [a ]
713+ a = a .normal or a .insert
714+ b = M .config .mappings [b ]
715+ b = b .normal or b .insert
716+ return a < b
717+ end )
718+ for _ , name in ipairs (chat_keys ) do
719+ local key = M .config .mappings [name ]
720+ chat_help = chat_help .. key_to_info (name , key ) .. ' \n '
726721 end
727- M .ask (input , state .config , state .source )
728- end
729- end )
730-
731- map_key (M .config .mappings .accept_diff , bufnr , function ()
732- local selection = get_selection ()
733- if not selection .start_row or not selection .end_row then
734- return
735- end
736-
737- local chat_lines = vim .api .nvim_buf_get_lines (bufnr , 0 , - 1 , false )
738- local section_lines =
739- find_lines_between_separator (chat_lines , M .config .separator .. ' $' , true )
740- local lines = find_lines_between_separator (section_lines , ' ^```%w*$' , true )
741- if # lines > 0 then
742- vim .api .nvim_buf_set_text (
743- state .source .bufnr ,
744- selection .start_row - 1 ,
745- selection .start_col - 1 ,
746- selection .end_row - 1 ,
747- selection .end_col ,
748- lines
749- )
750- end
751- end )
722+ chat_help = chat_help .. M .config .separator .. ' \n '
723+ state .help :show (chat_help , ' markdown' , ' markdown' , state .chat .winnr )
724+ end )
752725
753- map_key (M .config .mappings .yank_diff , bufnr , function ()
754- local selection = get_selection ()
755- if not selection .start_row or not selection .end_row then
756- return
757- end
726+ map_key (M .config .mappings .complete , bufnr , complete )
727+ map_key (M .config .mappings .reset , bufnr , M .reset )
728+ map_key (M .config .mappings .close , bufnr , M .close )
729+
730+ map_key (M .config .mappings .submit_prompt , bufnr , function ()
731+ local chat_lines = vim .api .nvim_buf_get_lines (bufnr , 0 , - 1 , false )
732+ local lines , start_line , end_line , line_count =
733+ find_lines_between_separator (chat_lines , M .config .separator .. ' $' )
734+ local input = vim .trim (table.concat (lines , ' \n ' ))
735+ if input ~= ' ' then
736+ -- If we are entering the input at the end, replace it
737+ if line_count == end_line then
738+ vim .api .nvim_buf_set_lines (bufnr , start_line , end_line , false , { ' ' })
739+ end
740+ M .ask (input , state .config , state .source )
741+ end
742+ end )
758743
759- local chat_lines = vim .api .nvim_buf_get_lines (bufnr , 0 , - 1 , false )
760- local section_lines =
761- find_lines_between_separator (chat_lines , M .config .separator .. ' $' , true )
762- local lines = find_lines_between_separator (section_lines , ' ^```%w*$' , true )
763- if # lines > 0 then
764- local content = table.concat (lines , ' \n ' )
765- vim .fn .setreg (M .config .mappings .yank_diff .register , content )
766- end
767- end )
744+ map_key (M .config .mappings .accept_diff , bufnr , function ()
745+ local selection = get_selection ()
746+ if not selection .start_row or not selection .end_row then
747+ return
748+ end
768749
769- map_key (M .config .mappings .show_diff , bufnr , function ()
770- local selection = get_selection ()
771- if not selection or not selection .start_row or not selection .end_row then
772- return
773- end
750+ local chat_lines = vim .api .nvim_buf_get_lines (bufnr , 0 , - 1 , false )
751+ local section_lines =
752+ find_lines_between_separator (chat_lines , M .config .separator .. ' $' , true )
753+ local lines = find_lines_between_separator (section_lines , ' ^```%w*$' , true )
754+ if # lines > 0 then
755+ vim .api .nvim_buf_set_text (
756+ state .source .bufnr ,
757+ selection .start_row - 1 ,
758+ selection .start_col - 1 ,
759+ selection .end_row - 1 ,
760+ selection .end_col ,
761+ lines
762+ )
763+ end
764+ end )
774765
775- local chat_lines = vim .api .nvim_buf_get_lines (state .chat .bufnr , 0 , - 1 , false )
776- local section_lines =
777- find_lines_between_separator (chat_lines , M .config .separator .. ' $' , true )
778- local lines =
779- table.concat (find_lines_between_separator (section_lines , ' ^```%w*$' , true ), ' \n ' )
780- if vim .trim (lines ) ~= ' ' then
781- state .last_code_output = lines
766+ map_key (M .config .mappings .yank_diff , bufnr , function ()
767+ local selection = get_selection ()
768+ if not selection .start_row or not selection .end_row then
769+ return
770+ end
782771
783- local filetype = selection .filetype or vim .bo [state .source .bufnr ].filetype
772+ local chat_lines = vim .api .nvim_buf_get_lines (bufnr , 0 , - 1 , false )
773+ local section_lines =
774+ find_lines_between_separator (chat_lines , M .config .separator .. ' $' , true )
775+ local lines = find_lines_between_separator (section_lines , ' ^```%w*$' , true )
776+ if # lines > 0 then
777+ local content = table.concat (lines , ' \n ' )
778+ vim .fn .setreg (M .config .mappings .yank_diff .register , content )
779+ end
780+ end )
784781
785- local diff = tostring (vim .diff (selection .lines , lines , {
786- result_type = ' unified' ,
787- ignore_blank_lines = true ,
788- ignore_whitespace = true ,
789- ignore_whitespace_change = true ,
790- ignore_whitespace_change_at_eol = true ,
791- ignore_cr_at_eol = true ,
792- algorithm = ' myers' ,
793- ctxlen = # selection .lines ,
794- }))
795-
796- state .diff :show (diff , filetype , ' diff' , state .chat .winnr )
797- end
798- end )
782+ map_key (M .config .mappings .show_diff , bufnr , function ()
783+ local selection = get_selection ()
784+ if not selection or not selection .start_row or not selection .end_row then
785+ return
786+ end
799787
800- map_key (M .config .mappings .show_system_prompt , bufnr , function ()
801- local prompt = state .last_system_prompt or M .config .system_prompt
802- if not prompt then
803- return
804- end
788+ local chat_lines = vim .api .nvim_buf_get_lines (state .chat .bufnr , 0 , - 1 , false )
789+ local section_lines =
790+ find_lines_between_separator (chat_lines , M .config .separator .. ' $' , true )
791+ local lines =
792+ table.concat (find_lines_between_separator (section_lines , ' ^```%w*$' , true ), ' \n ' )
793+ if vim .trim (lines ) ~= ' ' then
794+ state .last_code_output = lines
795+
796+ local filetype = selection .filetype or vim .bo [state .source .bufnr ].filetype
797+
798+ local diff = tostring (vim .diff (selection .lines , lines , {
799+ result_type = ' unified' ,
800+ ignore_blank_lines = true ,
801+ ignore_whitespace = true ,
802+ ignore_whitespace_change = true ,
803+ ignore_whitespace_change_at_eol = true ,
804+ ignore_cr_at_eol = true ,
805+ algorithm = ' myers' ,
806+ ctxlen = # selection .lines ,
807+ }))
808+
809+ state .diff :show (diff , filetype , ' diff' , state .chat .winnr )
810+ end
811+ end )
805812
806- state .system_prompt :show (prompt , ' markdown' , ' markdown' , state .chat .winnr )
807- end )
813+ map_key (M .config .mappings .show_system_prompt , bufnr , function ()
814+ local prompt = state .last_system_prompt or M .config .system_prompt
815+ if not prompt then
816+ return
817+ end
808818
809- map_key (M .config .mappings .show_user_selection , bufnr , function ()
810- local selection = get_selection ()
811- if not selection .start_row or not selection .end_row then
812- return
813- end
819+ state .system_prompt :show (prompt , ' markdown' , ' markdown' , state .chat .winnr )
820+ end )
814821
815- local filetype = selection .filetype or vim .bo [state .source .bufnr ].filetype
816- local lines = selection .lines
817- if vim .trim (lines ) ~= ' ' then
818- state .user_selection :show (lines , filetype , filetype , state .chat .winnr )
819- end
820- end )
822+ map_key (M .config .mappings .show_user_selection , bufnr , function ()
823+ local selection = get_selection ()
824+ if not selection .start_row or not selection .end_row then
825+ return
826+ end
821827
822- vim .api .nvim_create_autocmd ({ ' BufEnter' , ' BufLeave' }, {
823- buffer = state .chat .bufnr ,
824- callback = function (ev )
825- if state .config .highlight_selection then
826- M .highlight_selection (ev .event == ' BufLeave' )
828+ local filetype = selection .filetype or vim .bo [state .source .bufnr ].filetype
829+ local lines = selection .lines
830+ if vim .trim (lines ) ~= ' ' then
831+ state .user_selection :show (lines , filetype , filetype , state .chat .winnr )
827832 end
828- end ,
829- })
833+ end )
830834
831- if M .config .insert_at_end then
832- vim .api .nvim_create_autocmd ({ ' InsertEnter' }, {
835+ vim .api .nvim_create_autocmd ({ ' BufEnter' , ' BufLeave' }, {
833836 buffer = state .chat .bufnr ,
834- callback = function ()
835- vim . cmd ( ' normal! 0 ' )
836- vim . cmd ( ' normal! G$ ' )
837- vim . v . char = ' x '
837+ callback = function (ev )
838+ if state . config . highlight_selection then
839+ M . highlight_selection ( ev . event == ' BufLeave ' )
840+ end
838841 end ,
839842 })
840- end
841843
842- append (M .config .question_header .. M .config .separator .. ' \n\n ' )
843- state .chat :finish ()
844- end )
844+ if M .config .insert_at_end then
845+ vim .api .nvim_create_autocmd ({ ' InsertEnter' }, {
846+ buffer = state .chat .bufnr ,
847+ callback = function ()
848+ vim .cmd (' normal! 0' )
849+ vim .cmd (' normal! G$' )
850+ vim .v .char = ' x'
851+ end ,
852+ })
853+ end
854+
855+ append (M .config .question_header .. M .config .separator .. ' \n\n ' )
856+ state .chat :finish ()
857+ end
858+ )
845859
846860 for name , prompt in pairs (M .prompts (true )) do
847861 vim .api .nvim_create_user_command (' CopilotChat' .. name , function (args )
0 commit comments