604 lines
20 KiB
VimL
604 lines
20 KiB
VimL
|
scriptencoding utf-8
|
||
|
|
||
|
let s:plugin_version = copilot#version#String()
|
||
|
|
||
|
let s:error_exit = -1
|
||
|
|
||
|
let s:root = expand('<sfile>:h:h:h')
|
||
|
|
||
|
if !exists('s:instances')
|
||
|
let s:instances = {}
|
||
|
endif
|
||
|
|
||
|
" allow sourcing this file to reload the Lua file too
|
||
|
if has('nvim')
|
||
|
lua package.loaded._copilot = nil
|
||
|
endif
|
||
|
|
||
|
let s:jobstop = function(exists('*jobstop') ? 'jobstop' : 'job_stop')
|
||
|
function! s:Kill(agent, ...) abort
|
||
|
if has_key(a:agent, 'job')
|
||
|
call s:jobstop(a:agent.job)
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:AgentClose() dict abort
|
||
|
if !has_key(self, 'job')
|
||
|
return
|
||
|
endif
|
||
|
if exists('*chanclose')
|
||
|
call chanclose(self.job, 'stdin')
|
||
|
else
|
||
|
call ch_close_in(self.job)
|
||
|
endif
|
||
|
call copilot#logger#Info('agent stopped')
|
||
|
call timer_start(2000, function('s:Kill', [self]))
|
||
|
endfunction
|
||
|
|
||
|
function! s:LogSend(request, line) abort
|
||
|
return '--> ' . a:line
|
||
|
endfunction
|
||
|
|
||
|
function! s:RejectRequest(request, error) abort
|
||
|
if a:request.status ==# 'canceled'
|
||
|
return
|
||
|
endif
|
||
|
let a:request.waiting = {}
|
||
|
call remove(a:request, 'resolve')
|
||
|
let reject = remove(a:request, 'reject')
|
||
|
let a:request.status = 'error'
|
||
|
let a:request.error = a:error
|
||
|
for Cb in reject
|
||
|
let a:request.waiting[timer_start(0, function('s:Callback', [a:request, 'error', Cb]))] = 1
|
||
|
endfor
|
||
|
endfunction
|
||
|
|
||
|
function! s:Send(agent, request) abort
|
||
|
try
|
||
|
call ch_sendexpr(a:agent.job, a:request)
|
||
|
return v:true
|
||
|
catch /^Vim\%((\a\+)\)\=:E631:/
|
||
|
return v:false
|
||
|
endtry
|
||
|
endfunction
|
||
|
|
||
|
function! s:AgentNotify(method, params) dict abort
|
||
|
return s:Send(self, {'method': a:method, 'params': a:params})
|
||
|
endfunction
|
||
|
|
||
|
function! s:RequestWait() dict abort
|
||
|
while self.status ==# 'running'
|
||
|
sleep 1m
|
||
|
endwhile
|
||
|
while !empty(get(self, 'waiting', {}))
|
||
|
sleep 1m
|
||
|
endwhile
|
||
|
return self
|
||
|
endfunction
|
||
|
|
||
|
function! s:RequestAwait() dict abort
|
||
|
call self.Wait()
|
||
|
if has_key(self, 'result')
|
||
|
return self.result
|
||
|
endif
|
||
|
throw 'copilot#agent:E' . self.error.code . ': ' . self.error.message
|
||
|
endfunction
|
||
|
|
||
|
function! s:RequestAgent() dict abort
|
||
|
return get(s:instances, self.agent_id, v:null)
|
||
|
endfunction
|
||
|
|
||
|
if !exists('s:id')
|
||
|
let s:id = 0
|
||
|
endif
|
||
|
|
||
|
function! s:SetUpRequest(agent, id, method, params, ...) abort
|
||
|
let request = {
|
||
|
\ 'agent_id': a:agent.id,
|
||
|
\ 'id': a:id,
|
||
|
\ 'method': a:method,
|
||
|
\ 'params': a:params,
|
||
|
\ 'Agent': function('s:RequestAgent'),
|
||
|
\ 'Wait': function('s:RequestWait'),
|
||
|
\ 'Await': function('s:RequestAwait'),
|
||
|
\ 'Cancel': function('s:RequestCancel'),
|
||
|
\ 'resolve': [],
|
||
|
\ 'reject': [],
|
||
|
\ 'status': 'running'}
|
||
|
let a:agent.requests[a:id] = request
|
||
|
let args = a:000[2:-1]
|
||
|
if len(args)
|
||
|
if !empty(a:1)
|
||
|
call add(request.resolve, { v -> call(a:1, [v] + args)})
|
||
|
endif
|
||
|
if !empty(a:2)
|
||
|
call add(request.reject, { v -> call(a:2, [v] + args)})
|
||
|
endif
|
||
|
return request
|
||
|
endif
|
||
|
if a:0 && !empty(a:1)
|
||
|
call add(request.resolve, a:1)
|
||
|
endif
|
||
|
if a:0 > 1 && !empty(a:2)
|
||
|
call add(request.reject, a:2)
|
||
|
endif
|
||
|
return request
|
||
|
endfunction
|
||
|
|
||
|
function! s:UrlEncode(str) abort
|
||
|
return substitute(iconv(a:str, 'latin1', 'utf-8'),'[^A-Za-z0-9._~!$&''()*+,;=:@/-]','\="%".printf("%02X",char2nr(submatch(0)))','g')
|
||
|
endfunction
|
||
|
|
||
|
let s:slash = exists('+shellslash') ? '\' : '/'
|
||
|
function! s:UriFromBufnr(bufnr) abort
|
||
|
let absolute = tr(bufname(a:bufnr), s:slash, '/')
|
||
|
if absolute !~# '^\a\+:\|^/\|^$' && getbufvar(a:bufnr, 'buftype') =~# '^\%(nowrite\)\=$'
|
||
|
let absolute = substitute(tr(getcwd(), s:slash, '/'), '/\=$', '/', '') . absolute
|
||
|
endif
|
||
|
if has('win32') && absolute =~# '^\a://\@!'
|
||
|
return 'file:///' . strpart(absolute, 0, 2) . s:UrlEncode(strpart(absolute, 2))
|
||
|
elseif absolute =~# '^/'
|
||
|
return 'file://' . s:UrlEncode(absolute)
|
||
|
elseif absolute =~# '^\a[[:alnum:].+-]*:\|^$'
|
||
|
return absolute
|
||
|
else
|
||
|
return ''
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:BufferText(bufnr) abort
|
||
|
return join(getbufline(a:bufnr, 1, '$'), "\n") . "\n"
|
||
|
endfunction
|
||
|
|
||
|
function! s:LogMessage(params) abort
|
||
|
call copilot#logger#Raw(get(a:params, 'level', 3), get(a:params, 'message', ''))
|
||
|
endfunction
|
||
|
|
||
|
function! s:ShowMessageRequest(params) abort
|
||
|
let choice = inputlist([a:params.message . "\n\nRequest Actions:"] +
|
||
|
\ map(copy(get(a:params, 'actions', [])), { i, v -> (i + 1) . '. ' . v.title}))
|
||
|
return choice > 0 ? get(a:params.actions, choice - 1, v:null) : v:null
|
||
|
endfunction
|
||
|
|
||
|
function! s:SendRequest(agent, request) abort
|
||
|
if empty(s:Send(a:agent, a:request)) && has_key(a:agent.requests, a:request.id)
|
||
|
call s:RejectRequest(remove(a:agent.requests, a:request.id), {'code': 257, 'message': 'Write failed'})
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:AgentRequest(method, params, ...) dict abort
|
||
|
let s:id += 1
|
||
|
let request = {'method': a:method, 'params': deepcopy(a:params), 'id': s:id}
|
||
|
for doc in filter([get(request.params, 'doc', {}), get(request.params, 'textDocument',{})], 'type(get(v:val, "uri", "")) == v:t_number')
|
||
|
let bufnr = doc.uri
|
||
|
let doc.uri = s:UriFromBufnr(doc.uri)
|
||
|
let uri = doc.uri
|
||
|
let languageId = copilot#doc#LanguageForFileType(getbufvar(bufnr, '&filetype'))
|
||
|
let doc_version = getbufvar(bufnr, 'changedtick')
|
||
|
if has_key(self.open_buffers, bufnr) && (
|
||
|
\ self.open_buffers[bufnr].uri !=# doc.uri ||
|
||
|
\ self.open_buffers[bufnr].languageId !=# languageId)
|
||
|
call remove(self.open_buffers, bufnr)
|
||
|
sleep 1m
|
||
|
endif
|
||
|
if !has_key(self.open_buffers, bufnr)
|
||
|
let td_item = {
|
||
|
\ 'uri': doc.uri,
|
||
|
\ 'version': doc_version,
|
||
|
\ 'languageId': languageId,
|
||
|
\ 'text': s:BufferText(bufnr)}
|
||
|
call self.Notify('textDocument/didOpen', {'textDocument': td_item})
|
||
|
let self.open_buffers[bufnr] = {
|
||
|
\ 'uri': doc.uri,
|
||
|
\ 'version': doc_version,
|
||
|
\ 'languageId': languageId}
|
||
|
else
|
||
|
let vtd_id = {
|
||
|
\ 'uri': doc.uri,
|
||
|
\ 'version': doc_version}
|
||
|
call self.Notify('textDocument/didChange', {
|
||
|
\ 'textDocument': vtd_id,
|
||
|
\ 'contentChanges': [{'text': s:BufferText(bufnr)}]})
|
||
|
let self.open_buffers[bufnr].version = doc_version
|
||
|
endif
|
||
|
let doc.version = doc_version
|
||
|
endfor
|
||
|
call timer_start(0, { _ -> s:SendRequest(self, request) })
|
||
|
return call('s:SetUpRequest', [self, s:id, a:method, a:params] + a:000)
|
||
|
endfunction
|
||
|
|
||
|
function! s:AgentCall(method, params, ...) dict abort
|
||
|
let request = call(self.Request, [a:method, a:params] + a:000)
|
||
|
if a:0
|
||
|
return request
|
||
|
endif
|
||
|
return request.Await()
|
||
|
endfunction
|
||
|
|
||
|
function! s:AgentCancel(request) dict abort
|
||
|
if has_key(self.requests, get(a:request, 'id', ''))
|
||
|
call remove(self.requests, a:request.id)
|
||
|
call self.Notify('$/cancelRequest', {'id': a:request.id})
|
||
|
endif
|
||
|
if get(a:request, 'status', '') ==# 'running'
|
||
|
let a:request.status = 'canceled'
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:RequestCancel() dict abort
|
||
|
let agent = self.Agent()
|
||
|
if !empty(agent)
|
||
|
call agent.Cancel(self)
|
||
|
elseif get(self, 'status', '') ==# 'running'
|
||
|
let self.status = 'canceled'
|
||
|
endif
|
||
|
return self
|
||
|
endfunction
|
||
|
|
||
|
function! s:DispatchMessage(agent, method, handler, id, params, ...) abort
|
||
|
try
|
||
|
let response = {'result': call(a:handler, [a:params])}
|
||
|
if response.result is# 0
|
||
|
let response.result = v:null
|
||
|
endif
|
||
|
catch
|
||
|
call copilot#logger#Exception('lsp.request.' . a:method)
|
||
|
let response = {'error': {'code': -32000, 'message': v:exception}}
|
||
|
endtry
|
||
|
if !empty(a:id)
|
||
|
call s:Send(a:agent, extend({'id': a:id}, response))
|
||
|
endif
|
||
|
return response
|
||
|
endfunction
|
||
|
|
||
|
function! s:OnMessage(agent, body, ...) abort
|
||
|
if !has_key(a:body, 'method')
|
||
|
return s:OnResponse(a:agent, a:body)
|
||
|
endif
|
||
|
let request = a:body
|
||
|
let id = get(request, 'id', v:null)
|
||
|
let params = get(request, 'params', v:null)
|
||
|
if has_key(a:agent.methods, request.method)
|
||
|
return s:DispatchMessage(a:agent, request.method, a:agent.methods[request.method], id, params)
|
||
|
elseif !empty(id)
|
||
|
call s:Send(a:agent, {"id": id, "error": {"code": -32700, "message": "Method not found: " . request.method}})
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:OnResponse(agent, response, ...) abort
|
||
|
let response = a:response
|
||
|
let id = get(a:response, 'id', v:null)
|
||
|
if !has_key(a:agent.requests, id)
|
||
|
return
|
||
|
endif
|
||
|
let request = remove(a:agent.requests, id)
|
||
|
if request.status ==# 'canceled'
|
||
|
return
|
||
|
endif
|
||
|
let request.waiting = {}
|
||
|
let resolve = remove(request, 'resolve')
|
||
|
let reject = remove(request, 'reject')
|
||
|
if has_key(response, 'result')
|
||
|
let request.status = 'success'
|
||
|
let request.result = response.result
|
||
|
for Cb in resolve
|
||
|
let request.waiting[timer_start(0, function('s:Callback', [request, 'result', Cb]))] = 1
|
||
|
endfor
|
||
|
else
|
||
|
let request.status = 'error'
|
||
|
let request.error = response.error
|
||
|
for Cb in reject
|
||
|
let request.waiting[timer_start(0, function('s:Callback', [request, 'error', Cb]))] = 1
|
||
|
endfor
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:OnErr(agent, line, ...) abort
|
||
|
call copilot#logger#Debug('<-! ' . a:line)
|
||
|
endfunction
|
||
|
|
||
|
function! s:OnExit(agent, code, ...) abort
|
||
|
let a:agent.exit_status = a:code
|
||
|
if has_key(a:agent, 'job')
|
||
|
call remove(a:agent, 'job')
|
||
|
endif
|
||
|
if has_key(a:agent, 'client_id')
|
||
|
call remove(a:agent, 'client_id')
|
||
|
endif
|
||
|
let code = a:code < 0 || a:code > 255 ? 256 : a:code
|
||
|
for id in sort(keys(a:agent.requests), { a, b -> +a > +b })
|
||
|
call s:RejectRequest(remove(a:agent.requests, id), {'code': code, 'message': 'Agent exited', 'data': {'status': a:code}})
|
||
|
endfor
|
||
|
call timer_start(0, { _ -> get(s:instances, a:agent.id) is# a:agent ? remove(s:instances, a:agent.id) : {} })
|
||
|
call copilot#logger#Info('agent exited with status ' . a:code)
|
||
|
endfunction
|
||
|
|
||
|
function! copilot#agent#LspInit(agent_id, initialize_result) abort
|
||
|
if !has_key(s:instances, a:agent_id)
|
||
|
return
|
||
|
endif
|
||
|
let instance = s:instances[a:agent_id]
|
||
|
call timer_start(0, { _ -> s:GetCapabilitiesResult(a:initialize_result, instance)})
|
||
|
endfunction
|
||
|
|
||
|
function! copilot#agent#LspExit(agent_id, code, signal) abort
|
||
|
if !has_key(s:instances, a:agent_id)
|
||
|
return
|
||
|
endif
|
||
|
let instance = remove(s:instances, a:agent_id)
|
||
|
call s:OnExit(instance, a:code)
|
||
|
endfunction
|
||
|
|
||
|
function! copilot#agent#LspResponse(agent_id, opts, ...) abort
|
||
|
if !has_key(s:instances, a:agent_id)
|
||
|
return
|
||
|
endif
|
||
|
call s:OnResponse(s:instances[a:agent_id], a:opts)
|
||
|
endfunction
|
||
|
|
||
|
function! s:LspRequest(method, params, ...) dict abort
|
||
|
let id = v:lua.require'_copilot'.lsp_request(self.id, a:method, a:params)
|
||
|
if id isnot# v:null
|
||
|
return call('s:SetUpRequest', [self, id, a:method, a:params] + a:000)
|
||
|
endif
|
||
|
if has_key(self, 'client_id')
|
||
|
call copilot#agent#LspExit(self.client_id, -1, -1)
|
||
|
endif
|
||
|
throw 'copilot#agent: LSP client not available'
|
||
|
endfunction
|
||
|
|
||
|
function! s:LspClose() dict abort
|
||
|
if !has_key(self, 'client_id')
|
||
|
return
|
||
|
endif
|
||
|
return luaeval('vim.lsp.get_client_by_id(_A).stop()', self.client_id)
|
||
|
endfunction
|
||
|
|
||
|
function! s:LspNotify(method, params) dict abort
|
||
|
return v:lua.require'_copilot'.rpc_notify(self.id, a:method, a:params)
|
||
|
endfunction
|
||
|
|
||
|
function! copilot#agent#LspHandle(agent_id, request) abort
|
||
|
if !has_key(s:instances, a:agent_id)
|
||
|
return
|
||
|
endif
|
||
|
return s:OnMessage(s:instances[a:agent_id], a:request)
|
||
|
endfunction
|
||
|
|
||
|
function! s:GetNodeVersion(command) abort
|
||
|
let out = []
|
||
|
let err = []
|
||
|
let status = copilot#job#Stream(a:command + ['--version'], function('add', [out]), function('add', [err]))
|
||
|
let string = matchstr(join(out, ''), '^v\zs\d\+\.[^[:space:]]*')
|
||
|
if status != 0
|
||
|
let string = ''
|
||
|
endif
|
||
|
let major = str2nr(string)
|
||
|
let minor = str2nr(matchstr(string, '\.\zs\d\+'))
|
||
|
return {'status': status, 'string': string, 'major': major, 'minor': minor}
|
||
|
endfunction
|
||
|
|
||
|
function! s:Command() abort
|
||
|
if !has('nvim-0.6') && v:version < 900
|
||
|
return [v:null, '', 'Vim version too old']
|
||
|
endif
|
||
|
let agent = get(g:, 'copilot_agent_command', '')
|
||
|
if empty(agent) || !filereadable(agent)
|
||
|
let agent = s:root . '/dist/agent.js'
|
||
|
if !filereadable(agent)
|
||
|
return [v:null, '', 'Could not find dist/agent.js (bad install?)']
|
||
|
endif
|
||
|
endif
|
||
|
let node = get(g:, 'copilot_node_command', '')
|
||
|
if empty(node)
|
||
|
let node = ['node']
|
||
|
elseif type(node) == type('')
|
||
|
let node = [expand(node)]
|
||
|
endif
|
||
|
if !executable(get(node, 0, ''))
|
||
|
if get(node, 0, '') ==# 'node'
|
||
|
return [v:null, '', 'Node.js not found in PATH']
|
||
|
else
|
||
|
return [v:null, '', 'Node.js executable `' . get(node, 0, '') . "' not found"]
|
||
|
endif
|
||
|
endif
|
||
|
if get(g:, 'copilot_ignore_node_version')
|
||
|
return [node + [agent, '--stdio'], '', '']
|
||
|
endif
|
||
|
let node_version = s:GetNodeVersion(node)
|
||
|
let warning = ''
|
||
|
if node_version.major < 18 && get(node, 0, '') !=# 'node' && executable('node')
|
||
|
let node_version_from_path = s:GetNodeVersion(['node'])
|
||
|
if node_version_from_path.major >= 18
|
||
|
let warning = 'Ignoring g:copilot_node_command: Node.js ' . node_version.string . ' is end-of-life'
|
||
|
let node = ['node']
|
||
|
let node_version = node_version_from_path
|
||
|
endif
|
||
|
endif
|
||
|
if node_version.status != 0
|
||
|
return [v:null, '', 'Node.js exited with status ' . node_version.status]
|
||
|
endif
|
||
|
if !get(g:, 'copilot_ignore_node_version')
|
||
|
if node_version.major == 0
|
||
|
return [v:null, node_version.string, 'Could not determine Node.js version']
|
||
|
elseif node_version.major < 16 || node_version.major == 16 && node_version.minor < 14 || node_version.major == 17 && node_version.minor < 3
|
||
|
" 16.14+ and 17.3+ still work for now, but are end-of-life
|
||
|
return [v:null, node_version.string, 'Node.js version 18.x or newer required but found ' . node_version.string]
|
||
|
endif
|
||
|
endif
|
||
|
return [node + [agent, '--stdio'], node_version.string, warning]
|
||
|
endfunction
|
||
|
|
||
|
function! s:UrlDecode(str) abort
|
||
|
return substitute(a:str, '%\(\x\x\)', '\=iconv(nr2char("0x".submatch(1)), "utf-8", "latin1")', 'g')
|
||
|
endfunction
|
||
|
|
||
|
function! copilot#agent#EditorInfo() abort
|
||
|
if !exists('s:editor_version')
|
||
|
if has('nvim')
|
||
|
let s:editor_version = matchstr(execute('version'), 'NVIM v\zs[^[:space:]]\+')
|
||
|
else
|
||
|
let s:editor_version = (v:version / 100) . '.' . (v:version % 100) . (exists('v:versionlong') ? printf('.%04d', v:versionlong % 1000) : '')
|
||
|
endif
|
||
|
endif
|
||
|
let info = {
|
||
|
\ 'editorInfo': {'name': has('nvim') ? 'Neovim': 'Vim', 'version': s:editor_version},
|
||
|
\ 'editorPluginInfo': {'name': 'copilot.vim', 'version': s:plugin_version}}
|
||
|
if type(get(g:, 'copilot_proxy')) == v:t_string
|
||
|
let proxy = g:copilot_proxy
|
||
|
else
|
||
|
let proxy = ''
|
||
|
endif
|
||
|
let match = matchlist(proxy, '\c^\%([^:]\+://\)\=\%(\([^/#]\+@\)\)\=\%(\([^/:#]\+\)\|\[\([[:xdigit:]:]\+\)\]\)\%(:\(\d\+\)\)\=\%(/\|$\|?strict_\=ssl=\(.*\)\)')
|
||
|
if !empty(match)
|
||
|
let info.networkProxy = {'host': match[2] . match[3], 'port': empty(match[4]) ? 80 : +match[4]}
|
||
|
if match[5] =~? '^[0f]'
|
||
|
let info.networkProxy.rejectUnauthorized = v:false
|
||
|
elseif match[5] =~? '^[1t]'
|
||
|
let info.networkProxy.rejectUnauthorized = v:true
|
||
|
elseif exists('g:copilot_proxy_strict_ssl')
|
||
|
let info.networkProxy.rejectUnauthorized = empty(g:copilot_proxy_strict_ssl) ? v:false : v:true
|
||
|
endif
|
||
|
if !empty(match[1])
|
||
|
let info.networkProxy.username = s:UrlDecode(matchstr(match[1], '^[^:@]*'))
|
||
|
let info.networkProxy.password = s:UrlDecode(matchstr(match[1], ':\zs[^@]*'))
|
||
|
endif
|
||
|
endif
|
||
|
return info
|
||
|
endfunction
|
||
|
|
||
|
function! s:GetCapabilitiesResult(result, agent) abort
|
||
|
let a:agent.capabilities = get(a:result, 'capabilities', {})
|
||
|
let info = copilot#agent#EditorInfo()
|
||
|
call a:agent.Request('setEditorInfo', extend({'editorConfiguration': a:agent.editorConfiguration}, info))
|
||
|
endfunction
|
||
|
|
||
|
function! s:GetCapabilitiesError(error, agent) abort
|
||
|
if a:error.code == s:error_exit
|
||
|
let a:agent.startup_error = 'Agent exited with status ' . a:error.data.status
|
||
|
else
|
||
|
let a:agent.startup_error = 'Unexpected error ' . a:error.code . ' calling agent: ' . a:error.message
|
||
|
call a:agent.Close()
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:AgentStartupError() dict abort
|
||
|
while (has_key(self, 'job') || has_key(self, 'client_id')) && !has_key(self, 'startup_error') && !has_key(self, 'capabilities')
|
||
|
sleep 10m
|
||
|
endwhile
|
||
|
if has_key(self, 'capabilities')
|
||
|
return ''
|
||
|
else
|
||
|
return get(self, 'startup_error', 'Something unexpected went wrong spawning the agent')
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! copilot#agent#New(...) abort
|
||
|
let opts = a:0 ? a:1 : {}
|
||
|
let instance = {'requests': {},
|
||
|
\ 'editorConfiguration': get(opts, 'editorConfiguration', {}),
|
||
|
\ 'Close': function('s:AgentClose'),
|
||
|
\ 'Notify': function('s:AgentNotify'),
|
||
|
\ 'Request': function('s:AgentRequest'),
|
||
|
\ 'Call': function('s:AgentCall'),
|
||
|
\ 'Cancel': function('s:AgentCancel'),
|
||
|
\ 'StartupError': function('s:AgentStartupError'),
|
||
|
\ }
|
||
|
let instance.methods = extend({
|
||
|
\ 'LogMessage': function('s:LogMessage'),
|
||
|
\ 'window/logMessage': function('s:LogMessage'),
|
||
|
\ }, get(opts, 'methods', {}))
|
||
|
let [command, node_version, command_error] = s:Command()
|
||
|
if len(command_error)
|
||
|
if empty(command)
|
||
|
let instance.id = -1
|
||
|
let instance.startup_error = command_error
|
||
|
return instance
|
||
|
else
|
||
|
let instance.node_version_warning = command_error
|
||
|
endif
|
||
|
endif
|
||
|
if !empty(node_version)
|
||
|
let instance.node_version = node_version
|
||
|
endif
|
||
|
if has('nvim')
|
||
|
call extend(instance, {
|
||
|
\ 'Close': function('s:LspClose'),
|
||
|
\ 'Notify': function('s:LspNotify'),
|
||
|
\ 'Request': function('s:LspRequest')})
|
||
|
let instance.client_id = v:lua.require'_copilot'.lsp_start_client(command, keys(instance.methods))
|
||
|
let instance.id = instance.client_id
|
||
|
else
|
||
|
let state = {'headers': {}, 'mode': 'headers', 'buffer': ''}
|
||
|
let instance.open_buffers = {}
|
||
|
let instance.methods = extend({'window/showMessageRequest': function('s:ShowMessageRequest')}, instance.methods)
|
||
|
let instance.job = job_start(command, {
|
||
|
\ 'cwd': copilot#job#Cwd(),
|
||
|
\ 'in_mode': 'lsp',
|
||
|
\ 'out_mode': 'lsp',
|
||
|
\ 'out_cb': { j, d -> timer_start(0, function('s:OnMessage', [instance, d])) },
|
||
|
\ 'err_cb': { j, d -> timer_start(0, function('s:OnErr', [instance, d])) },
|
||
|
\ 'exit_cb': { j, d -> timer_start(0, function('s:OnExit', [instance, d])) },
|
||
|
\ })
|
||
|
let instance.id = exists('*jobpid') ? jobpid(instance.job) : job_info(instance.job).process
|
||
|
let capabilities = {'workspace': {'workspaceFolders': v:true}, 'copilot': {}}
|
||
|
for name in keys(instance.methods)
|
||
|
if name =~# '^copilot/'
|
||
|
let capabilities.copilot[matchstr(name, '/\zs.*')] = v:true
|
||
|
endif
|
||
|
endfor
|
||
|
let request = instance.Request('initialize', {'capabilities': capabilities}, function('s:GetCapabilitiesResult'), function('s:GetCapabilitiesError'), instance)
|
||
|
endif
|
||
|
let s:instances[instance.id] = instance
|
||
|
return instance
|
||
|
endfunction
|
||
|
|
||
|
function! copilot#agent#Cancel(request) abort
|
||
|
if type(a:request) == type({}) && has_key(a:request, 'Cancel')
|
||
|
call a:request.Cancel()
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:Callback(request, type, callback, timer) abort
|
||
|
call remove(a:request.waiting, a:timer)
|
||
|
if has_key(a:request, a:type)
|
||
|
call a:callback(a:request[a:type])
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! copilot#agent#Result(request, callback) abort
|
||
|
if has_key(a:request, 'resolve')
|
||
|
call add(a:request.resolve, a:callback)
|
||
|
elseif has_key(a:request, 'result')
|
||
|
let a:request.waiting[timer_start(0, function('s:Callback', [a:request, 'result', a:callback]))] = 1
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! copilot#agent#Error(request, callback) abort
|
||
|
if has_key(a:request, 'reject')
|
||
|
call add(a:request.reject, a:callback)
|
||
|
elseif has_key(a:request, 'error')
|
||
|
let a:request.waiting[timer_start(0, function('s:Callback', [a:request, 'error', a:callback]))] = 1
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! s:CloseBuffer(bufnr) abort
|
||
|
for instance in values(s:instances)
|
||
|
try
|
||
|
if has_key(instance, 'job') && has_key(instance.open_buffers, a:bufnr)
|
||
|
let buffer = remove(instance.open_buffers, a:bufnr)
|
||
|
call instance.Notify('textDocument/didClose', {'textDocument': {'uri': buffer.uri}})
|
||
|
endif
|
||
|
catch
|
||
|
call copilot#logger#Exception()
|
||
|
endtry
|
||
|
endfor
|
||
|
endfunction
|
||
|
|
||
|
augroup copilot_agent
|
||
|
autocmd!
|
||
|
if !has('nvim')
|
||
|
autocmd BufUnload * call s:CloseBuffer(+expand('<abuf>'))
|
||
|
endif
|
||
|
augroup END
|