2024-01-19 22:17:55 +01:00
|
|
|
scriptencoding utf-8
|
|
|
|
|
|
|
|
if !exists('s:panel_id')
|
|
|
|
let s:panel_id = 0
|
|
|
|
endif
|
|
|
|
|
|
|
|
let s:separator = repeat('─', 72)
|
|
|
|
|
2024-11-23 16:01:51 +01:00
|
|
|
function! s:Render(state) abort
|
|
|
|
let bufnr = bufnr('^' . a:state.panel . '$')
|
|
|
|
let state = a:state
|
|
|
|
if !bufloaded(bufnr)
|
2024-01-19 22:17:55 +01:00
|
|
|
return
|
|
|
|
endif
|
2024-11-23 16:01:51 +01:00
|
|
|
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')]
|
2024-01-19 22:17:55 +01:00
|
|
|
else
|
2024-11-23 16:01:51 +01:00
|
|
|
let lines = [substitute('Synthesizing ' . matchstr(get(a:state, 'message', ''), '\d\+\%(/\d\+\)\=') . ' completions', ' \+', ' ', 'g')]
|
2024-01-19 22:17:55 +01:00
|
|
|
endif
|
|
|
|
if len(sorted)
|
2024-11-23 16:01:51 +01:00
|
|
|
call add(lines, 'Press <CR> on a completion to accept')
|
2024-01-19 22:17:55 +01:00
|
|
|
endif
|
2024-11-23 16:01:51 +01:00
|
|
|
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
|
2024-01-19 22:17:55 +01:00
|
|
|
endfor
|
|
|
|
try
|
|
|
|
call setbufvar(bufnr, '&modifiable', 1)
|
|
|
|
call setbufvar(bufnr, '&readonly', 0)
|
|
|
|
call setbufline(bufnr, 1, lines)
|
|
|
|
finally
|
|
|
|
call setbufvar(bufnr, '&modifiable', 0)
|
|
|
|
endtry
|
2024-11-23 16:01:51 +01:00
|
|
|
call clearmatches()
|
|
|
|
call matchadd('CopilotSuggestion', '\C^' . s:separator . '\n\zs\%(' . join(sort(values(leads), { a, b -> len(b) - len(a) }), '\|') . '\)', 10, 4)
|
2024-01-19 22:17:55 +01:00
|
|
|
endfunction
|
|
|
|
|
2024-11-23 16:01:51 +01:00
|
|
|
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)
|
2024-01-19 22:17:55 +01:00
|
|
|
endfunction
|
|
|
|
|
2024-11-23 16:01:51 +01:00
|
|
|
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)
|
2024-01-19 22:17:55 +01:00
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! copilot#panel#Accept(...) abort
|
|
|
|
let state = get(b:, 'copilot_panel', {})
|
2024-11-23 16:01:51 +01:00
|
|
|
if empty(state.items)
|
2024-01-19 22:17:55 +01:00
|
|
|
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('.')
|
2024-11-23 16:01:51 +01:00
|
|
|
let index = 0
|
2024-01-19 22:17:55 +01:00
|
|
|
for lnum in range(1, at)
|
|
|
|
if getline(lnum) ==# s:separator
|
2024-11-23 16:01:51 +01:00
|
|
|
let index += 1
|
2024-01-19 22:17:55 +01:00
|
|
|
endif
|
|
|
|
endfor
|
2024-11-23 16:01:51 +01:00
|
|
|
if index > 0 && index <= len(state.items)
|
|
|
|
let item = state.items[index - 1]
|
|
|
|
let lnum = item.range.start.line + 1
|
2024-01-19 22:17:55 +01:00
|
|
|
if getbufline(state.bufnr, lnum) !=# [state.line]
|
2024-11-23 16:01:51 +01:00
|
|
|
return 'echoerr "Buffer has changed since synthesizing completion"'
|
2024-01-19 22:17:55 +01:00
|
|
|
endif
|
2024-11-23 16:01:51 +01:00
|
|
|
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)
|
2024-01-19 22:17:55 +01:00
|
|
|
bwipeout
|
|
|
|
let win = bufwinnr(state.bufnr)
|
|
|
|
if win > 0
|
|
|
|
exe win . 'wincmd w'
|
2024-11-23 16:01:51 +01:00
|
|
|
exe item.range.start.line + len(lines)
|
2024-01-19 22:17:55 +01:00
|
|
|
if state.was_insert
|
|
|
|
startinsert!
|
|
|
|
else
|
|
|
|
normal! $
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
endif
|
|
|
|
return ''
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! s:Initialize(state) abort
|
|
|
|
let &l:filetype = 'copilot' . (empty(a:state.filetype) ? '' : '.' . a:state.filetype)
|
|
|
|
let &l:tabstop = a:state.tabstop
|
|
|
|
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
|
2024-11-23 16:01:51 +01:00
|
|
|
setlocal bufhidden=wipe buftype=nofile nobuflisted nomodifiable
|
2024-01-19 22:17:55 +01:00
|
|
|
let state = get(b:, 'copilot_panel')
|
|
|
|
if type(state) != v:t_dict
|
|
|
|
return
|
|
|
|
endif
|
|
|
|
call s:Initialize(state)
|
2024-11-23 16:01:51 +01:00
|
|
|
call s:Render(state)
|
2024-01-19 22:17:55 +01:00
|
|
|
return ''
|
|
|
|
endfunction
|
|
|
|
|
2024-11-23 16:01:51 +01:00
|
|
|
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
|
|
|
|
|
2024-01-19 22:17:55 +01:00
|
|
|
function! copilot#panel#Open(opts) abort
|
|
|
|
let s:panel_id += 1
|
2024-11-23 16:01:51 +01:00
|
|
|
let state = {'items': [], 'filetype': &filetype, 'was_insert': mode() =~# '^[iR]', 'bufnr': bufnr(''), 'tabstop': &tabstop}
|
|
|
|
let state.panel = 'copilot:///panel/' . s:panel_id
|
2024-01-19 22:17:55 +01:00
|
|
|
if state.was_insert
|
2024-11-23 16:01:51 +01:00
|
|
|
let state.position = copilot#util#AppendPosition()
|
2024-01-19 22:17:55 +01:00
|
|
|
stopinsert
|
|
|
|
else
|
2024-11-23 16:01:51 +01:00
|
|
|
let state.position = {'line': a:opts.line1 >= 1 ? a:opts.line1 - 1 : 0, 'character': copilot#util#UTF16Width(getline('.'))}
|
2024-01-19 22:17:55 +01:00
|
|
|
endif
|
2024-11-23 16:01:51 +01:00
|
|
|
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
|
2024-01-19 22:17:55 +01:00
|
|
|
let b:copilot_panel = state
|
|
|
|
call s:Initialize(state)
|
2024-11-23 16:01:51 +01:00
|
|
|
call s:Render(state)
|
2024-01-19 22:17:55 +01:00
|
|
|
return ''
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
augroup github_copilot_panel
|
|
|
|
autocmd!
|
2024-11-23 16:01:51 +01:00
|
|
|
autocmd BufReadCmd copilot:///panel/* exe s:BufReadCmd()
|
2024-01-19 22:17:55 +01:00
|
|
|
augroup END
|