forked from CopilotC-Nvim/CopilotChat.nvim
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcompletion.lua
More file actions
239 lines (211 loc) · 6.38 KB
/
completion.lua
File metadata and controls
239 lines (211 loc) · 6.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
local async = require('plenary.async')
local client = require('CopilotChat.client')
local constants = require('CopilotChat.constants')
local config = require('CopilotChat.config')
local functions = require('CopilotChat.functions')
local utils = require('CopilotChat.utils')
local M = {}
--- Get the completion info for the chat window, for use with custom completion providers
---@return table
function M.info()
return {
triggers = { '@', '/', '#', '$' },
pattern = [[\%(@\|/\|#\|\$\)\S*]],
}
end
--- Get the completion items for the chat window, for use with custom completion providers
---@return table
---@async
function M.items()
local models = client:models()
local prompts = config.prompts or {}
local items = {}
for name, prompt in pairs(prompts) do
if type(prompt) == 'string' then
prompt = {
prompt = prompt,
}
end
local kind = ''
local info = ''
if prompt.prompt then
kind = constants.ROLE.USER
info = prompt.prompt
elseif prompt.system_prompt then
kind = constants.ROLE.SYSTEM
info = prompt.system_prompt
end
items[#items + 1] = {
word = '/' .. name,
abbr = name,
kind = kind,
info = info,
menu = prompt.description or '',
icase = 1,
dup = 0,
empty = 0,
}
end
for id, model in pairs(models) do
items[#items + 1] = {
word = '$' .. id,
abbr = id,
kind = model.provider,
menu = model.name,
icase = 1,
dup = 0,
empty = 0,
}
end
local groups = {}
for name, tool in pairs(config.functions) do
if tool.group then
groups[tool.group] = groups[tool.group] or {}
groups[tool.group][name] = tool
end
end
for name, group in pairs(groups) do
local group_tools = vim.tbl_keys(group)
items[#items + 1] = {
word = '@' .. name,
abbr = name,
kind = 'group',
info = table.concat(group_tools, '\n'),
menu = string.format('%s tools', #group_tools),
icase = 1,
dup = 0,
empty = 0,
}
end
for name, tool in pairs(config.functions) do
items[#items + 1] = {
word = '@' .. name,
abbr = name,
kind = constants.ROLE.TOOL,
info = tool.description,
menu = tool.group or '',
icase = 1,
dup = 0,
empty = 0,
}
end
local tools_to_use = functions.parse_tools(config.functions)
for _, tool in pairs(tools_to_use) do
local uri = config.functions[tool.name].uri
if uri then
local info =
string.format('%s\n\n%s', tool.description, tool.schema and vim.inspect(tool.schema, { indent = ' ' }) or '')
items[#items + 1] = {
word = '#' .. tool.name,
abbr = tool.name,
kind = config.functions[tool.name].group or 'resource',
info = info,
menu = uri,
icase = 1,
dup = 0,
empty = 0,
}
end
end
table.sort(items, function(a, b)
if a.kind == b.kind then
return a.word < b.word
end
return a.kind < b.kind
end)
return items
end
--- Trigger the completion for the chat window.
---@param without_input boolean?
function M.complete(without_input)
local source = require('CopilotChat').chat:get_source()
local info = M.info()
local bufnr = vim.api.nvim_get_current_buf()
local line = vim.api.nvim_get_current_line()
local win = vim.api.nvim_get_current_win()
local row, col = unpack(vim.api.nvim_win_get_cursor(win))
local prefix, cmp_start = unpack(vim.fn.matchstrpos(line:sub(1, col), info.pattern))
if not prefix then
return
end
if not without_input and vim.startswith(prefix, '#') and vim.endswith(prefix, ':') then
local found_tool = config.functions[prefix:sub(2, -2)]
local found_schema = found_tool and functions.parse_schema(found_tool)
if found_tool and found_schema and found_tool.uri then
async.run(function()
local value = functions.enter_input(found_schema, source)
if not value then
return
end
utils.schedule_main()
vim.api.nvim_buf_set_text(bufnr, row - 1, col, row - 1, col, { value })
vim.api.nvim_win_set_cursor(0, { row, col + #value })
end)
end
return
end
utils.debounce('copilot_chat_complete', function()
async.run(function()
local items = M.items()
utils.schedule_main()
if not vim.api.nvim_win_is_valid(win) then
return
end
local row_changed = vim.api.nvim_win_get_cursor(win)[1] ~= row
local mode = vim.api.nvim_get_mode().mode
if row_changed or not (mode == 'i' or mode == 'ic') then
return
end
vim.fn.complete(
cmp_start + 1,
vim.tbl_filter(function(item)
return vim.startswith(item.word:lower(), prefix:lower())
end, items)
)
end)
end, 100)
end
--- Omnifunc for the chat window completion.
---@param findstart integer 0 or 1, decides behavior
---@param base integer findstart=0, text to match against
---@return number|table
function M.omnifunc(findstart, base)
assert(base)
local bufnr = vim.api.nvim_get_current_buf()
local ft = vim.bo[bufnr].filetype
if ft ~= 'copilot-chat' then
return findstart == 1 and -1 or {}
end
M.complete(true)
return -2 -- Return -2 to indicate that we are handling the completion asynchronously
end
--- Enable the completion for specific buffer.
---@param bufnr number: the buffer number to enable completion for
---@param autocomplete boolean: whether to enable autocomplete
function M.enable(bufnr, autocomplete)
if autocomplete then
vim.api.nvim_create_autocmd('TextChangedI', {
buffer = bufnr,
callback = function()
local completeopt = vim.opt.completeopt:get()
if not vim.tbl_contains(completeopt, 'noinsert') and not vim.tbl_contains(completeopt, 'noselect') then
-- Don't trigger completion if completeopt is not set to noinsert or noselect
return
end
M.complete(true)
end,
})
-- Add noinsert completeopt if not present
if vim.fn.has('nvim-0.11.0') == 1 then
local completeopt = vim.opt.completeopt:get()
if not vim.tbl_contains(completeopt, 'noinsert') then
table.insert(completeopt, 'noinsert')
vim.bo[bufnr].completeopt = table.concat(completeopt, ',')
end
end
else
-- Just set the omnifunc for the buffer
vim.bo[bufnr].omnifunc = [[v:lua.require'CopilotChat.completion'.omnifunc]]
end
end
return M