scriptencoding utf-8 let s:has_nvim_ghost_text = has('nvim-0.7') && exists('*nvim_buf_get_mark') let s:vim_minimum_version = '9.0.0185' let s:has_vim_ghost_text = has('patch-' . s:vim_minimum_version) && has('textprop') let s:has_ghost_text = s:has_nvim_ghost_text || s:has_vim_ghost_text let s:hlgroup = 'CopilotSuggestion' let s:annot_hlgroup = 'CopilotAnnotation' if s:has_vim_ghost_text && empty(prop_type_get(s:hlgroup)) call prop_type_add(s:hlgroup, {'highlight': s:hlgroup}) endif if s:has_vim_ghost_text && empty(prop_type_get(s:annot_hlgroup)) call prop_type_add(s:annot_hlgroup, {'highlight': s:annot_hlgroup}) endif function! s:Echo(msg) abort if has('nvim') && &cmdheight == 0 call v:lua.vim.notify(a:msg, v:null, {'title': 'GitHub Copilot'}) else echo a:msg endif endfunction function! s:EditorConfiguration() abort let filetypes = copy(s:filetype_defaults) if type(get(g:, 'copilot_filetypes')) == v:t_dict call extend(filetypes, g:copilot_filetypes) endif return { \ 'enableAutoCompletions': empty(get(g:, 'copilot_enabled', 1)) ? v:false : v:true, \ 'disabledLanguages': map(sort(keys(filter(filetypes, { k, v -> empty(v) }))), { _, v -> {'languageId': v}}), \ } endfunction function! copilot#Init(...) abort call copilot#util#Defer({ -> exists('s:client') || s:Start() }) endfunction function! s:Running() abort return exists('s:client.job') || exists('s:client.client_id') endfunction function! s:Start() abort if s:Running() || exists('s:client.startup_error') return endif let s:client = copilot#client#New({'editorConfiguration' : s:EditorConfiguration()}) endfunction function! s:Stop() abort if exists('s:client') let client = remove(s:, 'client') call client.Close() endif endfunction function! copilot#Client() abort call s:Start() return s:client endfunction function! copilot#RunningClient() abort if s:Running() return s:client else return v:null endif endfunction if has('nvim-0.7') && !has(luaeval('vim.version().api_prerelease') ? 'nvim-0.8.1' : 'nvim-0.8.0') let s:editor_warning = 'Neovim 0.7 support is deprecated and will be dropped in a future release of copilot.vim.' endif if has('vim_starting') && exists('s:editor_warning') call copilot#logger#Warn(s:editor_warning) endif function! s:EditorVersionWarning() abort if exists('s:editor_warning') echohl WarningMsg echo 'Warning: ' . s:editor_warning echohl None endif endfunction function! copilot#Request(method, params, ...) abort let client = copilot#Client() return call(client.Request, [a:method, a:params] + a:000) endfunction function! copilot#Call(method, params, ...) abort let client = copilot#Client() return call(client.Call, [a:method, a:params] + a:000) endfunction function! copilot#Notify(method, params, ...) abort let client = copilot#Client() return call(client.Notify, [a:method, a:params] + a:000) endfunction function! copilot#NvimNs() abort return nvim_create_namespace('github-copilot') endfunction function! copilot#Clear() abort if exists('g:_copilot_timer') call timer_stop(remove(g:, '_copilot_timer')) endif if exists('b:_copilot') call copilot#client#Cancel(get(b:_copilot, 'first', {})) call copilot#client#Cancel(get(b:_copilot, 'cycling', {})) endif call s:UpdatePreview() unlet! b:_copilot return '' endfunction function! copilot#Dismiss() abort call copilot#Clear() call s:UpdatePreview() return '' endfunction let s:filetype_defaults = { \ 'gitcommit': 0, \ 'gitrebase': 0, \ 'hgcommit': 0, \ 'svn': 0, \ 'cvs': 0, \ '.': 0} function! s:BufferDisabled() abort if &buftype =~# '^\%(help\|prompt\|quickfix\|terminal\)$' return 5 endif if exists('b:copilot_disabled') return empty(b:copilot_disabled) ? 0 : 3 endif if exists('b:copilot_enabled') return empty(b:copilot_enabled) ? 4 : 0 endif let short = empty(&l:filetype) ? '.' : split(&l:filetype, '\.', 1)[0] let config = {} if type(get(g:, 'copilot_filetypes')) == v:t_dict let config = g:copilot_filetypes endif if has_key(config, &l:filetype) return empty(config[&l:filetype]) elseif has_key(config, short) return empty(config[short]) elseif has_key(config, '*') return empty(config['*']) else return get(s:filetype_defaults, short, 1) == 0 ? 2 : 0 endif endfunction function! copilot#Enabled() abort return get(g:, 'copilot_enabled', 1) \ && empty(s:BufferDisabled()) endfunction let s:inline_invoked = 1 let s:inline_automatic = 2 function! copilot#Complete(...) abort if exists('g:_copilot_timer') call timer_stop(remove(g:, '_copilot_timer')) endif let target = [bufnr(''), getbufvar('', 'changedtick'), line('.'), col('.')] if !exists('b:_copilot.target') || b:_copilot.target !=# target if exists('b:_copilot.first') call copilot#client#Cancel(b:_copilot.first) endif if exists('b:_copilot.cycling') call copilot#client#Cancel(b:_copilot.cycling) endif let params = { \ 'textDocument': {'uri': bufnr('')}, \ 'position': copilot#util#AppendPosition(), \ 'formattingOptions': {'insertSpaces': &expandtab ? v:true : v:false, 'tabSize': shiftwidth()}, \ 'context': {'triggerKind': s:inline_automatic}} let b:_copilot = { \ 'target': target, \ 'params': params, \ 'first': copilot#Request('textDocument/inlineCompletion', params)} let g:_copilot_last = b:_copilot endif let completion = b:_copilot.first if !a:0 return completion.Await() else call copilot#client#Result(completion, function(a:1, [b:_copilot])) if a:0 > 1 call copilot#client#Error(completion, function(a:2, [b:_copilot])) endif endif endfunction function! s:HideDuringCompletion() abort return get(g:, 'copilot_hide_during_completion', 1) endfunction function! s:SuggestionTextWithAdjustments() abort let empty = ['', 0, 0, {}] try if mode() !~# '^[iR]' || (s:HideDuringCompletion() && pumvisible()) || !exists('b:_copilot.suggestions') return empty endif let choice = get(b:_copilot.suggestions, b:_copilot.choice, {}) if !has_key(choice, 'range') || choice.range.start.line != line('.') - 1 || type(choice.insertText) !=# v:t_string return empty endif let line = getline('.') let offset = col('.') - 1 let choice_text = strpart(line, 0, copilot#util#UTF16ToByteIdx(line, choice.range.start.character)) . substitute(choice.insertText, "\n*$", '', '') let typed = strpart(line, 0, offset) let end_offset = copilot#util#UTF16ToByteIdx(line, choice.range.end.character) if end_offset < 0 let end_offset = len(line) endif let delete = strpart(line, offset, end_offset - offset) if typed =~# '^\s*$' let leading = matchstr(choice_text, '^\s\+') let unindented = strpart(choice_text, len(leading)) if strpart(typed, 0, len(leading)) == leading && unindented !=# delete return [unindented, len(typed) - len(leading), strchars(delete), choice] endif elseif typed ==# strpart(choice_text, 0, offset) return [strpart(choice_text, offset), 0, strchars(delete), choice] endif catch call copilot#logger#Exception() endtry return empty endfunction function! s:Advance(count, context, ...) abort if a:context isnot# get(b:, '_copilot', {}) return endif let a:context.choice += a:count if a:context.choice < 0 let a:context.choice += len(a:context.suggestions) endif let a:context.choice %= len(a:context.suggestions) call s:UpdatePreview() endfunction function! s:GetSuggestionsCyclingCallback(context, result) abort let callbacks = remove(a:context, 'cycling_callbacks') let seen = {} for suggestion in a:context.suggestions let seen[suggestion.insertText] = 1 endfor for suggestion in get(a:result, 'items', []) if !has_key(seen, suggestion.insertText) call add(a:context.suggestions, suggestion) let seen[suggestion.insertText] = 1 endif endfor for Callback in callbacks call Callback(a:context) endfor endfunction function! s:GetSuggestionsCycling(callback) abort if exists('b:_copilot.cycling_callbacks') call add(b:_copilot.cycling_callbacks, a:callback) elseif exists('b:_copilot.cycling') call a:callback(b:_copilot) elseif exists('b:_copilot.suggestions') let params = deepcopy(b:_copilot.first.params) let params.context.triggerKind = s:inline_invoked let b:_copilot.cycling_callbacks = [a:callback] let b:_copilot.cycling = copilot#Request('textDocument/inlineCompletion', \ params, \ function('s:GetSuggestionsCyclingCallback', [b:_copilot]), \ function('s:GetSuggestionsCyclingCallback', [b:_copilot]), \ ) call s:UpdatePreview() endif return '' endfunction function! copilot#Next() abort return s:GetSuggestionsCycling(function('s:Advance', [1])) endfunction function! copilot#Previous() abort return s:GetSuggestionsCycling(function('s:Advance', [-1])) endfunction function! copilot#GetDisplayedSuggestion() abort let [text, outdent, delete, item] = s:SuggestionTextWithAdjustments() return { \ 'item': item, \ 'text': text, \ 'outdentSize': outdent, \ 'deleteSize': delete} endfunction function! s:ClearPreview() abort if s:has_nvim_ghost_text call nvim_buf_del_extmark(0, copilot#NvimNs(), 1) elseif s:has_vim_ghost_text call prop_remove({'type': s:hlgroup, 'all': v:true}) call prop_remove({'type': s:annot_hlgroup, 'all': v:true}) endif endfunction function! s:UpdatePreview() abort try let [text, outdent, delete, item] = s:SuggestionTextWithAdjustments() let text = split(text, "\r\n\\=\\|\n", 1) if empty(text[-1]) call remove(text, -1) endif if empty(text) || !s:has_ghost_text return s:ClearPreview() endif if exists('b:_copilot.cycling_callbacks') let annot = '(1/…)' elseif exists('b:_copilot.cycling') let annot = '(' . (b:_copilot.choice + 1) . '/' . len(b:_copilot.suggestions) . ')' else let annot = '' endif call s:ClearPreview() if s:has_nvim_ghost_text let data = {'id': 1} let data.virt_text_pos = 'overlay' let append = strpart(getline('.'), col('.') - 1 + delete) let data.virt_text = [[text[0] . append . repeat(' ', delete - len(text[0])), s:hlgroup]] if len(text) > 1 let data.virt_lines = map(text[1:-1], { _, l -> [[l, s:hlgroup]] }) if !empty(annot) let data.virt_lines[-1] += [[' '], [annot, s:annot_hlgroup]] endif elseif len(annot) let data.virt_text += [[' '], [annot, s:annot_hlgroup]] endif let data.hl_mode = 'combine' call nvim_buf_set_extmark(0, copilot#NvimNs(), line('.')-1, col('.')-1, data) elseif s:has_vim_ghost_text let new_suffix = text[0] let current_suffix = getline('.')[col('.') - 1 :] let inset = '' while delete > 0 && !empty(new_suffix) let last_char = matchstr(new_suffix, '.$') let new_suffix = matchstr(new_suffix, '^.\{-\}\ze.$') if last_char ==# matchstr(current_suffix, '.$') if !empty(inset) call prop_add(line('.'), col('.') + len(current_suffix), {'type': s:hlgroup, 'text': inset}) let inset = '' endif let current_suffix = matchstr(current_suffix, '^.\{-\}\ze.$') let delete -= 1 else let inset = last_char . inset endif endwhile if !empty(new_suffix . inset) call prop_add(line('.'), col('.'), {'type': s:hlgroup, 'text': new_suffix . inset}) endif for line in text[1:] call prop_add(line('.'), 0, {'type': s:hlgroup, 'text_align': 'below', 'text': line}) endfor if !empty(annot) call prop_add(line('.'), col('$'), {'type': s:annot_hlgroup, 'text': ' ' . annot}) endif endif call copilot#Notify('textDocument/didShowCompletion', {'item': item}) catch return copilot#logger#Exception() endtry endfunction function! s:HandleTriggerResult(state, result) abort let a:state.suggestions = type(a:result) == type([]) ? a:result : get(empty(a:result) ? {} : a:result, 'items', []) let a:state.choice = 0 if get(b:, '_copilot') is# a:state call s:UpdatePreview() endif endfunction function! s:HandleTriggerError(state, result) abort let a:state.suggestions = [] let a:state.choice = 0 let a:state.error = a:result if get(b:, '_copilot') is# a:state call s:UpdatePreview() endif endfunction function! copilot#Suggest() abort if !s:Running() return '' endif try call copilot#Complete(function('s:HandleTriggerResult'), function('s:HandleTriggerError')) catch call copilot#logger#Exception() endtry return '' endfunction function! s:Trigger(bufnr, timer) abort let timer = get(g:, '_copilot_timer', -1) if a:bufnr !=# bufnr('') || a:timer isnot# timer || mode() !=# 'i' return endif unlet! g:_copilot_timer return copilot#Suggest() endfunction function! copilot#Schedule() abort if !s:has_ghost_text || !s:Running() || !copilot#Enabled() call copilot#Clear() return endif call s:UpdatePreview() let delay = get(g:, 'copilot_idle_delay', 45) call timer_stop(get(g:, '_copilot_timer', -1)) let g:_copilot_timer = timer_start(delay, function('s:Trigger', [bufnr('')])) endfunction function! s:Attach(bufnr, ...) abort try return copilot#Client().Attach(a:bufnr) catch call copilot#logger#Exception() endtry endfunction function! copilot#OnFileType() abort if empty(s:BufferDisabled()) && &l:modifiable && &l:buflisted call copilot#util#Defer(function('s:Attach'), bufnr('')) endif endfunction function! s:Focus(bufnr, ...) abort if s:Running() && copilot#Client().IsAttached(a:bufnr) call copilot#Client().Notify('textDocument/didFocus', {'textDocument': {'uri': copilot#Client().Attach(a:bufnr).uri}}) endif endfunction function! copilot#OnBufEnter() abort let bufnr = bufnr('') call copilot#util#Defer(function('s:Focus'), bufnr) endfunction function! copilot#OnInsertLeavePre() abort call copilot#Clear() call s:ClearPreview() endfunction function! copilot#OnInsertEnter() abort return copilot#Schedule() endfunction function! copilot#OnCompleteChanged() abort if s:HideDuringCompletion() return copilot#Clear() else return copilot#Schedule() endif endfunction function! copilot#OnCursorMovedI() abort return copilot#Schedule() endfunction function! copilot#OnBufUnload() abort endfunction function! copilot#OnVimLeavePre() abort endfunction function! copilot#TextQueuedForInsertion() abort try return remove(s:, 'suggestion_text') catch return '' endtry endfunction function! copilot#Accept(...) abort let s = copilot#GetDisplayedSuggestion() if !empty(s.text) unlet! b:_copilot let text = '' if a:0 > 1 let text = substitute(matchstr(s.text, "\n*" . '\%(' . a:2 .'\)'), "\n*$", '', '') endif if empty(text) let text = s.text endif if text ==# s.text && has_key(s.item, 'command') call copilot#Request('workspace/executeCommand', s.item.command) else let line_text = strpart(getline('.'), 0, col('.') - 1) . text call copilot#Notify('textDocument/didPartiallyAcceptCompletion', { \ 'item': s.item, \ 'acceptedLength': copilot#util#UTF16Width(line_text) - s.item.range.start.character}) endif call s:ClearPreview() let s:suggestion_text = text let recall = text =~# "\n" ? "\\=" : "\\=" return repeat("\\", s.outdentSize) . repeat("\", s.deleteSize) . \ recall . "copilot#TextQueuedForInsertion()\" . (a:0 > 1 ? '' : "\") endif let default = get(g:, 'copilot_tab_fallback', pumvisible() ? "\" : "\t") if !a:0 return default elseif type(a:1) == v:t_string return a:1 elseif type(a:1) == v:t_func try return call(a:1, []) catch return default endtry else return default endif endfunction function! copilot#AcceptWord(...) abort return copilot#Accept(a:0 ? a:1 : '', '\%(\k\@!.\)*\k*') endfunction function! copilot#AcceptLine(...) abort return copilot#Accept(a:0 ? a:1 : "\r", "[^\n]\\+") endfunction function! s:BrowserCallback(into, code) abort let a:into.code = a:code endfunction function! copilot#Browser() abort if type(get(g:, 'copilot_browser')) == v:t_list let cmd = copy(g:copilot_browser) elseif type(get(g:, 'open_command')) == v:t_list let cmd = copy(g:open_command) elseif has('win32') let cmd = ['rundll32', 'url.dll,FileProtocolHandler'] elseif has('mac') let cmd = ['open'] elseif executable('wslview') return ['wslview'] elseif executable('xdg-open') return ['xdg-open'] else return [] endif if executable(get(cmd, 0, '')) return cmd else return [] endif endfunction let s:commands = {} function! s:EnabledStatusMessage() abort let buf_disabled = s:BufferDisabled() if !s:has_ghost_text if has('nvim') return "Neovim 0.6 required to support ghost text" else return "Vim " . s:vim_minimum_version . " required to support ghost text" endif elseif !get(g:, 'copilot_enabled', 1) return 'Disabled globally by :Copilot disable' elseif buf_disabled is# 5 return 'Disabled for current buffer by buftype=' . &buftype elseif buf_disabled is# 4 return 'Disabled for current buffer by b:copilot_enabled' elseif buf_disabled is# 3 return 'Disabled for current buffer by b:copilot_disabled' elseif buf_disabled is# 2 return 'Disabled for filetype=' . &filetype . ' by internal default' elseif buf_disabled return 'Disabled for filetype=' . &filetype . ' by g:copilot_filetypes' elseif !copilot#Enabled() return 'BUG: Something is wrong with enabling/disabling' else return '' endif endfunction function! s:VerifySetup() abort let error = copilot#Client().StartupError() if !empty(error) echo 'Copilot: ' . error return endif let status = copilot#Call('checkStatus', {}) if !has_key(status, 'user') echo 'Copilot: Not authenticated. Invoke :Copilot setup' return endif if status.status ==# 'NoTelemetryConsent' echo 'Copilot: Telemetry terms not accepted. Invoke :Copilot setup' return endif if status.status ==# 'NotAuthorized' echo "Copilot: You don't have access to GitHub Copilot. Sign up by visiting https://github.com/settings/copilot" return endif return 1 endfunction function! s:commands.status(opts) abort if !s:VerifySetup() return endif if exists('s:client.status.status') && s:client.status.status =~# 'Warning\|Error' echo 'Copilot: ' . s:client.status.status if !empty(get(s:client.status, 'message', '')) echon ': ' . s:client.status.message endif return endif let status = s:EnabledStatusMessage() if !empty(status) echo 'Copilot: ' . status return endif echo 'Copilot: Ready' call s:EditorVersionWarning() endfunction function! s:commands.signout(opts) abort let status = copilot#Call('checkStatus', {'options': {'localChecksOnly': v:true}}) if has_key(status, 'user') echo 'Copilot: Signed out as GitHub user ' . status.user else echo 'Copilot: Not signed in' endif call copilot#Call('signOut', {}) endfunction function! s:commands.setup(opts) abort let startup_error = copilot#Client().StartupError() if !empty(startup_error) echo 'Copilot: ' . startup_error return endif let browser = copilot#Browser() let status = copilot#Call('checkStatus', {}) if has_key(status, 'user') let data = {'status': 'AlreadySignedIn', 'user': status.user} else let data = copilot#Call('signInInitiate', {}) endif if has_key(data, 'verificationUri') let uri = data.verificationUri if has('clipboard') try let @+ = data.userCode catch endtry try let @* = data.userCode catch endtry endif let codemsg = "First copy your one-time code: " . data.userCode . "\n" try if len(&mouse) let mouse = &mouse set mouse= endif if get(a:opts, 'bang') call s:Echo(codemsg . "In your browser, visit " . uri) elseif len(browser) call input(codemsg . "Press ENTER to open GitHub in your browser\n") let status = {} call copilot#job#Stream(browser + [uri], v:null, v:null, function('s:BrowserCallback', [status])) let time = reltime() while empty(status) && reltimefloat(reltime(time)) < 5 sleep 10m endwhile if get(status, 'code', browser[0] !=# 'xdg-open') != 0 call s:Echo("Failed to open browser. Visit " . uri) else call s:Echo("Opened " . uri) endif else call s:Echo(codemsg . "Could not find browser. Visit " . uri) endif call s:Echo("Waiting (could take up to 10 seconds)") let request = copilot#Request('signInConfirm', {'userCode': data.userCode}).Wait() finally if exists('mouse') let &mouse = mouse endif endtry if request.status ==# 'error' return 'echoerr ' . string('Copilot: Authentication failure: ' . request.error.message) else let status = request.result endif elseif get(data, 'status', '') isnot# 'AlreadySignedIn' return 'echoerr ' . string('Copilot: Something went wrong') endif let user = get(status, 'user', '') echo 'Copilot: Authenticated as GitHub user ' . user endfunction let s:commands.auth = s:commands.setup let s:commands.signin = s:commands.setup function! s:commands.help(opts) abort return a:opts.mods . ' help ' . (len(a:opts.arg) ? ':Copilot_' . a:opts.arg : 'copilot') endfunction function! s:commands.version(opts) abort echo 'copilot.vim ' .copilot#client#EditorPluginInfo().version let editorInfo = copilot#client#EditorInfo() echo editorInfo.name . ' ' . editorInfo.version if s:Running() let versions = s:client.Request('getVersion', {}) if exists('s:client.serverInfo.version') echo s:client.serverInfo.name . ' ' . s:client.serverInfo.version else echo 'GitHub Copilot Language Server ' . versions.Await().version endif if exists('s:client.node_version') echo 'Node.js ' . s:client.node_version else echo 'Node.js ' . substitute(get(versions.Await(), 'runtimeVersion', '?'), '^node/', '', 'g') endif else echo 'Not running' if exists('s:client.node_version') echo 'Node.js ' . s:client.node_version endif endif if has('win32') echo 'Windows' elseif has('macunix') echo 'macOS' elseif !has('unix') echo 'Unknown OS' elseif isdirectory('/sys/kernel') echo 'Linux' else echo 'UNIX' endif call s:EditorVersionWarning() endfunction function! s:UpdateEditorConfiguration() abort try if s:Running() call copilot#Notify('notifyChangeConfiguration', {'settings': s:EditorConfiguration()}) endif catch call copilot#logger#Exception() endtry endfunction let s:feedback_url = 'https://github.com/orgs/community/discussions/categories/copilot' function! s:commands.feedback(opts) abort echo s:feedback_url let browser = copilot#Browser() if len(browser) call copilot#job#Stream(browser + [s:feedback_url], v:null, v:null, v:null) endif endfunction function! s:commands.restart(opts) abort call s:Stop() echo 'Copilot: Restarting language server' call s:Start() endfunction function! s:commands.disable(opts) abort let g:copilot_enabled = 0 call s:UpdateEditorConfiguration() endfunction function! s:commands.enable(opts) abort let g:copilot_enabled = 1 call s:UpdateEditorConfiguration() endfunction function! s:commands.panel(opts) abort if s:VerifySetup() return copilot#panel#Open(a:opts) endif endfunction function! s:commands.log(opts) abort return a:opts.mods . ' split +$ copilot:///log' endfunction function! copilot#CommandComplete(arg, lead, pos) abort let args = matchstr(strpart(a:lead, 0, a:pos), 'C\%[opilot][! ] *\zs.*') if args !~# ' ' return sort(filter(map(keys(s:commands), { k, v -> tr(v, '_', '-') }), \ { k, v -> strpart(v, 0, len(a:arg)) ==# a:arg })) else return [] endif endfunction function! copilot#Command(line1, line2, range, bang, mods, arg) abort let cmd = matchstr(a:arg, '^\%(\\.\|\S\)\+') let arg = matchstr(a:arg, '\s\zs\S.*') if !empty(cmd) && !has_key(s:commands, tr(cmd, '-', '_')) return 'echoerr ' . string('Copilot: unknown command ' . string(cmd)) endif try if empty(cmd) if !s:Running() let cmd = 'restart' else try let opts = copilot#Call('checkStatus', {'options': {'localChecksOnly': v:true}}) if opts.status !=# 'OK' && opts.status !=# 'MaybeOK' let cmd = 'setup' else let cmd = 'panel' endif catch call copilot#logger#Exception() let cmd = 'log' endtry endif endif let opts = {'line1': a:line1, 'line2': a:line2, 'range': a:range, 'bang': a:bang, 'mods': a:mods, 'arg': arg} let retval = s:commands[tr(cmd, '-', '_')](opts) if type(retval) == v:t_string return retval else return '' endif catch /^Copilot:/ return 'echoerr ' . string(v:exception) endtry endfunction