mirror of
https://github.com/amix/vimrc
synced 2024-12-29 21:33:06 +00:00
764 lines
25 KiB
VimL
764 lines
25 KiB
VimL
scriptencoding utf-8
|
|
|
|
let s:plugin_version = copilot#version#String()
|
|
|
|
let s:error_canceled = {'code': -32800, 'message': 'Canceled'}
|
|
let s:error_exit = {'code': -32097, 'message': 'Process exited'}
|
|
let s:error_connection_inactive = {'code': -32096, 'message': 'Connection inactive'}
|
|
|
|
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
|
|
|
|
function! s:Warn(msg) abort
|
|
if !empty(get(g:, 'copilot_no_startup_warnings'))
|
|
return
|
|
endif
|
|
echohl WarningMsg
|
|
echomsg 'Copilot: ' . a:msg
|
|
echohl NONE
|
|
endfunction
|
|
|
|
function! s:VimClose() dict abort
|
|
if !has_key(self, 'job')
|
|
return
|
|
endif
|
|
let job = self.job
|
|
if has_key(self, 'kill')
|
|
call job_stop(job, 'kill')
|
|
call copilot#logger#Warn('Process forcefully terminated')
|
|
return
|
|
endif
|
|
let self.kill = v:true
|
|
let self.shutdown = self.Request('shutdown', {}, function(self.Notify, ['exit']))
|
|
call timer_start(2000, { _ -> job_stop(job, 'kill') })
|
|
call copilot#logger#Debug('Process shutdown initiated')
|
|
endfunction
|
|
|
|
function! s:LogSend(request, line) abort
|
|
return '--> ' . a:line
|
|
endfunction
|
|
|
|
function! s:RejectRequest(request, error) abort
|
|
if a:request.status !=# 'running'
|
|
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 = deepcopy(a:error)
|
|
for Cb in reject
|
|
let a:request.waiting[timer_start(0, function('s:Callback', [a:request, 'error', Cb]))] = 1
|
|
endfor
|
|
if index([s:error_canceled.code, s:error_connection_inactive.code], a:error.code) != -1
|
|
return
|
|
endif
|
|
let msg = 'Method ' . a:request.method . ' errored with E' . a:error.code . ': ' . json_encode(a:error.message)
|
|
if empty(reject)
|
|
call copilot#logger#Error(msg)
|
|
else
|
|
call copilot#logger#Debug(msg)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:AfterInitialized(fn, ...) dict abort
|
|
call add(self.after_initialized, function(a:fn, a:000))
|
|
endfunction
|
|
|
|
function! s:Send(instance, request) abort
|
|
if !has_key(a:instance, 'job')
|
|
return v:false
|
|
endif
|
|
try
|
|
call ch_sendexpr(a:instance.job, a:request)
|
|
return v:true
|
|
catch /^Vim\%((\a\+)\)\=:E906:/
|
|
let a:instance.kill = v:true
|
|
let job = remove(a:instance, 'job')
|
|
call job_stop(job)
|
|
call timer_start(2000, { _ -> job_stop(job, 'kill') })
|
|
call copilot#logger#Warn('Terminating process after failed write')
|
|
return v:false
|
|
catch /^Vim\%((\a\+)\)\=:E631:/
|
|
return v:false
|
|
endtry
|
|
endfunction
|
|
|
|
function! s:VimNotify(method, params) dict abort
|
|
let request = {'method': a:method, 'params': a:params}
|
|
call self.AfterInitialized(function('s:Send', [self, request]))
|
|
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:E' . self.error.code . ': ' . self.error.message
|
|
endfunction
|
|
|
|
function! s:RequestClient() dict abort
|
|
return get(s:instances, self.client_id, v:null)
|
|
endfunction
|
|
|
|
if !exists('s:id')
|
|
let s:id = 0
|
|
endif
|
|
if !exists('s:progress_token_id')
|
|
let s:progress_token_id = 0
|
|
endif
|
|
|
|
function! s:SetUpRequest(instance, id, method, params, progress, ...) abort
|
|
let request = {
|
|
\ 'client_id': a:instance.id,
|
|
\ 'id': a:id,
|
|
\ 'method': a:method,
|
|
\ 'params': a:params,
|
|
\ 'Client': function('s:RequestClient'),
|
|
\ 'Wait': function('s:RequestWait'),
|
|
\ 'Await': function('s:RequestAwait'),
|
|
\ 'Cancel': function('s:RequestCancel'),
|
|
\ 'resolve': [],
|
|
\ 'reject': [],
|
|
\ 'progress': a:progress,
|
|
\ 'status': 'running'}
|
|
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
|
|
return s:UriFromPath(absolute)
|
|
endfunction
|
|
|
|
function! s:UriFromPath(absolute) abort
|
|
let absolute = a:absolute
|
|
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
|
|
|
|
let s:valid_request_key = '^\%(id\|method\|params\)$'
|
|
function! s:SendRequest(instance, request, ...) abort
|
|
if !has_key(a:instance, 'job') || get(a:instance, 'shutdown', a:request) isnot# a:request
|
|
return s:RejectRequest(a:request, s:error_connection_inactive)
|
|
endif
|
|
let json = filter(copy(a:request), 'v:key =~# s:valid_request_key')
|
|
if empty(s:Send(a:instance, json)) && has_key(a:request, 'id') && has_key(a:instance.requests, a:request.id)
|
|
call s:RejectRequest(remove(a:instance.requests, a:request.id), {'code': -32099, 'message': 'Write failed'})
|
|
endif
|
|
endfunction
|
|
|
|
function! s:RegisterWorkspaceFolderForBuffer(instance, buf) abort
|
|
let root = getbufvar(a:buf, 'workspace_folder')
|
|
if type(root) != v:t_string
|
|
return
|
|
endif
|
|
let root = s:UriFromPath(substitute(root, '[\/]$', '', ''))
|
|
if empty(root) || has_key(a:instance.workspaceFolders, root)
|
|
return
|
|
endif
|
|
let a:instance.workspaceFolders[root] = v:true
|
|
call a:instance.Notify('workspace/didChangeWorkspaceFolders', {'event': {'added': [{'uri': root, 'name': fnamemodify(root, ':t')}], 'removed': []}})
|
|
endfunction
|
|
|
|
function! s:PreprocessParams(instance, params) abort
|
|
let bufnr = v:null
|
|
for doc in filter([get(a:params, 'textDocument', {})], 'type(get(v:val, "uri", "")) == v:t_number')
|
|
let bufnr = doc.uri
|
|
call s:RegisterWorkspaceFolderForBuffer(a:instance, bufnr)
|
|
call extend(doc, a:instance.Attach(bufnr))
|
|
endfor
|
|
let progress_tokens = []
|
|
for key in keys(a:params)
|
|
if key =~# 'Token$' && type(a:params[key]) == v:t_func
|
|
let s:progress_token_id += 1
|
|
let a:instance.progress[s:progress_token_id] = a:params[key]
|
|
call add(progress_tokens, s:progress_token_id)
|
|
let a:params[key] = s:progress_token_id
|
|
endif
|
|
endfor
|
|
return [bufnr, progress_tokens]
|
|
endfunction
|
|
|
|
function! s:VimAttach(bufnr) dict abort
|
|
if !bufloaded(a:bufnr)
|
|
return {'uri': '', 'version': 0}
|
|
endif
|
|
let bufnr = a:bufnr
|
|
let doc = {
|
|
\ 'uri': s:UriFromBufnr(bufnr),
|
|
\ 'version': getbufvar(bufnr, 'changedtick', 0),
|
|
\ 'languageId': getbufvar(bufnr, '&filetype'),
|
|
\ }
|
|
if has_key(self.open_buffers, bufnr) && (
|
|
\ self.open_buffers[bufnr].uri !=# doc.uri ||
|
|
\ self.open_buffers[bufnr].languageId !=# doc.languageId)
|
|
call self.Notify('textDocument/didClose', {'textDocument': {'uri': self.open_buffers[bufnr].uri}})
|
|
call remove(self.open_buffers, bufnr)
|
|
endif
|
|
if !has_key(self.open_buffers, bufnr)
|
|
call self.Notify('textDocument/didOpen', {'textDocument': extend({'text': s:BufferText(bufnr)}, doc)})
|
|
let self.open_buffers[bufnr] = doc
|
|
else
|
|
call self.Notify('textDocument/didChange', {
|
|
\ 'textDocument': {'uri': doc.uri, 'version': doc.version},
|
|
\ 'contentChanges': [{'text': s:BufferText(bufnr)}]})
|
|
let self.open_buffers[bufnr].version = doc.version
|
|
endif
|
|
return doc
|
|
endfunction
|
|
|
|
function! s:VimIsAttached(bufnr) dict abort
|
|
return bufloaded(a:bufnr) && has_key(self.open_buffers, a:bufnr) ? v:true : v:false
|
|
endfunction
|
|
|
|
function! s:VimRequest(method, params, ...) dict abort
|
|
let s:id += 1
|
|
let params = deepcopy(a:params)
|
|
let [_, progress] = s:PreprocessParams(self, params)
|
|
let request = call('s:SetUpRequest', [self, s:id, a:method, params, progress] + a:000)
|
|
call self.AfterInitialized(function('s:SendRequest', [self, request]))
|
|
let self.requests[s:id] = request
|
|
return request
|
|
endfunction
|
|
|
|
function! s:Call(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:Cancel(request) dict abort
|
|
if has_key(self.requests, get(a:request, 'id', ''))
|
|
call self.Notify('$/cancelRequest', {'id': a:request.id})
|
|
call s:RejectRequest(remove(self.requests, a:request.id), s:error_canceled)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:RequestCancel() dict abort
|
|
let instance = self.Client()
|
|
if !empty(instance)
|
|
call instance.Cancel(self)
|
|
elseif get(self, 'status', '') ==# 'running'
|
|
call s:RejectRequest(self, s:error_canceled)
|
|
endif
|
|
return self
|
|
endfunction
|
|
|
|
function! s:DispatchMessage(instance, method, handler, id, params, ...) abort
|
|
try
|
|
let response = {'result': call(a:handler, [a:params, a:instance])}
|
|
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 a:id isnot# v:null
|
|
call s:Send(a:instance, extend({'id': a:id}, response))
|
|
endif
|
|
if !has_key(s:notifications, a:method)
|
|
return response
|
|
endif
|
|
endfunction
|
|
|
|
function! s:OnMessage(instance, body, ...) abort
|
|
if !has_key(a:body, 'method')
|
|
return s:OnResponse(a:instance, 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:instance.methods, request.method)
|
|
return s:DispatchMessage(a:instance, request.method, a:instance.methods[request.method], id, params)
|
|
elseif id isnot# v:null
|
|
call s:Send(a:instance, {"id": id, "error": {"code": -32700, "message": "Method not found: " . request.method}})
|
|
call copilot#logger#Debug('Unexpected request ' . request.method . ' called with ' . json_encode(params))
|
|
elseif request.method !~# '^\$/'
|
|
call copilot#logger#Debug('Unexpected notification ' . request.method . ' called with ' . json_encode(params))
|
|
endif
|
|
endfunction
|
|
|
|
function! s:OnResponse(instance, response, ...) abort
|
|
let response = a:response
|
|
let id = get(a:response, 'id', v:null)
|
|
if !has_key(a:instance.requests, id)
|
|
return
|
|
endif
|
|
let request = remove(a:instance.requests, id)
|
|
for progress_token in request.progress
|
|
if has_key(a:instance.progress, progress_token)
|
|
call remove(a:instance.progress, progress_token)
|
|
endif
|
|
endfor
|
|
if request.status !=# 'running'
|
|
return
|
|
endif
|
|
if has_key(response, 'result')
|
|
let request.waiting = {}
|
|
let resolve = remove(request, 'resolve')
|
|
call remove(request, 'reject')
|
|
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
|
|
call s:RejectRequest(request, response.error)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:OnErr(instance, ch, line, ...) abort
|
|
if !has_key(a:instance, 'serverInfo')
|
|
call copilot#logger#Bare('<-! ' . a:line)
|
|
endif
|
|
endfunction
|
|
|
|
function! s:OnExit(instance, code, ...) abort
|
|
let a:instance.exit_status = a:code
|
|
if has_key(a:instance, 'job')
|
|
call remove(a:instance, 'job')
|
|
endif
|
|
if has_key(a:instance, 'client_id')
|
|
call remove(a:instance, 'client_id')
|
|
endif
|
|
let message = 'Process exited with status ' . a:code
|
|
if a:code >= 18 && a:code < 100
|
|
let message = 'Node.js too old. ' .
|
|
\ (get(a:instance.node, 0, 'node') ==# 'node' ? 'Upgrade' : 'Change g:copilot_node_command') .
|
|
\ ' to ' . a:code . '.x or newer'
|
|
endif
|
|
if !has_key(a:instance, 'serverInfo') && !has_key(a:instance, 'startup_error')
|
|
let a:instance.startup_error = message
|
|
endif
|
|
for id in sort(keys(a:instance.requests), { a, b -> +a > +b })
|
|
call s:RejectRequest(remove(a:instance.requests, id), s:error_exit)
|
|
endfor
|
|
if has_key(a:instance, 'after_initialized')
|
|
let a:instance.AfterInitialized = function('copilot#util#Defer')
|
|
for Fn in remove(a:instance, 'after_initialized')
|
|
call copilot#util#Defer(Fn)
|
|
endfor
|
|
endif
|
|
call copilot#util#Defer({ -> get(s:instances, a:instance.id) is# a:instance ? remove(s:instances, a:instance.id) : {} })
|
|
if a:code == 0
|
|
call copilot#logger#Info(message)
|
|
else
|
|
call copilot#logger#Warn(message)
|
|
if !has_key(a:instance, 'kill')
|
|
call copilot#util#Defer(function('s:Warn'), message)
|
|
endif
|
|
endif
|
|
endfunction
|
|
|
|
function! copilot#client#LspInit(id, initialize_result) abort
|
|
if !has_key(s:instances, a:id)
|
|
return
|
|
endif
|
|
call s:PostInit(a:initialize_result, s:instances[a:id])
|
|
endfunction
|
|
|
|
function! copilot#client#LspExit(id, code, signal) abort
|
|
if !has_key(s:instances, a:id)
|
|
return
|
|
endif
|
|
let instance = remove(s:instances, a:id)
|
|
call s:OnExit(instance, a:code)
|
|
endfunction
|
|
|
|
function! copilot#client#LspResponse(id, opts, ...) abort
|
|
if !has_key(s:instances, a:id)
|
|
return
|
|
endif
|
|
call s:OnResponse(s:instances[a:id], a:opts)
|
|
endfunction
|
|
|
|
function! s:NvimAttach(bufnr) dict abort
|
|
if !bufloaded(a:bufnr)
|
|
return {'uri': '', 'version': 0}
|
|
endif
|
|
call luaeval('pcall(vim.lsp.buf_attach_client, _A[1], _A[2])', [a:bufnr, self.id])
|
|
return luaeval('{uri = vim.uri_from_bufnr(_A), version = vim.lsp.util.buf_versions[_A]}', a:bufnr)
|
|
endfunction
|
|
|
|
function! s:NvimIsAttached(bufnr) dict abort
|
|
return bufloaded(a:bufnr) ? luaeval('vim.lsp.buf_is_attached(_A[1], _A[2])', [a:bufnr, self.id]) : v:false
|
|
endfunction
|
|
|
|
function! s:NvimRequest(method, params, ...) dict abort
|
|
let params = deepcopy(a:params)
|
|
let [bufnr, progress] = s:PreprocessParams(self, params)
|
|
let request = call('s:SetUpRequest', [self, v:null, a:method, params, progress] + a:000)
|
|
call self.AfterInitialized(function('s:NvimDoRequest', [self, request, bufnr]))
|
|
return request
|
|
endfunction
|
|
|
|
function! s:NvimDoRequest(client, request, bufnr) abort
|
|
let request = a:request
|
|
if has_key(a:client, 'client_id') && !has_key(a:client, 'kill')
|
|
let request.id = eval("v:lua.require'_copilot'.lsp_request(a:client.id, a:request.method, a:request.params, a:bufnr)")
|
|
endif
|
|
if request.id isnot# v:null
|
|
let a:client.requests[request.id] = request
|
|
else
|
|
if has_key(a:client, 'client_id')
|
|
call copilot#client#LspExit(a:client.client_id, -1, -1)
|
|
endif
|
|
call copilot#util#Defer(function('s:RejectRequest'), request, s:error_connection_inactive)
|
|
endif
|
|
return request
|
|
endfunction
|
|
|
|
function! s:NvimClose() dict abort
|
|
if !has_key(self, 'client_id')
|
|
return
|
|
endif
|
|
let self.kill = v:true
|
|
return luaeval('vim.lsp.get_client_by_id(_A).stop()', self.client_id)
|
|
endfunction
|
|
|
|
function! s:NvimNotify(method, params) dict abort
|
|
call self.AfterInitialized(function('s:NvimDoNotify', [self.client_id, a:method, a:params]))
|
|
endfunction
|
|
|
|
function! s:NvimDoNotify(client_id, method, params) abort
|
|
return eval("v:lua.require'_copilot'.rpc_notify(a:client_id, a:method, a:params)")
|
|
endfunction
|
|
|
|
function! copilot#client#LspHandle(id, request) abort
|
|
if !has_key(s:instances, a:id)
|
|
return
|
|
endif
|
|
return s:OnMessage(s:instances[a:id], a:request)
|
|
endfunction
|
|
|
|
let s:script_name = 'dist/language-server.js'
|
|
function! s:Command() abort
|
|
if !has('nvim-0.7') && v:version < 900
|
|
return [[], [], 'Vim version too old']
|
|
endif
|
|
let script = get(g:, 'copilot_command', '')
|
|
if type(script) == type('')
|
|
let script = [expand(script)]
|
|
endif
|
|
if empty(script) || !filereadable(script[0])
|
|
let script = [s:root . '/' . s:script_name]
|
|
if !filereadable(script[0])
|
|
return [[], [], 'Could not find ' . s:script_name . ' (bad install?)']
|
|
endif
|
|
elseif script[0] !~# '\.js$'
|
|
return [[], script + ['--stdio'], '']
|
|
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 [[], [], 'Node.js not found in PATH']
|
|
else
|
|
return [[], [], 'Node.js executable `' . get(node, 0, '') . "' not found"]
|
|
endif
|
|
endif
|
|
return [node, script + ['--stdio'], '']
|
|
endfunction
|
|
|
|
function! s:UrlDecode(str) abort
|
|
return substitute(a:str, '%\(\x\x\)', '\=iconv(nr2char("0x".submatch(1)), "utf-8", "latin1")', 'g')
|
|
endfunction
|
|
|
|
function! copilot#client#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 % 10000) : '')
|
|
endif
|
|
endif
|
|
return {'name': has('nvim') ? 'Neovim': 'Vim', 'version': s:editor_version}
|
|
endfunction
|
|
|
|
function! copilot#client#EditorPluginInfo() abort
|
|
return {'name': 'copilot.vim', 'version': s:plugin_version}
|
|
endfunction
|
|
|
|
function! copilot#client#Settings() abort
|
|
let settings = {
|
|
\ 'http': {
|
|
\ 'proxy': get(g:, 'copilot_proxy', v:null),
|
|
\ 'proxyStrictSSL': get(g:, 'copilot_proxy_strict_ssl', v:null)},
|
|
\ 'github-enterprise': {'uri': get(g:, 'copilot_auth_provider_url', v:null)},
|
|
\ }
|
|
if type(settings.http.proxy) ==# v:t_string && settings.http.proxy =~# '^[^/]\+$'
|
|
let settings.http.proxy = 'http://' . settings.http.proxy
|
|
endif
|
|
if type(get(g:, 'copilot_settings')) == v:t_dict
|
|
call extend(settings, g:copilot_settings)
|
|
endif
|
|
return settings
|
|
endfunction
|
|
|
|
function! s:PostInit(result, instance) abort
|
|
let a:instance.serverInfo = get(a:result, 'serverInfo', {})
|
|
if !has_key(a:instance, 'node_version') && has_key(a:result.serverInfo, 'nodeVersion')
|
|
let a:instance.node_version = a:result.serverInfo.nodeVersion
|
|
endif
|
|
let a:instance.AfterInitialized = function('copilot#util#Defer')
|
|
for Fn in remove(a:instance, 'after_initialized')
|
|
call copilot#util#Defer(Fn)
|
|
endfor
|
|
endfunction
|
|
|
|
function! s:InitializeResult(result, instance) abort
|
|
call s:Send(a:instance, {'method': 'initialized', 'params': {}})
|
|
call s:PostInit(a:result, a:instance)
|
|
endfunction
|
|
|
|
function! s:InitializeError(error, instance) abort
|
|
if !has_key(a:instance, 'startup_error')
|
|
let a:instance.startup_error = 'Unexpected error E' . a:error.code . ' initializing language server: ' . a:error.message
|
|
call a:instance.Close()
|
|
endif
|
|
endfunction
|
|
|
|
function! s:StartupError() dict abort
|
|
while (has_key(self, 'job') || has_key(self, 'client_id')) && !has_key(self, 'startup_error') && !has_key(self, 'serverInfo')
|
|
sleep 10m
|
|
endwhile
|
|
if has_key(self, 'serverInfo')
|
|
return ''
|
|
else
|
|
return get(self, 'startup_error', 'Something unexpected went wrong spawning the language server')
|
|
endif
|
|
endfunction
|
|
|
|
function! s:StatusNotification(params, instance) abort
|
|
let a:instance.status = a:params
|
|
endfunction
|
|
|
|
function! s:Nop(...) abort
|
|
return v:null
|
|
endfunction
|
|
|
|
function! s:False(...) abort
|
|
return v:false
|
|
endfunction
|
|
|
|
function! s:Progress(params, instance) abort
|
|
if has_key(a:instance.progress, a:params.token)
|
|
call a:instance.progress[a:params.token](a:params.value)
|
|
endif
|
|
endfunction
|
|
|
|
let s:notifications = {
|
|
\ '$/progress': function('s:Progress'),
|
|
\ 'featureFlagsNotification': function('s:Nop'),
|
|
\ 'statusNotification': function('s:StatusNotification'),
|
|
\ 'window/logMessage': function('copilot#handlers#window_logMessage'),
|
|
\ }
|
|
|
|
let s:vim_handlers = {
|
|
\ 'window/showMessageRequest': function('copilot#handlers#window_showMessageRequest'),
|
|
\ 'window/showDocument': function('copilot#handlers#window_showDocument'),
|
|
\ }
|
|
|
|
let s:vim_capabilities = {
|
|
\ 'workspace': {'workspaceFolders': v:true},
|
|
\ 'window': {'showDocument': {'support': v:true}},
|
|
\ }
|
|
|
|
function! copilot#client#New(...) abort
|
|
let opts = a:0 ? a:1 : {}
|
|
let instance = {'requests': {},
|
|
\ 'progress': {},
|
|
\ 'workspaceFolders': {},
|
|
\ 'after_initialized': [],
|
|
\ 'status': {'status': 'Starting', 'message': ''},
|
|
\ 'AfterInitialized': function('s:AfterInitialized'),
|
|
\ 'Close': function('s:Nop'),
|
|
\ 'Notify': function('s:False'),
|
|
\ 'Request': function('s:VimRequest'),
|
|
\ 'Attach': function('s:Nop'),
|
|
\ 'IsAttached': function('s:False'),
|
|
\ 'Call': function('s:Call'),
|
|
\ 'Cancel': function('s:Cancel'),
|
|
\ 'StartupError': function('s:StartupError'),
|
|
\ }
|
|
let instance.methods = copy(s:notifications)
|
|
let [node, argv, command_error] = s:Command()
|
|
if !empty(command_error)
|
|
let instance.id = -1
|
|
let instance.startup_error = command_error
|
|
call copilot#logger#Error(command_error)
|
|
return instance
|
|
endif
|
|
let instance.node = node
|
|
let command = node + argv
|
|
let opts = {}
|
|
let opts.initializationOptions = {
|
|
\ 'editorInfo': copilot#client#EditorInfo(),
|
|
\ 'editorPluginInfo': copilot#client#EditorPluginInfo(),
|
|
\ }
|
|
let opts.workspaceFolders = []
|
|
let settings = extend(copilot#client#Settings(), get(opts, 'editorConfiguration', {}))
|
|
if type(get(g:, 'copilot_workspace_folders')) == v:t_list
|
|
for folder in g:copilot_workspace_folders
|
|
if type(folder) == v:t_string && !empty(folder) && folder !~# '\*\*\|^/$'
|
|
for path in glob(folder . '/', 0, 1)
|
|
let uri = s:UriFromPath(substitute(path, '[\/]*$', '', ''))
|
|
call add(opts.workspaceFolders, {'uri': uri, 'name': fnamemodify(uri, ':t')})
|
|
endfor
|
|
elseif type(folder) == v:t_dict && has_key(v:t_dict, 'uri') && !empty(folder.uri) && has_key(folder, 'name')
|
|
call add(opts.workspaceFolders, folder)
|
|
endif
|
|
endfor
|
|
endif
|
|
for folder in opts.workspaceFolders
|
|
let instance.workspaceFolders[folder.uri] = v:true
|
|
endfor
|
|
if has('nvim')
|
|
call extend(instance, {
|
|
\ 'Close': function('s:NvimClose'),
|
|
\ 'Notify': function('s:NvimNotify'),
|
|
\ 'Request': function('s:NvimRequest'),
|
|
\ 'Attach': function('s:NvimAttach'),
|
|
\ 'IsAttached': function('s:NvimIsAttached'),
|
|
\ })
|
|
let instance.client_id = eval("v:lua.require'_copilot'.lsp_start_client(command, keys(instance.methods), opts, settings)")
|
|
let instance.id = instance.client_id
|
|
else
|
|
call extend(instance, {
|
|
\ 'Close': function('s:VimClose'),
|
|
\ 'Notify': function('s:VimNotify'),
|
|
\ 'Attach': function('s:VimAttach'),
|
|
\ 'IsAttached': function('s:VimIsAttached'),
|
|
\ })
|
|
let state = {'headers': {}, 'mode': 'headers', 'buffer': ''}
|
|
let instance.open_buffers = {}
|
|
let instance.methods = extend(s:vim_handlers, instance.methods)
|
|
let instance.job = job_start(command, {
|
|
\ 'cwd': copilot#job#Cwd(),
|
|
\ 'noblock': 1,
|
|
\ 'stoponexit': '',
|
|
\ 'in_mode': 'lsp',
|
|
\ 'out_mode': 'lsp',
|
|
\ 'out_cb': { j, d -> copilot#util#Defer(function('s:OnMessage'), instance, d) },
|
|
\ 'err_cb': function('s:OnErr', [instance]),
|
|
\ 'exit_cb': { j, d -> copilot#util#Defer(function('s:OnExit'), instance, d) },
|
|
\ })
|
|
let instance.id = job_info(instance.job).process
|
|
let opts.capabilities = s:vim_capabilities
|
|
let opts.processId = getpid()
|
|
let request = instance.Request('initialize', opts, function('s:InitializeResult'), function('s:InitializeError'), instance)
|
|
call call(remove(instance.after_initialized, 0), [])
|
|
call instance.Notify('workspace/didChangeConfiguration', {'settings': settings})
|
|
endif
|
|
let s:instances[instance.id] = instance
|
|
return instance
|
|
endfunction
|
|
|
|
function! copilot#client#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#client#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#client#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_close
|
|
autocmd!
|
|
if !has('nvim')
|
|
autocmd BufUnload * call s:CloseBuffer(+expand('<abuf>'))
|
|
endif
|
|
augroup END
|