Skip to content

Commit 4f2c9ae

Browse files
committed
Add support for #workspace (workspace file map) context
Support workspace map context. This context will simply include all related filenames to the prompt in chat context. Useful for searching where something might be (but limited to only searching on filenames for now). Signed-off-by: Tomas Slusny <[email protected]>
1 parent f83f045 commit 4f2c9ae

File tree

5 files changed

+96
-41
lines changed

5 files changed

+96
-41
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ Supported contexts are:
154154

155155
- `buffers` - Includes all open buffers in chat context
156156
- `buffer` - Includes only the current buffer in chat context
157+
- `files` - Includes all non-hidden filenames in the current workspace in chat context
157158

158159
### API
159160

@@ -237,7 +238,7 @@ Also see [here](/lua/CopilotChat/config.lua):
237238
system_prompt = prompts.COPILOT_INSTRUCTIONS, -- System prompt to use
238239
model = 'gpt-4o', -- Default model to use, see ':CopilotChatModels' for available models
239240
agent = 'copilot', -- Default agent to use, see ':CopilotChatAgents' for available agents (can be specified manually in prompt via @).
240-
context = nil, -- Default context to use, 'buffers', 'buffer' or none (can be specified manually in prompt via #).
241+
context = nil, -- Default context to use, 'buffers', 'buffer', 'files' or none (can be specified manually in prompt via #).
241242
temperature = 0.1, -- GPT result temperature
242243

243244
question_header = '## User ', -- Header to use for user questions

lua/CopilotChat/config.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ return {
9797
system_prompt = prompts.COPILOT_INSTRUCTIONS, -- System prompt to use
9898
model = 'gpt-4o', -- Default model to use, see ':CopilotChatModels' for available models
9999
agent = 'copilot', -- Default agent to use, see ':CopilotChatAgents' for available agents (can be specified manually in prompt via @).
100-
context = nil, -- Default context to use, 'buffers', 'buffer' or none (can be specified manually in prompt via #).
100+
context = nil, -- Default context to use, 'buffers', 'buffer', 'files' or none (can be specified manually in prompt via #).
101101
temperature = 0.1, -- GPT result temperature
102102

103103
question_header = '## User ', -- Header to use for user questions

lua/CopilotChat/context.lua

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
local async = require('plenary.async')
12
local log = require('plenary.log')
23

34
local M = {}
@@ -72,6 +73,78 @@ local function data_ranked_by_relatedness(query, data, top_n)
7273
return result
7374
end
7475

76+
local get_context_data = async.wrap(function(context, bufnr, callback)
77+
vim.schedule(function()
78+
local outline = {}
79+
if context == 'buffers' then
80+
outline = vim.tbl_map(
81+
M.build_outline,
82+
vim.tbl_filter(function(b)
83+
return vim.api.nvim_buf_is_loaded(b) and vim.fn.buflisted(b) == 1
84+
end, vim.api.nvim_list_bufs())
85+
)
86+
elseif context == 'buffer' then
87+
table.insert(outline, M.build_outline(bufnr))
88+
elseif context == 'files' then
89+
outline = M.build_file_map()
90+
end
91+
92+
callback(outline)
93+
end)
94+
end, 3)
95+
96+
--- Get supported contexts
97+
---@return table<string>
98+
function M.supported_contexts()
99+
return {
100+
buffers = 'Includes all open buffers in chat context',
101+
buffer = 'Includes only the current buffer in chat context',
102+
files = 'Includes all non-hidden filenames in the current workspace in chat context',
103+
}
104+
end
105+
106+
--- Get list of all files in workspace
107+
---@return table<CopilotChat.copilot.embed>
108+
function M.build_file_map()
109+
-- Use vim.fn.glob() to get all files
110+
local files = vim.fn.glob('**/*', false, true)
111+
112+
-- Filter out hidden files
113+
local filtered_files = vim.tbl_filter(function(file)
114+
local file_name = vim.fn.fnamemodify(file, ':t')
115+
local file_dir = vim.fn.fnamemodify(file, ':h')
116+
117+
if file_name:match('^%.') or file_dir:match('^%.') then
118+
return false
119+
end
120+
121+
return vim.fn.isdirectory(file) == 0
122+
end, files)
123+
124+
if #filtered_files == 0 then
125+
return {}
126+
end
127+
128+
local out = {}
129+
130+
-- Create embeddings in chunks
131+
local chunk_size = 100
132+
for i = 1, #filtered_files, chunk_size do
133+
local chunk = {}
134+
for j = i, math.min(i + chunk_size - 1, #filtered_files) do
135+
table.insert(chunk, filtered_files[j])
136+
end
137+
138+
table.insert(out, {
139+
content = table.concat(chunk, '\n'),
140+
filename = 'project_map',
141+
filetype = 'text',
142+
})
143+
end
144+
145+
return out
146+
end
147+
75148
--- Build an outline for a buffer
76149
--- FIXME: Handle multiline function argument definitions when building the outline
77150
---@param bufnr number
@@ -198,21 +271,7 @@ function M.find_for_query(copilot, opts)
198271
local filetype = opts.filetype
199272
local bufnr = opts.bufnr
200273

201-
local outline = {}
202-
if context == 'buffers' then
203-
-- For multiple buffers, only make outlines
204-
outline = vim.tbl_map(
205-
function(b)
206-
return M.build_outline(b)
207-
end,
208-
vim.tbl_filter(function(b)
209-
return vim.api.nvim_buf_is_loaded(b) and vim.fn.buflisted(b) == 1
210-
end, vim.api.nvim_list_bufs())
211-
)
212-
elseif context == 'buffer' then
213-
table.insert(outline, M.build_outline(bufnr))
214-
end
215-
274+
local outline = get_context_data(context, bufnr)
216275
outline = vim.tbl_filter(function(item)
217276
return item ~= nil
218277
end, outline)

lua/CopilotChat/copilot.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,8 @@ local function generate_embedding_request(inputs, model)
323323
if input.content then
324324
out = out
325325
.. string.format(
326-
'File: `%s`\n```%s\n%s\n```',
327-
input.filename,
326+
'# FILE:%s CONTEXT\n```%s\n%s\n```',
327+
input.filename:upper(),
328328
input.filetype,
329329
input.content
330330
)

lua/CopilotChat/init.lua

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ end
247247
function M.complete_items(callback)
248248
async.run(function()
249249
local agents = state.copilot:list_agents()
250+
local contexts = context.supported_contexts()
250251
local items = {}
251252
local prompts_to_use = M.prompts()
252253

@@ -273,23 +274,16 @@ function M.complete_items(callback)
273274
}
274275
end
275276

276-
items[#items + 1] = {
277-
word = '#buffers',
278-
kind = 'context',
279-
menu = 'Include all loaded buffers in context',
280-
icase = 1,
281-
dup = 0,
282-
empty = 0,
283-
}
284-
285-
items[#items + 1] = {
286-
word = '#buffer',
287-
kind = 'context',
288-
menu = 'Include the specified buffer in context',
289-
icase = 1,
290-
dup = 0,
291-
empty = 0,
292-
}
277+
for prompt_context, description in ipairs(contexts) do
278+
items[#items + 1] = {
279+
word = '#' .. prompt_context,
280+
kind = 'context',
281+
menu = description,
282+
icase = 1,
283+
dup = 0,
284+
empty = 0,
285+
}
286+
end
293287

294288
vim.schedule(function()
295289
callback(items)
@@ -499,12 +493,13 @@ function M.ask(prompt, config, source)
499493
append('\n\n' .. config.answer_header .. config.separator .. '\n\n', config)
500494

501495
local selected_context = config.context
502-
if string.find(prompt, '#buffers') then
503-
selected_context = 'buffers'
504-
elseif string.find(prompt, '#buffer') then
505-
selected_context = 'buffer'
496+
local contexts = vim.tbl_keys(context.supported_contexts())
497+
for prompt_context in updated_prompt:gmatch('#([%w_-]+)') do
498+
if vim.tbl_contains(contexts, prompt_context) then
499+
selected_context = prompt_context
500+
updated_prompt = string.gsub(updated_prompt, '#' .. prompt_context .. '%s*', '')
501+
end
506502
end
507-
updated_prompt = string.gsub(updated_prompt, '#buffers?%s*', '')
508503

509504
async.run(function()
510505
local agents = state.copilot:list_agents()

0 commit comments

Comments
 (0)