1
0
mirror of https://github.com/amix/vimrc synced 2025-07-06 16:05:01 +08:00

Updated plugins

This commit is contained in:
Amir
2024-10-06 10:25:50 +02:00
parent ee7e062909
commit 46294d589d
202 changed files with 306918 additions and 203617 deletions

View File

@ -1,603 +0,0 @@
if exists('g:autoloaded_copilot_agent')
finish
endif
let g:autoloaded_copilot_agent = 1
scriptencoding utf-8
let s:plugin_version = '1.13.0'
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(' . 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, 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()
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, 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 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
let node_version = s:GetNodeVersion(node)
let warning = ''
if !get(g:, 'copilot_ignore_node_version') && 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
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, node_version.string, 'Could not find dist/agent.js (bad install?)']
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
let instance.node_version = node_version
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

View File

@ -0,0 +1,764 @@
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

View File

@ -1,116 +0,0 @@
if exists('g:autoloaded_copilot_prompt')
finish
endif
let g:autoloaded_copilot_prompt = 1
scriptencoding utf-8
let s:slash = exists('+shellslash') ? '\' : '/'
function copilot#doc#UTF16Width(str) abort
return strchars(substitute(a:str, "\\%#=2[^\u0001-\uffff]", " ", 'g'))
endfunction
if exists('*utf16idx')
function! copilot#doc#UTF16ToByteIdx(str, utf16_idx) abort
return byteidx(a:str, a:utf16_idx, 1)
endfunction
elseif has('nvim')
function! copilot#doc#UTF16ToByteIdx(str, utf16_idx) abort
try
return v:lua.vim.str_byteindex(a:str, a:utf16_idx, 1)
catch /^Vim(return):E5108:/
return -1
endtry
endfunction
else
function! copilot#doc#UTF16ToByteIdx(str, utf16_idx) abort
if copilot#doc#UTF16Width(a:str) < a:utf16_idx
return -1
endif
let end_offset = len(a:str)
while copilot#doc#UTF16Width(strpart(a:str, 0, end_offset)) > a:utf16_idx && end_offset > 0
let end_offset -= 1
endwhile
return end_offset
endfunction
endif
let s:language_normalization_map = {
\ "bash": "shellscript",
\ "bst": "bibtex",
\ "cs": "csharp",
\ "cuda": "cuda-cpp",
\ "dosbatch": "bat",
\ "dosini": "ini",
\ "gitcommit": "git-commit",
\ "gitrebase": "git-rebase",
\ "make": "makefile",
\ "objc": "objective-c",
\ "objcpp": "objective-cpp",
\ "ps1": "powershell",
\ "raku": "perl6",
\ "sh": "shellscript",
\ "text": "plaintext",
\ }
function copilot#doc#LanguageForFileType(filetype) abort
let filetype = substitute(a:filetype, '\..*', '', '')
return get(s:language_normalization_map, empty(filetype) ? "text" : filetype, filetype)
endfunction
function! s:RelativePath(absolute) abort
if exists('b:copilot_relative_path')
return b:copilot_relative_path
elseif exists('b:copilot_root')
let root = b:copilot_root
elseif len(get(b:, 'projectionist', {}))
let root = sort(keys(b:projectionist), { a, b -> a < b })[0]
else
let root = getcwd()
endif
let root = tr(root, s:slash, '/') . '/'
if strpart(tr(a:absolute, 'A-Z', 'a-z'), 0, len(root)) ==# tr(root, 'A-Z', 'a-z')
return strpart(a:absolute, len(root))
else
return fnamemodify(a:absolute, ':t')
endif
endfunction
function! copilot#doc#Get() abort
let absolute = tr(@%, s:slash, '/')
if absolute !~# '^\a\+:\|^/\|^$' && &buftype =~# '^\%(nowrite\)\=$'
let absolute = substitute(tr(getcwd(), s:slash, '/'), '/\=$', '/', '') . absolute
endif
let doc = {
\ 'uri': bufnr(''),
\ 'version': getbufvar('', 'changedtick'),
\ 'relativePath': s:RelativePath(absolute),
\ 'insertSpaces': &expandtab ? v:true : v:false,
\ 'tabSize': shiftwidth(),
\ 'indentSize': shiftwidth(),
\ }
let line = getline('.')
let col_byte = col('.') - (mode() =~# '^[iR]' || empty(line))
let col_utf16 = copilot#doc#UTF16Width(strpart(line, 0, col_byte))
let doc.position = {'line': line('.') - 1, 'character': col_utf16}
return doc
endfunction
function! copilot#doc#Params(...) abort
let extra = a:0 ? a:1 : {}
let params = extend({'doc': extend(copilot#doc#Get(), get(extra, 'doc', {}))}, extra, 'keep')
let params.textDocument = {
\ 'uri': params.doc.uri,
\ 'version': params.doc.version,
\ 'relativePath': params.doc.relativePath,
\ }
let params.position = params.doc.position
return params
endfunction

View File

@ -0,0 +1,31 @@
function! copilot#handlers#window_logMessage(params, ...) abort
call copilot#logger#Raw(get(a:params, 'type', 6), get(a:params, 'message', ''))
endfunction
function! copilot#handlers#window_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:BrowserCallback(into, code) abort
let a:into.code = a:code
endfunction
function! copilot#handlers#window_showDocument(params, ...) abort
echo a:params.uri
if empty(get(a:params, 'external'))
return {'success': v:false}
endif
let browser = copilot#Browser()
if empty(browser)
return {'success': v:false}
endif
let status = {}
call copilot#job#Stream(browser + [a:params.uri], v:null, v:null, function('s:BrowserCallback', [status]))
let time = reltime()
while empty(status) && reltimefloat(reltime(time)) < 1
sleep 10m
endwhile
return {'success': get(status, 'code') ? v:false : v:true}
endfunction

View File

@ -1,11 +1,6 @@
if exists('g:autoloaded_copilot_job')
finish
endif
let g:autoloaded_copilot_job = 1
scriptencoding utf-8
function copilot#job#Nop(...) abort
function! copilot#job#Nop(...) abort
endfunction
function! s:Jobs(job_or_jobs) abort

View File

@ -1,8 +1,3 @@
if exists('g:autoloaded_copilot_log')
finish
endif
let g:autoloaded_copilot_log = 1
if !exists('s:log_file')
let s:log_file = tempname() . '-copilot.log'
try
@ -11,35 +6,59 @@ if !exists('s:log_file')
endtry
endif
function! copilot#logger#File() abort
return s:log_file
let s:logs = []
function! copilot#logger#BufReadCmd() abort
try
setlocal modifiable noreadonly
silent call deletebufline('', 1, '$')
if !empty(s:logs)
call setline(1, s:logs)
endif
finally
setlocal buftype=nofile bufhidden=wipe nobuflisted nomodified nomodifiable
endtry
endfunction
let s:level_prefixes = ['', '[ERROR] ', '[WARN] ', '[INFO] ', '[DEBUG] ', '[DEBUG] ']
function! copilot#logger#Raw(level, message) abort
if $COPILOT_AGENT_VERBOSE !~# '^\%(1\|true\)$' && a:level < 1
return
endif
let lines = type(a:message) == v:t_list ? copy(a:message) : split(a:message, "\n", 1)
let lines[0] = strftime('[%Y-%m-%d %H:%M:%S] ') . get(s:level_prefixes, a:level, '[UNKNOWN] ') . get(lines, 0, '')
try
if !filewritable(s:log_file)
return
endif
call map(lines, { k, L -> type(L) == v:t_func ? call(L, []) : L })
call writefile(lines, s:log_file, 'a')
call extend(s:logs, lines)
let overflow = len(s:logs) - get(g:, 'copilot_log_history', 10000)
if overflow > 0
call remove(s:logs, 0, overflow - 1)
endif
let bufnr = bufnr('copilot:///log')
if bufnr > 0 && bufloaded(bufnr)
call setbufvar(bufnr, '&modifiable', 1)
call setbufline(bufnr, 1, s:logs)
call setbufvar(bufnr, '&modifiable', 0)
for winid in win_findbuf(bufnr)
if has('nvim') && winid != win_getid()
call nvim_win_set_cursor(winid, [len(s:logs), 0])
endif
endfor
endif
catch
endtry
endfunction
function! copilot#logger#Trace(...) abort
call copilot#logger#Raw(-1, a:000)
endfunction
function! copilot#logger#Debug(...) abort
call copilot#logger#Raw(0, a:000)
if empty(get(g:, 'copilot_debug'))
return
endif
call copilot#logger#Raw(4, a:000)
endfunction
function! copilot#logger#Info(...) abort
call copilot#logger#Raw(1, a:000)
call copilot#logger#Raw(3, a:000)
endfunction
function! copilot#logger#Warn(...) abort
@ -47,26 +66,40 @@ function! copilot#logger#Warn(...) abort
endfunction
function! copilot#logger#Error(...) abort
call copilot#logger#Raw(3, a:000)
call copilot#logger#Raw(1, a:000)
endfunction
function! copilot#logger#Exception() abort
function! copilot#logger#Bare(...) abort
call copilot#logger#Raw(0, a:000)
endfunction
function! copilot#logger#Exception(...) abort
if !empty(v:exception) && v:exception !=# 'Vim:Interrupt'
call copilot#logger#Error('Exception: ' . v:exception . ' @ ' . v:throwpoint)
let agent = copilot#RunningAgent()
if !empty(agent)
let client = copilot#RunningClient()
if !empty(client)
let [_, type, code, message; __] = matchlist(v:exception, '^\%(\(^[[:alnum:]_#]\+\)\%((\a\+)\)\=\%(\(:E-\=\d\+\)\)\=:\s*\)\=\(.*\)$')
let stacklines = []
for frame in split(substitute(substitute(v:throwpoint, ', \S\+ \(\d\+\)$', '[\1]', ''), '^function ', '', ''), '\.\@<!\.\.\.\@!')
if frame =~# '[\/]'
call add(stacklines, '[redacted]')
for frame in split(substitute(v:throwpoint, ', \S\+ \(\d\+\)$', '[\1]', ''), '\.\@<!\.\.\.\@!')
let fn_line = matchlist(frame, '^\%(function \)\=\(\S\+\)\[\(\d\+\)\]$')
if !empty(fn_line)
call add(stacklines, {'function': substitute(fn_line[1], '^<SNR>\d\+_', '<SID>', ''), 'lineno': +fn_line[2]})
elseif frame =~# ' Autocmds for "\*"$'
call add(stacklines, {'function': frame})
elseif frame =~# ' Autocmds for ".*"$'
call add(stacklines, {'function': substitute(frame, ' for ".*"$', ' for "[redacted]"', '')})
else
call add(stacklines, substitute(frame, '^<SNR>\d\+_', '<SID>', ''))
call add(stacklines, {'function': '[redacted]'})
endif
endfor
call agent.Request('telemetry/exception', {
\ 'origin': 'copilot.vim',
\ 'stacktrace': join([v:exception] + stacklines, "\n")
\ })
return client.Request('telemetry/exception', {
\ 'transaction': a:0 ? a:1 : '',
\ 'platform': 'other',
\ 'exception_detail': [{
\ 'type': type . code,
\ 'value': message,
\ 'stacktrace': stacklines}]
\ }, v:null, function('copilot#util#Nop'))
endif
endif
endfunction

View File

@ -1,8 +1,3 @@
if exists('g:autoloaded_copilot_panel')
finish
endif
let g:autoloaded_copilot_panel = 1
scriptencoding utf-8
if !exists('s:panel_id')
@ -11,29 +6,35 @@ endif
let s:separator = repeat('─', 72)
function! s:Solutions(state) abort
return sort(values(get(a:state, 'solutions', {})), { a, b -> a.score < b.score })
endfunction
function! s:Render(panel_id) abort
let bufnr = bufnr('^' . a:panel_id . '$')
let state = getbufvar(bufnr, 'copilot_panel')
if !bufloaded(bufnr) || type(state) != v:t_dict
function! s:Render(state) abort
let bufnr = bufnr('^' . a:state.panel . '$')
let state = a:state
if !bufloaded(bufnr)
return
endif
let sorted = s:Solutions(state)
if !empty(get(state, 'status', ''))
let lines = ['Error: ' . state.status]
let sorted = a:state.items
if !empty(get(a:state, 'error'))
let lines = ['Error: ' . a:state.error.message]
let sorted = []
elseif get(a:state, 'percentage') == 100
let lines = ['Synthesized ' . (len(sorted) == 1 ? '1 completion' : len(sorted) . ' completions')]
else
let target = get(state, 'count_target', '?')
let received = has_key(state, 'status') ? target : len(sorted)
let lines = ['Synthesiz' . (has_key(state, 'status') ? 'ed ' : 'ing ') . received . '/' . target . ' solutions (Duplicates hidden)']
let lines = [substitute('Synthesizing ' . matchstr(get(a:state, 'message', ''), '\d\+\%(/\d\+\)\=') . ' completions', ' \+', ' ', 'g')]
endif
if len(sorted)
call add(lines, 'Press <CR> on a solution to accept')
call add(lines, 'Press <CR> on a completion to accept')
endif
for solution in sorted
let lines += [s:separator] + split(solution.displayText, "\n", 1)
let leads = {}
for item in sorted
let insert = split(item.insertText, "\r\n\\=\\|\n", 1)
let insert[0] = strpart(a:state.line, 0, copilot#util#UTF16ToByteIdx(a:state.line, item.range.start.character)) . insert[0]
let lines += [s:separator] + insert
if !has_key(leads, string(item.range.start))
let match = insert[0 : a:state.position.line - item.range.start.line]
let match[-1] = strpart(match[-1], 0, copilot#util#UTF16ToByteIdx(match[-1], a:state.position.character))
call map(match, { k, v -> escape(v, '][^$.*\~') })
let leads[string(item.range.start)] = join(match, '\n')
endif
endfor
try
call setbufvar(bufnr, '&modifiable', 1)
@ -41,64 +42,61 @@ function! s:Render(panel_id) abort
call setbufline(bufnr, 1, lines)
finally
call setbufvar(bufnr, '&modifiable', 0)
call setbufvar(bufnr, '&readonly', 1)
endtry
call clearmatches()
call matchadd('CopilotSuggestion', '\C^' . s:separator . '\n\zs\%(' . join(sort(values(leads), { a, b -> len(b) - len(a) }), '\|') . '\)', 10, 4)
endfunction
function! copilot#panel#Solution(params, ...) abort
let state = getbufvar('^' . a:params.panelId . '$', 'copilot_panel')
if !bufloaded(a:params.panelId) || type(state) != v:t_dict
return
endif
let state.solutions[a:params.solutionId] = a:params
call s:Render(a:params.panelId)
function! s:PartialResult(state, value) abort
let items = type(a:value) == v:t_list ? a:value : a:value.items
call extend(a:state.items, items)
call s:Render(a:state)
endfunction
function! copilot#panel#SolutionsDone(params, ...) abort
let state = getbufvar('^' . a:params.panelId . '$', 'copilot_panel')
if !bufloaded(a:params.panelId) || type(state) != v:t_dict
call copilot#logger#Debug('SolutionsDone: ' . a:params.panelId)
return
function! s:WorkDone(state, value) abort
if has_key(a:value, 'message')
let a:state.message = a:value.message
endif
if has_key(a:value, 'percentage')
let a:state.percentage = a:value.percentage
call s:Render(a:state)
endif
let state.status = get(a:params, 'message', '')
call s:Render(a:params.panelId)
endfunction
function! copilot#panel#Accept(...) abort
let state = get(b:, 'copilot_panel', {})
let solutions = s:Solutions(state)
if empty(solutions)
if empty(state.items)
return ''
endif
if !has_key(state, 'bufnr') || !bufloaded(get(state, 'bufnr', -1))
return "echoerr 'Buffer was closed'"
endif
let at = a:0 ? a:1 : line('.')
let solution_index = 0
let index = 0
for lnum in range(1, at)
if getline(lnum) ==# s:separator
let solution_index += 1
let index += 1
endif
endfor
if solution_index > 0 && solution_index <= len(solutions)
let solution = solutions[solution_index - 1]
let lnum = solution.range.start.line + 1
if index > 0 && index <= len(state.items)
let item = state.items[index - 1]
let lnum = item.range.start.line + 1
if getbufline(state.bufnr, lnum) !=# [state.line]
return 'echoerr "Buffer has changed since synthesizing solution"'
return 'echoerr "Buffer has changed since synthesizing completion"'
endif
let lines = split(solution.displayText, "\n", 1)
let old_first = getline(solution.range.start.line + 1)
let lines[0] = strpart(old_first, 0, copilot#doc#UTF16ToByteIdx(old_first, solution.range.start.character)) . lines[0]
let old_last = getline(solution.range.end.line + 1)
let lines[-1] .= strpart(old_last, copilot#doc#UTF16ToByteIdx(old_last, solution.range.start.character))
call setbufline(state.bufnr, solution.range.start.line + 1, lines[0])
call appendbufline(state.bufnr, solution.range.start.line + 1, lines[1:-1])
call copilot#Request('notifyAccepted', {'uuid': solution.solutionId})
let lines = split(item.insertText, "\n", 1)
let old_first = getbufline(state.bufnr, item.range.start.line + 1)[0]
let lines[0] = strpart(old_first, 0, copilot#util#UTF16ToByteIdx(old_first, item.range.start.character)) . lines[0]
let old_last = getbufline(state.bufnr, item.range.end.line + 1)[0]
let lines[-1] .= strpart(old_last, copilot#util#UTF16ToByteIdx(old_last, item.range.end.character))
call deletebufline(state.bufnr, item.range.start.line + 1, item.range.end.line + 1)
call appendbufline(state.bufnr, item.range.start.line, lines)
call copilot#Request('workspace/executeCommand', item.command)
bwipeout
let win = bufwinnr(state.bufnr)
if win > 0
exe win . 'wincmd w'
exe solution.range.start.line + len(lines)
exe item.range.start.line + len(lines)
if state.was_insert
startinsert!
else
@ -112,49 +110,58 @@ endfunction
function! s:Initialize(state) abort
let &l:filetype = 'copilot' . (empty(a:state.filetype) ? '' : '.' . a:state.filetype)
let &l:tabstop = a:state.tabstop
call clearmatches()
call matchadd('CopilotSuggestion', '\C^' . s:separator . '\n\zs' . escape(a:state.line, '][^$.*\~'), 10, 4)
nmap <buffer><script> <CR> <Cmd>exe copilot#panel#Accept()<CR>
nmap <buffer><script> [[ <Cmd>call search('^─\{9,}\n.', 'bWe')<CR>
nmap <buffer><script> ]] <Cmd>call search('^─\{9,}\n.', 'We')<CR>
endfunction
function! s:BufReadCmd() abort
setlocal bufhidden=wipe buftype=nofile nobuflisted readonly nomodifiable
setlocal bufhidden=wipe buftype=nofile nobuflisted nomodifiable
let state = get(b:, 'copilot_panel')
if type(state) != v:t_dict
return
endif
call s:Initialize(state)
call s:Render(expand('<amatch>'))
call s:Render(state)
return ''
endfunction
function! s:Result(state, result) abort
let a:state.percentage = 100
call s:PartialResult(a:state, a:result)
endfunction
function! s:Error(state, error) abort
let a:state.error = a:error
call s:Render(a:state)
endfunction
function! copilot#panel#Open(opts) abort
let s:panel_id += 1
let state = {'solutions': {}, 'filetype': &filetype, 'line': getline('.'), 'bufnr': bufnr(''), 'tabstop': &tabstop}
let bufname = 'copilot:///' . s:panel_id
let params = copilot#doc#Params({'panelId': bufname})
let state.was_insert = mode() =~# '^[iR]'
let state = {'items': [], 'filetype': &filetype, 'was_insert': mode() =~# '^[iR]', 'bufnr': bufnr(''), 'tabstop': &tabstop}
let state.panel = 'copilot:///panel/' . s:panel_id
if state.was_insert
let state.position = copilot#util#AppendPosition()
stopinsert
else
let params.doc.position.character = copilot#doc#UTF16Width(state.line)
let params.position.character = params.doc.position.character
let state.position = {'line': a:opts.line1 >= 1 ? a:opts.line1 - 1 : 0, 'character': copilot#util#UTF16Width(getline('.'))}
endif
let response = copilot#Request('getPanelCompletions', params).Wait()
if response.status ==# 'error'
return 'echoerr ' . string(response.error.message)
endif
let state.count_target = response.result.solutionCountTarget
exe substitute(a:opts.mods, '\C\<tab\>', '-tab', 'g') 'keepalt split' bufname
let state.line = getline(state.position.line + 1)
let params = {
\ 'textDocument': {'uri': state.bufnr},
\ 'position': state.position,
\ 'partialResultToken': function('s:PartialResult', [state]),
\ 'workDoneToken': function('s:WorkDone', [state]),
\ }
let response = copilot#Request('textDocument/copilotPanelCompletion', params, function('s:Result', [state]), function('s:Error', [state]))
exe substitute(a:opts.mods, '\C\<tab\>', '-tab', 'g') 'keepalt split' state.panel
let b:copilot_panel = state
call s:Initialize(state)
call s:Render(@%)
call s:Render(state)
return ''
endfunction
augroup github_copilot_panel
autocmd!
autocmd BufReadCmd copilot:///* exe s:BufReadCmd()
autocmd BufReadCmd copilot:///panel/* exe s:BufReadCmd()
augroup END

View File

@ -0,0 +1,61 @@
let s:deferred = []
function! copilot#util#Nop(...) abort
return v:null
endfunction
function! copilot#util#Defer(fn, ...) abort
call add(s:deferred, function(a:fn, a:000))
return timer_start(0, function('s:RunDeferred'))
endfunction
function! s:RunDeferred(...) abort
if empty(s:deferred)
return
endif
let Fn = remove(s:deferred, 0)
call timer_start(0, function('s:RunDeferred'))
call call(Fn, [])
endfunction
function! copilot#util#UTF16Width(str) abort
return strchars(substitute(a:str, "\\%#=2[^\u0001-\uffff]", " ", 'g'))
endfunction
if exists('*utf16idx')
function! copilot#util#UTF16ToByteIdx(str, utf16_idx) abort
return byteidx(a:str, a:utf16_idx, 1)
endfunction
elseif has('nvim')
function! copilot#util#UTF16ToByteIdx(str, utf16_idx) abort
try
return v:lua.vim.str_byteindex(a:str, a:utf16_idx, 1)
catch /^Vim(return):E5108:/
return -1
endtry
endfunction
else
function! copilot#util#UTF16ToByteIdx(str, utf16_idx) abort
if copilot#util#UTF16Width(a:str) < a:utf16_idx
return -1
endif
let end_offset = len(a:str)
while copilot#util#UTF16Width(strpart(a:str, 0, end_offset)) > a:utf16_idx && end_offset > 0
let end_offset -= 1
endwhile
return end_offset
endfunction
endif
function! copilot#util#AppendPosition() abort
let line = getline('.')
let col_byte = col('.') - (mode() =~# '^[iR]' || empty(line))
let col_utf16 = copilot#util#UTF16Width(strpart(line, 0, col_byte))
return {'line': line('.') - 1, 'character': col_utf16}
endfunction

View File

@ -1,3 +1,3 @@
function! copilot#version#String() abort
return '1.18.0'
return '1.40.0'
endfunction