Skip to content

Commit

Permalink
Add support for #files (workspace file map) context
Browse files Browse the repository at this point in the history
Support workspace file 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 <slusnucky@gmail.com>
  • Loading branch information
deathbeam committed Nov 16, 2024
1 parent cd755e1 commit f378a6e
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 110 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ Supported contexts are:

- `buffers` - Includes all open buffers in chat context
- `buffer` - Includes only the current buffer in chat context
- `files` - Includes all non-hidden filenames in the current workspace in chat context

### API

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

question_header = '## User ', -- Header to use for user questions
Expand Down
2 changes: 1 addition & 1 deletion lua/CopilotChat/config.lua
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ return {
system_prompt = prompts.COPILOT_INSTRUCTIONS, -- System prompt to use
model = 'gpt-4o', -- Default model to use, see ':CopilotChatModels' for available models
agent = 'copilot', -- Default agent to use, see ':CopilotChatAgents' for available agents (can be specified manually in prompt via @).
context = nil, -- Default context to use, 'buffers', 'buffer' or none (can be specified manually in prompt via #).
context = nil, -- Default context to use, 'buffers', 'buffer', 'files' or none (can be specified manually in prompt via #).
temperature = 0.1, -- GPT result temperature

question_header = '## User ', -- Header to use for user questions
Expand Down
82 changes: 67 additions & 15 deletions lua/CopilotChat/context.lua
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
local async = require('plenary.async')
local log = require('plenary.log')

local M = {}
Expand Down Expand Up @@ -72,6 +73,71 @@ local function data_ranked_by_relatedness(query, data, top_n)
return result
end

local get_context_data = async.wrap(function(context, bufnr, callback)
vim.schedule(function()
local outline = {}
if context == 'buffers' then
outline = vim.tbl_map(
M.build_outline,
vim.tbl_filter(function(b)
return vim.api.nvim_buf_is_loaded(b) and vim.fn.buflisted(b) == 1
end, vim.api.nvim_list_bufs())
)
elseif context == 'buffer' then
table.insert(outline, M.build_outline(bufnr))
elseif context == 'files' then
outline = M.build_file_map()
end

callback(outline)
end)
end, 3)

--- Get supported contexts
---@return table<string>
function M.supported_contexts()
return {
buffers = 'Includes all open buffers in chat context',
buffer = 'Includes only the current buffer in chat context',
files = 'Includes all non-hidden filenames in the current workspace in chat context',
}
end

--- Get list of all files in workspace
---@return table<CopilotChat.copilot.embed>
function M.build_file_map()
-- Use vim.fn.glob() to get all files
local files = vim.fn.glob('**/*', false, true)

-- Filter out directories
local files = vim.tbl_filter(function(file)
return vim.fn.isdirectory(file) == 0
end, files)

if #files == 0 then
return {}
end

local out = {}

-- Create embeddings in chunks
local chunk_size = 100
for i = 1, #files, chunk_size do
local chunk = {}
for j = i, math.min(i + chunk_size - 1, #files) do
table.insert(chunk, files[j])
end

table.insert(out, {
content = table.concat(chunk, '\n'),
filename = 'file_map',
filetype = 'text',
})
end

return out
end

--- Build an outline for a buffer
--- FIXME: Handle multiline function argument definitions when building the outline
---@param bufnr number
Expand Down Expand Up @@ -198,21 +264,7 @@ function M.find_for_query(copilot, opts)
local filetype = opts.filetype
local bufnr = opts.bufnr

local outline = {}
if context == 'buffers' then
-- For multiple buffers, only make outlines
outline = vim.tbl_map(
function(b)
return M.build_outline(b)
end,
vim.tbl_filter(function(b)
return vim.api.nvim_buf_is_loaded(b) and vim.fn.buflisted(b) == 1
end, vim.api.nvim_list_bufs())
)
elseif context == 'buffer' then
table.insert(outline, M.build_outline(bufnr))
end

local outline = get_context_data(context, bufnr)
outline = vim.tbl_filter(function(item)
return item ~= nil
end, outline)
Expand Down
4 changes: 2 additions & 2 deletions lua/CopilotChat/copilot.lua
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,8 @@ local function generate_embedding_request(inputs, model)
if input.content then
out = out
.. string.format(
'File: `%s`\n```%s\n%s\n```',
input.filename,
'# FILE:%s CONTEXT\n```%s\n%s\n```',
input.filename:upper(),
input.filetype,
input.content
)
Expand Down
122 changes: 64 additions & 58 deletions lua/CopilotChat/debuginfo.lua
Original file line number Diff line number Diff line change
@@ -1,75 +1,81 @@
local log = require('plenary.log')
local utils = require('CopilotChat.utils')
local context = require('CopilotChat.context')
local M = {}

function M.setup()
-- Show debug info
vim.api.nvim_create_user_command('CopilotChatDebugInfo', function()
-- Get the log file path
local log_file_path = utils.get_log_file_path()
function M.open()
local lines = {
'If you are facing issues, run `:checkhealth CopilotChat` and share the output.',
'',
'Log file path:',
'`' .. log.logfile .. '`',
'',
'Temp directory:',
'`' .. vim.fn.fnamemodify(os.tmpname(), ':h') .. '`',
'',
'Data directory:',
'`' .. vim.fn.stdpath('data') .. '`',
'',
}

-- Create a popup with the log file path
local lines = {
'If you are facing issues, run `:checkhealth CopilotChat` and share the output.',
'',
'Log file path:',
'`' .. log_file_path .. '`',
'',
}
local outline = context.build_outline(vim.api.nvim_get_current_buf())
if outline then
table.insert(lines, 'Current buffer outline:')
table.insert(lines, '`' .. outline.filename .. '`')
table.insert(lines, '```' .. outline.filetype)
local outline_lines = vim.split(outline.content, '\n')
for _, line in ipairs(outline_lines) do
table.insert(lines, line)
end
table.insert(lines, '```')
end

local outline = context.build_outline(vim.api.nvim_get_current_buf())
if outline then
table.insert(lines, 'Current buffer outline:')
table.insert(lines, '`' .. outline.filename .. '`')
table.insert(lines, '```' .. outline.filetype)
local outline_lines = vim.split(outline.content, '\n')
for _, line in ipairs(outline_lines) do
local files = context.build_file_map()
if files then
table.insert(lines, 'Current workspace file map:')
table.insert(lines, '```text')
for _, file in ipairs(files) do
for _, line in ipairs(vim.split(file.content, '\n')) do
table.insert(lines, line)
end
table.insert(lines, '```')
end
table.insert(lines, '```')
end

local width = 0
for _, line in ipairs(lines) do
width = math.max(width, #line)
end
local height = math.min(vim.o.lines - 3, #lines)
local opts = {
title = 'CopilotChat.nvim Debug Info',
relative = 'editor',
width = width,
height = height,
row = (vim.o.lines - height) / 2 - 1,
col = (vim.o.columns - width) / 2,
style = 'minimal',
border = 'rounded',
}
local width = 0
for _, line in ipairs(lines) do
width = math.max(width, #line)
end
local height = math.min(vim.o.lines - 3, #lines)
local opts = {
title = 'CopilotChat.nvim Debug Info',
relative = 'editor',
width = width,
height = height,
row = (vim.o.lines - height) / 2 - 1,
col = (vim.o.columns - width) / 2,
style = 'minimal',
border = 'rounded',
}

if not utils.is_stable() then
opts.footer = "Press 'q' to close this window."
end
if not utils.is_stable() then
opts.footer = "Press 'q' to close this window."
end

local bufnr = vim.api.nvim_create_buf(false, true)
vim.bo[bufnr].syntax = 'markdown'
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
vim.bo[bufnr].modifiable = false
vim.treesitter.start(bufnr, 'markdown')
local bufnr = vim.api.nvim_create_buf(false, true)
vim.bo[bufnr].syntax = 'markdown'
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
vim.bo[bufnr].modifiable = false
vim.treesitter.start(bufnr, 'markdown')

local win = vim.api.nvim_open_win(bufnr, true, opts)
vim.wo[win].wrap = true
vim.wo[win].linebreak = true
vim.wo[win].cursorline = true
vim.wo[win].conceallevel = 2
local win = vim.api.nvim_open_win(bufnr, true, opts)
vim.wo[win].wrap = true
vim.wo[win].linebreak = true
vim.wo[win].cursorline = true
vim.wo[win].conceallevel = 2

-- Bind 'q' to close the window
vim.api.nvim_buf_set_keymap(
bufnr,
'n',
'q',
'<cmd>close<CR>',
{ noremap = true, silent = true }
)
end, { nargs = '*', range = true })
-- Bind 'q' to close the window
vim.api.nvim_buf_set_keymap(bufnr, 'n', 'q', '<cmd>close<CR>', { noremap = true, silent = true })
end

return M
4 changes: 4 additions & 0 deletions lua/CopilotChat/health.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ local start = vim.health.start or vim.health.report_start
local error = vim.health.error or vim.health.report_error
local warn = vim.health.warn or vim.health.report_warn
local ok = vim.health.ok or vim.health.report_ok
local info = vim.health.info or vim.health.report_info

--- Run a command and handle potential errors
---@param executable string
Expand Down Expand Up @@ -39,6 +40,9 @@ local function treesitter_parser_available(ft)
end

function M.check()
start('CopilotChat.nvim')
info('If you are facing any issues, also see :CopilotChatDebugInfo for more information.')

start('CopilotChat.nvim [core]')

local vim_version = vim.trim(vim.api.nvim_command_output('version'))
Expand Down
47 changes: 20 additions & 27 deletions lua/CopilotChat/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ end
function M.complete_items(callback)
async.run(function()
local agents = state.copilot:list_agents()
local contexts = context.supported_contexts()
local items = {}
local prompts_to_use = M.prompts()

Expand All @@ -273,23 +274,16 @@ function M.complete_items(callback)
}
end

items[#items + 1] = {
word = '#buffers',
kind = 'context',
menu = 'Include all loaded buffers in context',
icase = 1,
dup = 0,
empty = 0,
}

items[#items + 1] = {
word = '#buffer',
kind = 'context',
menu = 'Include the specified buffer in context',
icase = 1,
dup = 0,
empty = 0,
}
for prompt_context, description in pairs(contexts) do
items[#items + 1] = {
word = '#' .. prompt_context,
kind = 'context',
menu = description,
icase = 1,
dup = 0,
empty = 0,
}
end

vim.schedule(function()
callback(items)
Expand Down Expand Up @@ -499,12 +493,13 @@ function M.ask(prompt, config, source)
append('\n\n' .. config.answer_header .. config.separator .. '\n\n', config)

local selected_context = config.context
if string.find(prompt, '#buffers') then
selected_context = 'buffers'
elseif string.find(prompt, '#buffer') then
selected_context = 'buffer'
local contexts = vim.tbl_keys(context.supported_contexts())
for prompt_context in updated_prompt:gmatch('#([%w_-]+)') do
if vim.tbl_contains(contexts, prompt_context) then
selected_context = prompt_context
updated_prompt = string.gsub(updated_prompt, '#' .. prompt_context .. '%s*', '')
end
end
updated_prompt = string.gsub(updated_prompt, '#buffers?%s*', '')

async.run(function()
local agents = vim.tbl_keys(state.copilot:list_agents())
Expand Down Expand Up @@ -712,14 +707,9 @@ function M.setup(config)
end, { force = true })

M.config = vim.tbl_deep_extend('force', default_config, config or {})
if M.config.model == 'gpt-4o' then
M.config.model = 'gpt-4o-2024-05-13'
end

if state.copilot then
state.copilot:stop()
else
debuginfo.setup()
end

state.copilot = Copilot(M.config.proxy, M.config.allow_insecure)
Expand Down Expand Up @@ -1063,6 +1053,9 @@ function M.setup(config)
vim.api.nvim_create_user_command('CopilotChatReset', function()
M.reset()
end, { force = true })
vim.api.nvim_create_user_command('CopilotChatDebugInfo', function()
debuginfo.open()
end, { force = true })

local function complete_load()
local options = vim.tbl_map(function(file)
Expand Down
6 changes: 0 additions & 6 deletions lua/CopilotChat/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,6 @@ function M.class(fn, parent)
return out
end

--- Get the log file path
---@return string
function M.get_log_file_path()
return log.logfile
end

--- Check if the current version of neovim is stable
---@return boolean
function M.is_stable()
Expand Down

0 comments on commit f378a6e

Please sign in to comment.