Skip to content

Commit 5b2a8b1

Browse files
deathbeamTomas Slusny
andauthored
feat(CopilotChat): Improve line separation and prompt update logic, add diff view (#99)
* feat(CopilotChat): Improve line separation and prompt update logic Now submit_code and submit_prompt properly try to find first match from the end so you dont need to cursor over specific blocks. This is also prerequisite for showing current diff as float or in corner somewhere. BREAKING CHANGE: The key mapping for submitting code is now <C-d> instead of <C-y>. Signed-off-by: Tomas Slusny <ts6234@att.com> * Improve the default prompts slightly and fix issue with active selection Signed-off-by: Tomas Slusny <ts6234@att.com> * feat: Add diff display For now with keybinding (default K). Maybe find a way to display it in better place. Signed-off-by: Tomas Slusny <ts6234@att.com> * Adjust keybindings for accept_diff and show_diff --------- Signed-off-by: Tomas Slusny <ts6234@att.com> Co-authored-by: Tomas Slusny <ts6234@att.com>
1 parent 98706ea commit 5b2a8b1

3 files changed

Lines changed: 94 additions & 45 deletions

File tree

lua/CopilotChat/copilot.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ local function generate_request(history, selection, filetype, system_prompt, mod
7070
if selection ~= '' then
7171
-- Insert the active selection before last prompt
7272
table.insert(messages, #messages, {
73-
content = '\nActive selection:\n```' .. selection .. '\n' .. filetype .. '\n```',
73+
content = '\nActive selection:\n```' .. filetype .. '\n' .. selection .. '\n```',
7474
role = 'system',
7575
})
7676
end

lua/CopilotChat/init.lua

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -23,53 +23,88 @@ function CopilotChatFoldExpr(lnum, separator)
2323
return '='
2424
end
2525

26-
local function find_lines_between_separator_at_cursor(bufnr, separator)
27-
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
28-
local cursor = vim.api.nvim_win_get_cursor(0)
29-
local cursor_line = cursor[1]
26+
local function find_lines_between_separator(lines, pattern, at_least_one)
3027
local line_count = #lines
31-
local last_separator_line = 1
32-
local next_separator_line = line_count
33-
local pattern = '^' .. separator .. '%w*$'
28+
local separator_line_start = 1
29+
local separator_line_finish = line_count
30+
local found_one = false
3431

3532
-- Find the last occurrence of the separator
36-
for i, line in ipairs(lines) do
37-
if i > cursor_line and string.find(line, pattern) then
38-
next_separator_line = i - 1
39-
break
40-
end
33+
for i = line_count, 1, -1 do -- Reverse the loop to start from the end
34+
local line = lines[i]
4135
if string.find(line, pattern) then
42-
last_separator_line = i + 1
36+
if i < (separator_line_finish + 1) and (not at_least_one or found_one) then
37+
separator_line_start = i + 1
38+
break -- Exit the loop as soon as the condition is met
39+
end
40+
41+
found_one = true
42+
separator_line_finish = i - 1
4343
end
4444
end
4545

46+
if at_least_one and not found_one then
47+
return {}, 1, 1, 0
48+
end
49+
4650
-- Extract everything between the last and next separator
4751
local result = {}
48-
for i = last_separator_line, next_separator_line do
52+
for i = separator_line_start, separator_line_finish do
4953
table.insert(result, lines[i])
5054
end
5155

52-
return table.concat(result, '\n'), last_separator_line, next_separator_line, line_count
56+
return result, separator_line_start, separator_line_finish, line_count
57+
end
58+
59+
local function show_diff_between_selection_and_copilot()
60+
local selection = state.selection
61+
if not selection or not selection.buffer or not selection.start_row or not selection.end_row then
62+
return
63+
end
64+
65+
local chat_lines = vim.api.nvim_buf_get_lines(state.chat.bufnr, 0, -1, false)
66+
local section_lines = find_lines_between_separator(chat_lines, M.config.separator .. '$', true)
67+
local lines = find_lines_between_separator(section_lines, '^```%w*$', true)
68+
if #lines > 0 then
69+
local diff = tostring(vim.diff(selection.lines, table.concat(lines, '\n'), {}))
70+
if diff and diff ~= '' then
71+
vim.lsp.util.open_floating_preview(vim.split(diff, '\n'), 'diff', {
72+
border = 'single',
73+
title = M.config.name .. ' Diff',
74+
title_pos = 'left',
75+
focusable = false,
76+
focus = false,
77+
relative = 'editor',
78+
row = 0,
79+
col = 0,
80+
width = vim.api.nvim_win_get_width(0) - 3,
81+
})
82+
end
83+
end
5384
end
5485

5586
local function update_prompts(prompt, system_prompt)
5687
local prompts_to_use = M.get_prompts()
88+
local try_again = false
5789
local result = string.gsub(prompt, [[/[%w_]+]], function(match)
58-
match = string.sub(match, 2)
59-
local found = prompts_to_use[match]
60-
90+
local found = prompts_to_use[string.sub(match, 2)]
6191
if found then
6292
if found.kind == 'user' then
63-
return found.prompt
93+
local out = found.prompt
94+
if string.match(out, [[/[%w_]+]]) then
95+
try_again = true
96+
end
97+
return out
6498
elseif found.kind == 'system' then
6599
system_prompt = found.prompt
100+
return ''
66101
end
67102
end
68103

69-
return ''
104+
return match
70105
end)
71106

72-
if string.match(result, [[/[%w_]+]]) then
107+
if try_again then
73108
return update_prompts(result, system_prompt)
74109
end
75110

@@ -204,9 +239,10 @@ function M.open(config)
204239

205240
if config.mappings.submit_prompt then
206241
vim.keymap.set('n', config.mappings.submit_prompt, function()
207-
local input, start_line, end_line, line_count =
208-
find_lines_between_separator_at_cursor(state.chat.bufnr, config.separator)
209-
input = vim.trim(input)
242+
local chat_lines = vim.api.nvim_buf_get_lines(state.chat.bufnr, 0, -1, false)
243+
local lines, start_line, end_line, line_count =
244+
find_lines_between_separator(chat_lines, config.separator .. '$')
245+
local input = vim.trim(table.concat(lines, '\n'))
210246
if input ~= '' then
211247
-- If we are entering the input at the end, replace it
212248
if line_count == end_line then
@@ -217,8 +253,14 @@ function M.open(config)
217253
end, { buffer = state.chat.bufnr })
218254
end
219255

220-
if config.mappings.submit_code then
221-
vim.keymap.set('n', config.mappings.submit_code, function()
256+
if config.mappings.show_diff then
257+
vim.keymap.set('n', config.mappings.show_diff, show_diff_between_selection_and_copilot, {
258+
buffer = state.chat.bufnr,
259+
})
260+
end
261+
262+
if config.mappings.accept_diff then
263+
vim.keymap.set('n', config.mappings.accept_diff, function()
222264
if
223265
not state.selection
224266
or not state.selection.buffer
@@ -229,15 +271,18 @@ function M.open(config)
229271
return
230272
end
231273

232-
local input = find_lines_between_separator_at_cursor(state.chat.bufnr, '```')
233-
if input ~= '' then
274+
local chat_lines = vim.api.nvim_buf_get_lines(state.chat.bufnr, 0, -1, false)
275+
local section_lines =
276+
find_lines_between_separator(chat_lines, config.separator .. '$', true)
277+
local lines = find_lines_between_separator(section_lines, '^```%w*$', true)
278+
if #lines > 0 then
234279
vim.api.nvim_buf_set_text(
235280
state.selection.buffer,
236281
state.selection.start_row - 1,
237282
state.selection.start_col - 1,
238283
state.selection.end_row - 1,
239284
state.selection.end_col,
240-
vim.split(input, '\n')
285+
lines
241286
)
242287
end
243288
end, { buffer = state.chat.bufnr })
@@ -261,6 +306,7 @@ function M.open(config)
261306
elseif layout == 'horizontal' then
262307
win_opts.vertical = false
263308
elseif layout == 'float' then
309+
win_opts.zindex = 1
264310
win_opts.relative = config.window.relative
265311
win_opts.border = config.window.border
266312
win_opts.title = config.window.title
@@ -466,7 +512,8 @@ M.config = {
466512
reset = '<C-l>',
467513
complete = '<Tab>',
468514
submit_prompt = '<CR>',
469-
submit_code = '<C-y>',
515+
accept_diff = '<C-y>',
516+
show_diff = '<C-d>',
470517
},
471518
}
472519
--- Set up the plugin

lua/CopilotChat/prompts.lua

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,15 +75,25 @@ Preserve user's code comment blocks, do not exclude them when refactoring code.
7575

7676
M.COPILOT_DEVELOPER = M.COPILOT_INSTRUCTIONS
7777
.. [[
78-
You're a 10x senior developer that is an expert in programming.
79-
Your job is to change the user's code according to their needs.
80-
Your job is only to change / edit the code.
81-
Your code output should keep the same level of indentation as the user's code.
82-
You MUST add whitespace in the beginning of each line as needed to match the user's code.
78+
You also specialize in being a highly skilled code generator. Given a description of what to do you can refactor, modify or enhance existing code. Your task is help the Developer change their code according to their needs. Pay especially close attention to the selection context.
8379
80+
Additional Rules:
81+
If context is provided, try to match the style of the provided code as best as possible
82+
Generated code is readable and properly indented
83+
Markdown blocks are used to denote code
84+
Preserve user's code comment blocks, do not exclude them when refactoring code.
85+
86+
]]
87+
88+
M.USER_EXPLAIN = 'Write a explanation for the code above as paragraphs of text.'
89+
M.USER_TESTS = 'Write a set of detailed unit test functions for the code above.'
90+
M.USER_FIX = 'There is a problem in this code. Rewrite the code to show it with the bug fixed.'
91+
M.USER_DOCS = [[Write documentation for the selected code.
92+
The reply should be a codeblock containing the original code with the documentation added as comments.
93+
Use the most appropriate documentation style for the programming language used (e.g. JSDoc for JavaScript, docstrings for Python etc.)
8494
]]
8595

86-
M.COPILOT_WORKSPACE =
96+
COPILOT_WORKSPACE =
8797
[[You are a software engineer with expert knowledge of the codebase the user has open in their workspace.
8898
When asked for your name, you must respond with "GitHub Copilot".
8999
Follow the user's requirements carefully & to the letter.
@@ -144,14 +154,6 @@ Response:
144154
To read a file, you can use a [`FileReader`](src/fs/fileReader.ts) class from [src/fs/fileReader.ts](src/fs/fileReader.ts).
145155
]]
146156

147-
M.USER_EXPLAIN = 'Write a explanation for the code above as paragraphs of text.'
148-
M.USER_TESTS = 'Write a set of detailed unit test functions for the code above.'
149-
M.USER_FIX = 'There is a problem in this code. Rewrite the code to show it with the bug fixed.'
150-
M.USER_DOCS = [[Write documentation for the selected code.
151-
The reply should be a codeblock containing the original code with the documentation added as comments.
152-
Use the most appropriate documentation style for the programming language used (e.g. JSDoc for JavaScript, docstrings for Python etc.)
153-
]]
154-
155157
EMBEDDING_KEYWORDS =
156158
[[You are a coding assistant who help the user answer questions about code in their workspace by providing a list of relevant keywords they can search for to answer the question.
157159
The user will provide you with potentially relevant information from the workspace. This information may be incomplete.

0 commit comments

Comments
 (0)