Update 2024-11-23 16:01 OpenBSD/amd64-t14

This commit is contained in:
c0dev0id 2024-11-23 16:01:51 +01:00
parent 381dec3be2
commit ed33e478fd
49 changed files with 302670 additions and 204181 deletions

View File

@ -1,4 +1,4 @@
# Copilot.vim # GitHub Copilot for Vim and Neovim
GitHub Copilot uses OpenAI Codex to suggest code and entire functions in GitHub Copilot uses OpenAI Codex to suggest code and entire functions in
real-time right from your editor. Trained on billions of lines of public real-time right from your editor. Trained on billions of lines of public
@ -48,7 +48,7 @@ Terms](https://docs.github.com/en/site-policy/github-terms/github-terms-for-addi
git clone https://github.com/github/copilot.vim.git ` git clone https://github.com/github/copilot.vim.git `
$HOME/AppData/Local/nvim/pack/github/start/copilot.vim $HOME/AppData/Local/nvim/pack/github/start/copilot.vim
4. Start Neovim and invoke `:Copilot setup`. 4. Start Vim/Neovim and invoke `:Copilot setup`.
[Node.js]: https://nodejs.org/en/download/ [Node.js]: https://nodejs.org/en/download/
[Neovim]: https://github.com/neovim/neovim/releases/latest [Neovim]: https://github.com/neovim/neovim/releases/latest

View File

@ -1,6 +1,6 @@
scriptencoding utf-8 scriptencoding utf-8
let s:has_nvim_ghost_text = has('nvim-0.6') && exists('*nvim_buf_get_mark') 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:vim_minimum_version = '9.0.0185'
let s:has_vim_ghost_text = has('patch-' . s:vim_minimum_version) && has('textprop') 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:has_ghost_text = s:has_nvim_ghost_text || s:has_vim_ghost_text
@ -34,81 +34,68 @@ function! s:EditorConfiguration() abort
\ } \ }
endfunction endfunction
function! s:StatusNotification(params, ...) abort
let status = get(a:params, 'status', '')
if status ==? 'error'
let s:agent_error = a:params.message
else
unlet! s:agent_error
endif
endfunction
function! copilot#Init(...) abort function! copilot#Init(...) abort
call timer_start(0, { _ -> s:Start() }) call copilot#util#Defer({ -> exists('s:client') || s:Start() })
endfunction endfunction
function! s:Running() abort function! s:Running() abort
return exists('s:agent.job') || exists('s:agent.client_id') return exists('s:client.job') || exists('s:client.client_id')
endfunction endfunction
function! s:Start() abort function! s:Start() abort
if s:Running() if s:Running() || exists('s:client.startup_error')
return return
endif endif
let s:agent = copilot#agent#New({'methods': { let s:client = copilot#client#New({'editorConfiguration' : s:EditorConfiguration()})
\ 'statusNotification': function('s:StatusNotification'),
\ 'PanelSolution': function('copilot#panel#Solution'),
\ 'PanelSolutionsDone': function('copilot#panel#SolutionsDone'),
\ 'copilot/openURL': function('s:OpenURL'),
\ },
\ 'editorConfiguration' : s:EditorConfiguration()})
endfunction endfunction
function! s:Stop() abort function! s:Stop() abort
if exists('s:agent') if exists('s:client')
let agent = remove(s:, 'agent') let client = remove(s:, 'client')
call agent.Close() call client.Close()
endif endif
endfunction endfunction
function! copilot#Agent() abort function! copilot#Client() abort
call s:Start() call s:Start()
return s:agent return s:client
endfunction endfunction
function! copilot#RunningAgent() abort function! copilot#RunningClient() abort
if s:Running() if s:Running()
return s:agent return s:client
else else
return v:null return v:null
endif endif
endfunction endfunction
function! s:NodeVersionWarning() abort if has('nvim-0.7') && !has(luaeval('vim.version().api_prerelease') ? 'nvim-0.8.1' : 'nvim-0.8.0')
if exists('s:agent.node_version') && s:agent.node_version =~# '^16\.' 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 echohl WarningMsg
echo "Warning: Node.js 16 is approaching end of life and support will be dropped in a future release of copilot.vim." echo 'Warning: ' . s:editor_warning
echohl NONE echohl None
elseif exists('s:agent.node_version_warning')
echohl WarningMsg
echo 'Warning:' s:agent.node_version_warning
echohl NONE
endif endif
endfunction endfunction
function! copilot#Request(method, params, ...) abort function! copilot#Request(method, params, ...) abort
let agent = copilot#Agent() let client = copilot#Client()
return call(agent.Request, [a:method, a:params] + a:000) return call(client.Request, [a:method, a:params] + a:000)
endfunction endfunction
function! copilot#Call(method, params, ...) abort function! copilot#Call(method, params, ...) abort
let agent = copilot#Agent() let client = copilot#Client()
return call(agent.Call, [a:method, a:params] + a:000) return call(client.Call, [a:method, a:params] + a:000)
endfunction endfunction
function! copilot#Notify(method, params, ...) abort function! copilot#Notify(method, params, ...) abort
let agent = copilot#Agent() let client = copilot#Client()
return call(agent.Notify, [a:method, a:params] + a:000) return call(client.Notify, [a:method, a:params] + a:000)
endfunction endfunction
function! copilot#NvimNs() abort function! copilot#NvimNs() abort
@ -120,37 +107,21 @@ function! copilot#Clear() abort
call timer_stop(remove(g:, '_copilot_timer')) call timer_stop(remove(g:, '_copilot_timer'))
endif endif
if exists('b:_copilot') if exists('b:_copilot')
call copilot#agent#Cancel(get(b:_copilot, 'first', {})) call copilot#client#Cancel(get(b:_copilot, 'first', {}))
call copilot#agent#Cancel(get(b:_copilot, 'cycling', {})) call copilot#client#Cancel(get(b:_copilot, 'cycling', {}))
endif endif
call s:UpdatePreview() call s:UpdatePreview()
unlet! b:_copilot unlet! b:_copilot
return '' return ''
endfunction endfunction
function! s:Reject(bufnr) abort
try
let dict = getbufvar(a:bufnr, '_copilot')
if type(dict) == v:t_dict && !empty(get(dict, 'shown_choices', {}))
call copilot#Request('notifyRejected', {'uuids': keys(dict.shown_choices)})
let dict.shown_choices = {}
endif
catch
call copilot#logger#Exception()
endtry
endfunction
function! copilot#Dismiss() abort function! copilot#Dismiss() abort
call s:Reject('%')
call copilot#Clear() call copilot#Clear()
call s:UpdatePreview() call s:UpdatePreview()
return '' return ''
endfunction endfunction
let s:filetype_defaults = { let s:filetype_defaults = {
\ 'yaml': 0,
\ 'markdown': 0,
\ 'help': 0,
\ 'gitcommit': 0, \ 'gitcommit': 0,
\ 'gitrebase': 0, \ 'gitrebase': 0,
\ 'hgcommit': 0, \ 'hgcommit': 0,
@ -187,26 +158,41 @@ endfunction
function! copilot#Enabled() abort function! copilot#Enabled() abort
return get(g:, 'copilot_enabled', 1) return get(g:, 'copilot_enabled', 1)
\ && empty(s:BufferDisabled()) \ && empty(s:BufferDisabled())
\ && empty(copilot#Agent().StartupError())
endfunction endfunction
let s:inline_invoked = 1
let s:inline_automatic = 2
function! copilot#Complete(...) abort function! copilot#Complete(...) abort
if exists('g:_copilot_timer') if exists('g:_copilot_timer')
call timer_stop(remove(g:, '_copilot_timer')) call timer_stop(remove(g:, '_copilot_timer'))
endif endif
let params = copilot#doc#Params() let target = [bufnr(''), getbufvar('', 'changedtick'), line('.'), col('.')]
if !exists('b:_copilot.params') || b:_copilot.params !=# params if !exists('b:_copilot.target') || b:_copilot.target !=# target
let b:_copilot = {'params': params, 'first': if exists('b:_copilot.first')
\ copilot#Request('getCompletions', params)} 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 let g:_copilot_last = b:_copilot
endif endif
let completion = b:_copilot.first let completion = b:_copilot.first
if !a:0 if !a:0
return completion.Await() return completion.Await()
else else
call copilot#agent#Result(completion, a:1) call copilot#client#Result(completion, function(a:1, [b:_copilot]))
if a:0 > 1 if a:0 > 1
call copilot#agent#Error(completion, a:2) call copilot#client#Error(completion, function(a:2, [b:_copilot]))
endif endif
endif endif
endfunction endfunction
@ -216,37 +202,37 @@ function! s:HideDuringCompletion() abort
endfunction endfunction
function! s:SuggestionTextWithAdjustments() abort function! s:SuggestionTextWithAdjustments() abort
let empty = ['', 0, 0, {}]
try try
if mode() !~# '^[iR]' || (s:HideDuringCompletion() && pumvisible()) || !exists('b:_copilot.suggestions') if mode() !~# '^[iR]' || (s:HideDuringCompletion() && pumvisible()) || !exists('b:_copilot.suggestions')
return ['', 0, 0, ''] return empty
endif endif
let choice = get(b:_copilot.suggestions, b:_copilot.choice, {}) let choice = get(b:_copilot.suggestions, b:_copilot.choice, {})
if !has_key(choice, 'range') || choice.range.start.line != line('.') - 1 || type(choice.text) !=# v:t_string if !has_key(choice, 'range') || choice.range.start.line != line('.') - 1 || type(choice.insertText) !=# v:t_string
return ['', 0, 0, ''] return empty
endif endif
let line = getline('.') let line = getline('.')
let offset = col('.') - 1 let offset = col('.') - 1
let choice_text = strpart(line, 0, copilot#doc#UTF16ToByteIdx(line, choice.range.start.character)) . choice.text 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 typed = strpart(line, 0, offset)
let end_offset = copilot#doc#UTF16ToByteIdx(line, choice.range.end.character) let end_offset = copilot#util#UTF16ToByteIdx(line, choice.range.end.character)
if end_offset < 0 if end_offset < 0
let end_offset = len(line) let end_offset = len(line)
endif endif
let delete = strpart(line, offset, end_offset - offset) let delete = strpart(line, offset, end_offset - offset)
let uuid = get(choice, 'uuid', '')
if typed =~# '^\s*$' if typed =~# '^\s*$'
let leading = matchstr(choice_text, '^\s\+') let leading = matchstr(choice_text, '^\s\+')
let unindented = strpart(choice_text, len(leading)) let unindented = strpart(choice_text, len(leading))
if strpart(typed, 0, len(leading)) == leading && unindented !=# delete if strpart(typed, 0, len(leading)) == leading && unindented !=# delete
return [unindented, len(typed) - len(leading), strchars(delete), uuid] return [unindented, len(typed) - len(leading), strchars(delete), choice]
endif endif
elseif typed ==# strpart(choice_text, 0, offset) elseif typed ==# strpart(choice_text, 0, offset)
return [strpart(choice_text, offset), 0, strchars(delete), uuid] return [strpart(choice_text, offset), 0, strchars(delete), choice]
endif endif
catch catch
call copilot#logger#Exception() call copilot#logger#Exception()
endtry endtry
return ['', 0, 0, ''] return empty
endfunction endfunction
@ -266,12 +252,12 @@ function! s:GetSuggestionsCyclingCallback(context, result) abort
let callbacks = remove(a:context, 'cycling_callbacks') let callbacks = remove(a:context, 'cycling_callbacks')
let seen = {} let seen = {}
for suggestion in a:context.suggestions for suggestion in a:context.suggestions
let seen[suggestion.text] = 1 let seen[suggestion.insertText] = 1
endfor endfor
for suggestion in get(a:result, 'completions', []) for suggestion in get(a:result, 'items', [])
if !has_key(seen, suggestion.text) if !has_key(seen, suggestion.insertText)
call add(a:context.suggestions, suggestion) call add(a:context.suggestions, suggestion)
let seen[suggestion.text] = 1 let seen[suggestion.insertText] = 1
endif endif
endfor endfor
for Callback in callbacks for Callback in callbacks
@ -285,9 +271,11 @@ function! s:GetSuggestionsCycling(callback) abort
elseif exists('b:_copilot.cycling') elseif exists('b:_copilot.cycling')
call a:callback(b:_copilot) call a:callback(b:_copilot)
elseif exists('b:_copilot.suggestions') 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_callbacks = [a:callback]
let b:_copilot.cycling = copilot#Request('getCompletionsCycling', let b:_copilot.cycling = copilot#Request('textDocument/inlineCompletion',
\ b:_copilot.first.params, \ params,
\ function('s:GetSuggestionsCyclingCallback', [b:_copilot]), \ function('s:GetSuggestionsCyclingCallback', [b:_copilot]),
\ function('s:GetSuggestionsCyclingCallback', [b:_copilot]), \ function('s:GetSuggestionsCyclingCallback', [b:_copilot]),
\ ) \ )
@ -305,10 +293,10 @@ function! copilot#Previous() abort
endfunction endfunction
function! copilot#GetDisplayedSuggestion() abort function! copilot#GetDisplayedSuggestion() abort
let [text, outdent, delete, uuid] = s:SuggestionTextWithAdjustments() let [text, outdent, delete, item] = s:SuggestionTextWithAdjustments()
return { return {
\ 'uuid': uuid, \ 'item': item,
\ 'text': text, \ 'text': text,
\ 'outdentSize': outdent, \ 'outdentSize': outdent,
\ 'deleteSize': delete} \ 'deleteSize': delete}
@ -325,8 +313,8 @@ endfunction
function! s:UpdatePreview() abort function! s:UpdatePreview() abort
try try
let [text, outdent, delete, uuid] = s:SuggestionTextWithAdjustments() let [text, outdent, delete, item] = s:SuggestionTextWithAdjustments()
let text = split(text, "\n", 1) let text = split(text, "\r\n\\=\\|\n", 1)
if empty(text[-1]) if empty(text[-1])
call remove(text, -1) call remove(text, -1)
endif endif
@ -343,7 +331,7 @@ function! s:UpdatePreview() abort
call s:ClearPreview() call s:ClearPreview()
if s:has_nvim_ghost_text if s:has_nvim_ghost_text
let data = {'id': 1} let data = {'id': 1}
let data.virt_text_win_col = virtcol('.') - 1 let data.virt_text_pos = 'overlay'
let append = strpart(getline('.'), col('.') - 1 + delete) let append = strpart(getline('.'), col('.') - 1 + delete)
let data.virt_text = [[text[0] . append . repeat(' ', delete - len(text[0])), s:hlgroup]] let data.virt_text = [[text[0] . append . repeat(' ', delete - len(text[0])), s:hlgroup]]
if len(text) > 1 if len(text) > 1
@ -356,8 +344,27 @@ function! s:UpdatePreview() abort
endif endif
let data.hl_mode = 'combine' let data.hl_mode = 'combine'
call nvim_buf_set_extmark(0, copilot#NvimNs(), line('.')-1, col('.')-1, data) call nvim_buf_set_extmark(0, copilot#NvimNs(), line('.')-1, col('.')-1, data)
else elseif s:has_vim_ghost_text
call prop_add(line('.'), col('.'), {'type': s:hlgroup, 'text': text[0]}) 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:] for line in text[1:]
call prop_add(line('.'), 0, {'type': s:hlgroup, 'text_align': 'below', 'text': line}) call prop_add(line('.'), 0, {'type': s:hlgroup, 'text_align': 'below', 'text': line})
endfor endfor
@ -365,28 +372,35 @@ function! s:UpdatePreview() abort
call prop_add(line('.'), col('$'), {'type': s:annot_hlgroup, 'text': ' ' . annot}) call prop_add(line('.'), col('$'), {'type': s:annot_hlgroup, 'text': ' ' . annot})
endif endif
endif endif
if !has_key(b:_copilot.shown_choices, uuid) call copilot#Notify('textDocument/didShowCompletion', {'item': item})
let b:_copilot.shown_choices[uuid] = v:true
call copilot#Request('notifyShown', {'uuid': uuid})
endif
catch catch
return copilot#logger#Exception() return copilot#logger#Exception()
endtry endtry
endfunction endfunction
function! s:HandleTriggerResult(result) abort function! s:HandleTriggerResult(state, result) abort
if !exists('b:_copilot') let a:state.suggestions = type(a:result) == type([]) ? a:result : get(empty(a:result) ? {} : a:result, 'items', [])
return 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 endif
let b:_copilot.suggestions = get(a:result, 'completions', [])
let b:_copilot.choice = 0
let b:_copilot.shown_choices = {}
call s:UpdatePreview()
endfunction endfunction
function! copilot#Suggest() abort function! copilot#Suggest() abort
if !s:Running()
return ''
endif
try try
call copilot#Complete(function('s:HandleTriggerResult'), function('s:HandleTriggerResult')) call copilot#Complete(function('s:HandleTriggerResult'), function('s:HandleTriggerError'))
catch catch
call copilot#logger#Exception() call copilot#logger#Exception()
endtry endtry
@ -402,24 +416,45 @@ function! s:Trigger(bufnr, timer) abort
return copilot#Suggest() return copilot#Suggest()
endfunction endfunction
function! copilot#IsMapped() abort function! copilot#Schedule() abort
return get(g:, 'copilot_assume_mapped') || if !s:has_ghost_text || !s:Running() || !copilot#Enabled()
\ hasmapto('copilot#Accept(', 'i')
endfunction
function! copilot#Schedule(...) abort
if !s:has_ghost_text || !copilot#Enabled() || !copilot#IsMapped()
call copilot#Clear() call copilot#Clear()
return return
endif endif
call s:UpdatePreview() call s:UpdatePreview()
let delay = a:0 ? a:1 : get(g:, 'copilot_idle_delay', 15) let delay = get(g:, 'copilot_idle_delay', 45)
call timer_stop(get(g:, '_copilot_timer', -1)) call timer_stop(get(g:, '_copilot_timer', -1))
let g:_copilot_timer = timer_start(delay, function('s:Trigger', [bufnr('')])) let g:_copilot_timer = timer_start(delay, function('s:Trigger', [bufnr('')]))
endfunction endfunction
function! copilot#OnInsertLeave() abort function! s:Attach(bufnr, ...) abort
return copilot#Clear() 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 endfunction
function! copilot#OnInsertEnter() abort function! copilot#OnInsertEnter() abort
@ -439,7 +474,6 @@ function! copilot#OnCursorMovedI() abort
endfunction endfunction
function! copilot#OnBufUnload() abort function! copilot#OnBufUnload() abort
call s:Reject(+expand('<abuf>'))
endfunction endfunction
function! copilot#OnVimLeavePre() abort function! copilot#OnVimLeavePre() abort
@ -464,11 +498,19 @@ function! copilot#Accept(...) abort
if empty(text) if empty(text)
let text = s.text let text = s.text
endif endif
call copilot#Request('notifyAccepted', {'uuid': s.uuid, 'acceptedLength': copilot#doc#UTF16Width(text)}) 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() call s:ClearPreview()
let s:suggestion_text = text let s:suggestion_text = text
let recall = text =~# "\n" ? "\<C-R>\<C-O>=" : "\<C-R>\<C-R>="
return repeat("\<Left>\<Del>", s.outdentSize) . repeat("\<Del>", s.deleteSize) . return repeat("\<Left>\<Del>", s.outdentSize) . repeat("\<Del>", s.deleteSize) .
\ "\<C-R>\<C-O>=copilot#TextQueuedForInsertion()\<CR>" . (a:0 > 1 ? '' : "\<End>") \ recall . "copilot#TextQueuedForInsertion()\<CR>" . (a:0 > 1 ? '' : "\<End>")
endif endif
let default = get(g:, 'copilot_tab_fallback', pumvisible() ? "\<C-N>" : "\t") let default = get(g:, 'copilot_tab_fallback', pumvisible() ? "\<C-N>" : "\t")
if !a:0 if !a:0
@ -521,21 +563,6 @@ function! copilot#Browser() abort
endif endif
endfunction endfunction
function! s:OpenURL(params) abort
echo a:params.target
let browser = copilot#Browser()
if empty(browser)
return v:false
endif
let status = {}
call copilot#job#Stream(browser + [a:params.target], v:null, v:null, function('s:BrowserCallback', [status]))
let time = reltime()
while empty(status) && reltimefloat(reltime(time)) < 1
sleep 10m
endwhile
return get(status, 'code') ? v:false : v:true
endfunction
let s:commands = {} let s:commands = {}
function! s:EnabledStatusMessage() abort function! s:EnabledStatusMessage() abort
@ -546,8 +573,6 @@ function! s:EnabledStatusMessage() abort
else else
return "Vim " . s:vim_minimum_version . " required to support ghost text" return "Vim " . s:vim_minimum_version . " required to support ghost text"
endif endif
elseif !copilot#IsMapped()
return '<Tab> map has been disabled or is claimed by another plugin'
elseif !get(g:, 'copilot_enabled', 1) elseif !get(g:, 'copilot_enabled', 1)
return 'Disabled globally by :Copilot disable' return 'Disabled globally by :Copilot disable'
elseif buf_disabled is# 5 elseif buf_disabled is# 5
@ -568,7 +593,7 @@ function! s:EnabledStatusMessage() abort
endfunction endfunction
function! s:VerifySetup() abort function! s:VerifySetup() abort
let error = copilot#Agent().StartupError() let error = copilot#Client().StartupError()
if !empty(error) if !empty(error)
echo 'Copilot: ' . error echo 'Copilot: ' . error
return return
@ -585,6 +610,12 @@ function! s:VerifySetup() abort
echo 'Copilot: Telemetry terms not accepted. Invoke :Copilot setup' echo 'Copilot: Telemetry terms not accepted. Invoke :Copilot setup'
return return
endif 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 return 1
endfunction endfunction
@ -593,31 +624,22 @@ function! s:commands.status(opts) abort
return return
endif 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() let status = s:EnabledStatusMessage()
if !empty(status) if !empty(status)
echo 'Copilot: ' . status echo 'Copilot: ' . status
return return
endif endif
let startup_error = copilot#Agent().StartupError() echo 'Copilot: Ready'
if !empty(startup_error) call s:EditorVersionWarning()
echo 'Copilot: ' . startup_error
return
endif
if exists('s:agent_error')
echo 'Copilot: ' . s:agent_error
return
endif
let status = copilot#Call('checkStatus', {})
if status.status ==# 'NotAuthorized'
echo 'Copilot: Not authorized'
return
endif
echo 'Copilot: Enabled and online'
call s:NodeVersionWarning()
endfunction endfunction
function! s:commands.signout(opts) abort function! s:commands.signout(opts) abort
@ -631,7 +653,7 @@ function! s:commands.signout(opts) abort
endfunction endfunction
function! s:commands.setup(opts) abort function! s:commands.setup(opts) abort
let startup_error = copilot#Agent().StartupError() let startup_error = copilot#Client().StartupError()
if !empty(startup_error) if !empty(startup_error)
echo 'Copilot: ' . startup_error echo 'Copilot: ' . startup_error
return return
@ -641,7 +663,7 @@ function! s:commands.setup(opts) abort
let status = copilot#Call('checkStatus', {}) let status = copilot#Call('checkStatus', {})
if has_key(status, 'user') if has_key(status, 'user')
let data = {} let data = {'status': 'AlreadySignedIn', 'user': status.user}
else else
let data = copilot#Call('signInInitiate', {}) let data = copilot#Call('signInInitiate', {})
endif endif
@ -649,23 +671,25 @@ function! s:commands.setup(opts) abort
if has_key(data, 'verificationUri') if has_key(data, 'verificationUri')
let uri = data.verificationUri let uri = data.verificationUri
if has('clipboard') if has('clipboard')
let @+ = data.userCode try
let @* = data.userCode let @+ = data.userCode
catch
endtry
try
let @* = data.userCode
catch
endtry
endif endif
call s:Echo("First copy your one-time code: " . data.userCode) let codemsg = "First copy your one-time code: " . data.userCode . "\n"
try try
if len(&mouse) if len(&mouse)
let mouse = &mouse let mouse = &mouse
set mouse= set mouse=
endif endif
if get(a:opts, 'bang') if get(a:opts, 'bang')
call s:Echo("In your browser, visit " . uri) call s:Echo(codemsg . "In your browser, visit " . uri)
elseif len(browser) elseif len(browser)
call s:Echo("Press ENTER to open GitHub in your browser") call input(codemsg . "Press ENTER to open GitHub in your browser\n")
let c = getchar()
while c isnot# 13 && c isnot# 10 && c isnot# 0
let c = getchar()
endwhile
let status = {} let status = {}
call copilot#job#Stream(browser + [uri], v:null, v:null, function('s:BrowserCallback', [status])) call copilot#job#Stream(browser + [uri], v:null, v:null, function('s:BrowserCallback', [status]))
let time = reltime() let time = reltime()
@ -678,9 +702,9 @@ function! s:commands.setup(opts) abort
call s:Echo("Opened " . uri) call s:Echo("Opened " . uri)
endif endif
else else
call s:Echo("Could not find browser. Visit " . uri) call s:Echo(codemsg . "Could not find browser. Visit " . uri)
endif endif
call s:Echo("Waiting (could take up to 5 seconds)") call s:Echo("Waiting (could take up to 10 seconds)")
let request = copilot#Request('signInConfirm', {'userCode': data.userCode}).Wait() let request = copilot#Request('signInConfirm', {'userCode': data.userCode}).Wait()
finally finally
if exists('mouse') if exists('mouse')
@ -692,6 +716,8 @@ function! s:commands.setup(opts) abort
else else
let status = request.result let status = request.result
endif endif
elseif get(data, 'status', '') isnot# 'AlreadySignedIn'
return 'echoerr ' . string('Copilot: Something went wrong')
endif endif
let user = get(status, 'user', '<unknown>') let user = get(status, 'user', '<unknown>')
@ -700,23 +726,46 @@ function! s:commands.setup(opts) abort
endfunction endfunction
let s:commands.auth = s:commands.setup let s:commands.auth = s:commands.setup
let s:commands.signin = s:commands.setup
function! s:commands.help(opts) abort function! s:commands.help(opts) abort
return a:opts.mods . ' help ' . (len(a:opts.arg) ? ':Copilot_' . a:opts.arg : 'copilot') return a:opts.mods . ' help ' . (len(a:opts.arg) ? ':Copilot_' . a:opts.arg : 'copilot')
endfunction endfunction
function! s:commands.version(opts) abort function! s:commands.version(opts) abort
let info = copilot#agent#EditorInfo() echo 'copilot.vim ' .copilot#client#EditorPluginInfo().version
echo 'copilot.vim ' .info.editorPluginInfo.version let editorInfo = copilot#client#EditorInfo()
echo info.editorInfo.name . ' ' . info.editorInfo.version echo editorInfo.name . ' ' . editorInfo.version
if s:Running() if s:Running()
let versions = s:agent.Call('getVersion', {}) let versions = s:client.Request('getVersion', {})
echo 'dist/agent.js ' . versions.version if exists('s:client.serverInfo.version')
echo 'Node.js ' . get(s:agent, 'node_version', substitute(get(versions, 'runtimeVersion', '?'), '^node/', '', 'g')) echo s:client.serverInfo.name . ' ' . s:client.serverInfo.version
call s:NodeVersionWarning() 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 else
echo 'dist/agent.js not running' echo 'Not running'
if exists('s:client.node_version')
echo 'Node.js ' . s:client.node_version
endif
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 endfunction
function! s:UpdateEditorConfiguration() abort function! s:UpdateEditorConfiguration() abort
@ -740,11 +789,8 @@ endfunction
function! s:commands.restart(opts) abort function! s:commands.restart(opts) abort
call s:Stop() call s:Stop()
let err = copilot#Agent().StartupError() echo 'Copilot: Restarting language server'
if !empty(err) call s:Start()
return 'echoerr ' . string('Copilot: ' . err)
endif
echo 'Copilot: Restarting agent.'
endfunction endfunction
function! s:commands.disable(opts) abort function! s:commands.disable(opts) abort
@ -763,6 +809,10 @@ function! s:commands.panel(opts) abort
endif endif
endfunction endfunction
function! s:commands.log(opts) abort
return a:opts.mods . ' split +$ copilot:///log'
endfunction
function! copilot#CommandComplete(arg, lead, pos) abort function! copilot#CommandComplete(arg, lead, pos) abort
let args = matchstr(strpart(a:lead, 0, a:pos), 'C\%[opilot][! ] *\zs.*') let args = matchstr(strpart(a:lead, 0, a:pos), 'C\%[opilot][! ] *\zs.*')
if args !~# ' ' if args !~# ' '
@ -776,33 +826,28 @@ endfunction
function! copilot#Command(line1, line2, range, bang, mods, arg) abort function! copilot#Command(line1, line2, range, bang, mods, arg) abort
let cmd = matchstr(a:arg, '^\%(\\.\|\S\)\+') let cmd = matchstr(a:arg, '^\%(\\.\|\S\)\+')
let arg = matchstr(a:arg, '\s\zs\S.*') let arg = matchstr(a:arg, '\s\zs\S.*')
if cmd ==# 'log'
return a:mods . ' split +$ ' . fnameescape(copilot#logger#File())
endif
if !empty(cmd) && !has_key(s:commands, tr(cmd, '-', '_')) if !empty(cmd) && !has_key(s:commands, tr(cmd, '-', '_'))
return 'echoerr ' . string('Copilot: unknown command ' . string(cmd)) return 'echoerr ' . string('Copilot: unknown command ' . string(cmd))
endif endif
try try
let err = copilot#Agent().StartupError()
if !empty(err)
return 'echo ' . string('Copilot: ' . err)
endif
try
let opts = copilot#Call('checkStatus', {'options': {'localChecksOnly': v:true}})
catch
call copilot#logger#Exception()
let opts = {'status': 'VimException'}
endtry
if empty(cmd) if empty(cmd)
if opts.status ==# 'VimException' if !s:Running()
return a:mods . ' split +$ ' . fnameescape(copilot#logger#File()) let cmd = 'restart'
elseif opts.status !=# 'OK' && opts.status !=# 'MaybeOK'
let cmd = 'setup'
else else
let cmd = 'panel' 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
endif endif
call extend(opts, {'line1': a:line1, 'line2': a:line2, 'range': a:range, 'bang': a:bang, 'mods': a:mods, 'arg': arg}) 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) let retval = s:commands[tr(cmd, '-', '_')](opts)
if type(retval) == v:t_string if type(retval) == v:t_string
return retval return retval

View File

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

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

@ -6,35 +6,59 @@ if !exists('s:log_file')
endtry endtry
endif endif
function! copilot#logger#File() abort let s:logs = []
return s:log_file
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 endfunction
let s:level_prefixes = ['', '[ERROR] ', '[WARN] ', '[INFO] ', '[DEBUG] ', '[DEBUG] ']
function! copilot#logger#Raw(level, message) abort 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 = 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 try
if !filewritable(s:log_file) if !filewritable(s:log_file)
return return
endif endif
call map(lines, { k, L -> type(L) == v:t_func ? call(L, []) : L }) 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 catch
endtry endtry
endfunction endfunction
function! copilot#logger#Trace(...) abort
call copilot#logger#Raw(-1, a:000)
endfunction
function! copilot#logger#Debug(...) abort 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 endfunction
function! copilot#logger#Info(...) abort function! copilot#logger#Info(...) abort
call copilot#logger#Raw(1, a:000) call copilot#logger#Raw(3, a:000)
endfunction endfunction
function! copilot#logger#Warn(...) abort function! copilot#logger#Warn(...) abort
@ -42,18 +66,22 @@ function! copilot#logger#Warn(...) abort
endfunction endfunction
function! copilot#logger#Error(...) abort function! copilot#logger#Error(...) abort
call copilot#logger#Raw(3, a:000) call copilot#logger#Raw(1, a:000)
endfunction
function! copilot#logger#Bare(...) abort
call copilot#logger#Raw(0, a:000)
endfunction endfunction
function! copilot#logger#Exception(...) abort function! copilot#logger#Exception(...) abort
if !empty(v:exception) && v:exception !=# 'Vim:Interrupt' if !empty(v:exception) && v:exception !=# 'Vim:Interrupt'
call copilot#logger#Error('Exception: ' . v:exception . ' @ ' . v:throwpoint) call copilot#logger#Error('Exception: ' . v:exception . ' @ ' . v:throwpoint)
let agent = copilot#RunningAgent() let client = copilot#RunningClient()
if !empty(agent) if !empty(client)
let [_, type, code, message; __] = matchlist(v:exception, '^\%(\(^[[:alnum:]_#]\+\)\%((\a\+)\)\=\%(\(:E-\=\d\+\)\)\=:\s*\)\=\(.*\)$') let [_, type, code, message; __] = matchlist(v:exception, '^\%(\(^[[:alnum:]_#]\+\)\%((\a\+)\)\=\%(\(:E-\=\d\+\)\)\=:\s*\)\=\(.*\)$')
let stacklines = [] let stacklines = []
for frame in split(substitute(v:throwpoint, ', \S\+ \(\d\+\)$', '[\1]', ''), '\.\@<!\.\.\.\@!') for frame in split(substitute(v:throwpoint, ', \S\+ \(\d\+\)$', '[\1]', ''), '\.\@<!\.\.\.\@!')
let fn_line = matchlist(frame, '^\%(function \)\=\(\S\+\)\[\(\d+\)\]$') let fn_line = matchlist(frame, '^\%(function \)\=\(\S\+\)\[\(\d\+\)\]$')
if !empty(fn_line) if !empty(fn_line)
call add(stacklines, {'function': substitute(fn_line[1], '^<SNR>\d\+_', '<SID>', ''), 'lineno': +fn_line[2]}) call add(stacklines, {'function': substitute(fn_line[1], '^<SNR>\d\+_', '<SID>', ''), 'lineno': +fn_line[2]})
elseif frame =~# ' Autocmds for "\*"$' elseif frame =~# ' Autocmds for "\*"$'
@ -64,14 +92,14 @@ function! copilot#logger#Exception(...) abort
call add(stacklines, {'function': '[redacted]'}) call add(stacklines, {'function': '[redacted]'})
endif endif
endfor endfor
return agent.Request('telemetry/exception', { return client.Request('telemetry/exception', {
\ 'transaction': a:0 ? a:1 : '', \ 'transaction': a:0 ? a:1 : '',
\ 'platform': 'other', \ 'platform': 'other',
\ 'exception_detail': [{ \ 'exception_detail': [{
\ 'type': type . code, \ 'type': type . code,
\ 'value': message, \ 'value': message,
\ 'stacktrace': stacklines}] \ 'stacktrace': stacklines}]
\ }) \ }, v:null, function('copilot#util#Nop'))
endif endif
endif endif
endfunction endfunction

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -28,10 +28,10 @@ COMMANDS *:Copilot*
*:Copilot_panel* *:Copilot_panel*
:Copilot panel Open a window with up to 10 completions for the :Copilot panel Open a window with up to 10 completions for the
current buffer. Use <CR> to accept a solution. Maps current buffer. Use <CR> to accept a completion.
are also provided for [[ and ]] to jump from solution Maps are also provided for [[ and ]] to jump from
to solution. This is the default command if :Copilot completion to completion. This is the default command
is called without an argument. if :Copilot is called without an argument.
*:Copilot_version* *:Copilot_version*
:Copilot version Show version information. :Copilot version Show version information.
@ -75,12 +75,13 @@ g:copilot_node_command Tell Copilot what `node` binary to use with
\ "~/.nodenv/versions/18.18.0/bin/node" \ "~/.nodenv/versions/18.18.0/bin/node"
< <
*g:copilot_proxy* *g:copilot_proxy*
g:copilot_proxy Tell Copilot what proxy server to use. This is a g:copilot_proxy Tell Copilot what proxy server to use.
string in the format of `hostname:port` or
`username:password@host:port`.
> >
let g:copilot_proxy = 'localhost:3128' let g:copilot_proxy = 'http://localhost:3128'
< <
If this is not set, Copilot will use the value of
environment variables like $HTTPS_PROXY.
*g:copilot_proxy_strict_ssl* *g:copilot_proxy_strict_ssl*
g:copilot_proxy_strict_ssl g:copilot_proxy_strict_ssl
Corporate proxies sometimes use a man-in-the-middle Corporate proxies sometimes use a man-in-the-middle
@ -90,6 +91,23 @@ g:copilot_proxy_strict_ssl
> >
let g:copilot_proxy_strict_ssl = v:false let g:copilot_proxy_strict_ssl = v:false
< <
You can also tell Node.js to disable SSL verification
by setting the $NODE_TLS_REJECT_UNAUTHORIZED
environment variable to "0".
*g:copilot_workspace_folders*
g:copilot_workspace_folders
A list of "workspace folders" or project roots that
Copilot may use to improve to improve the quality of
suggestions.
>
let g:copilot_workspace_folders =
\ ["~/Projects/myproject"]
<
You can also set b:workspace_folder for an individual
buffer and newly seen values will be added
automatically.
MAPS *copilot-maps* MAPS *copilot-maps*
*copilot-i_<Tab>* *copilot-i_<Tab>*
@ -158,10 +176,24 @@ Lua version:
SYNTAX HIGHLIGHTING *copilot-highlighting* SYNTAX HIGHLIGHTING *copilot-highlighting*
Inline suggestions are highlighted using the CopilotSuggestion group, Inline suggestions are highlighted using the CopilotSuggestion group,
defaulting to a medium gray. The best place to override this is a file named defaulting to a medium gray. The best place to override this is with
after/colors/<colorschemename>.vim in your 'runtimepath' (e.g., a |ColorScheme| autocommand:
~/.config/nvim/after/colors/solarized.vim). Example declaration:
> >
highlight CopilotSuggestion guifg=#555555 ctermfg=8 autocmd ColorScheme solarized
\ highlight CopilotSuggestion guifg=#555555 ctermfg=8
<
Lua version:
>
vim.api.nvim_create_autocmd('ColorScheme', {
pattern = 'solarized',
-- group = ...,
callback = function()
vim.api.nvim_set_hl(0, 'CopilotSuggestion', {
fg = '#555555',
ctermfg = 8,
force = true
})
end
})
< <
vim:tw=78:et:ft=help:norl: vim:tw=78:et:ft=help:norl:

View File

@ -1,27 +0,0 @@
:Copilot copilot.txt /*:Copilot*
:Copilot_disable copilot.txt /*:Copilot_disable*
:Copilot_enable copilot.txt /*:Copilot_enable*
:Copilot_feedback copilot.txt /*:Copilot_feedback*
:Copilot_panel copilot.txt /*:Copilot_panel*
:Copilot_setup copilot.txt /*:Copilot_setup*
:Copilot_signout copilot.txt /*:Copilot_signout*
:Copilot_status copilot.txt /*:Copilot_status*
:Copilot_version copilot.txt /*:Copilot_version*
b:copilot_enabled copilot.txt /*b:copilot_enabled*
copilot copilot.txt /*copilot*
copilot#Accept() copilot.txt /*copilot#Accept()*
copilot-highlighting copilot.txt /*copilot-highlighting*
copilot-i_<Tab> copilot.txt /*copilot-i_<Tab>*
copilot-i_ALT-CTRL-Right copilot.txt /*copilot-i_ALT-CTRL-Right*
copilot-i_ALT-Right copilot.txt /*copilot-i_ALT-Right*
copilot-i_ALT-[ copilot.txt /*copilot-i_ALT-[*
copilot-i_ALT-\ copilot.txt /*copilot-i_ALT-\\*
copilot-i_ALT-] copilot.txt /*copilot-i_ALT-]*
copilot-i_CTRL-] copilot.txt /*copilot-i_CTRL-]*
copilot-maps copilot.txt /*copilot-maps*
copilot-options copilot.txt /*copilot-options*
copilot.txt copilot.txt /*copilot.txt*
g:copilot_filetypes copilot.txt /*g:copilot_filetypes*
g:copilot_node_command copilot.txt /*g:copilot_node_command*
g:copilot_proxy copilot.txt /*g:copilot_proxy*
g:copilot_proxy_strict_ssl copilot.txt /*g:copilot_proxy_strict_ssl*

View File

@ -1,62 +1,68 @@
local copilot = {} local copilot = {}
copilot.lsp_start_client = function(cmd, handler_names) local showDocument = function(err, result, ctx, _)
local capabilities = vim.lsp.protocol.make_client_capabilities() local fallback = vim.lsp.handlers['window/showDocument']
local handlers = {} if not fallback or (result.external and vim.g.copilot_browser) then
return vim.fn['copilot#handlers#window_showDocument'](result)
else
return fallback(err, result, ctx, _)
end
end
copilot.lsp_start_client = function(cmd, handler_names, opts, settings)
local handlers = {['window/showDocument'] = showDocument}
local id local id
for _, name in ipairs(handler_names) do for _, name in ipairs(handler_names) do
handlers[name] = function(err, result) handlers[name] = function(err, result, ctx, _)
if result then if result then
local retval = vim.call('copilot#agent#LspHandle', id, { method = name, params = result }) local retval = vim.call('copilot#client#LspHandle', id, { method = name, params = result })
if type(retval) == 'table' then if type(retval) == 'table' then
return retval.result, retval.error return retval.result, retval.error
elseif vim.lsp.handlers[name] then
return vim.lsp.handlers[name](err, result, ctx, _)
end end
end end
end end
if name:match('^copilot/') then end
capabilities.copilot = capabilities.copilot or {} local workspace_folders = opts.workspaceFolders
capabilities.copilot[name:match('^copilot/(.*)$')] = true if #workspace_folders == 0 then
end workspace_folders = nil
end end
id = vim.lsp.start_client({ id = vim.lsp.start_client({
cmd = cmd, cmd = cmd,
cmd_cwd = vim.call('copilot#job#Cwd'), cmd_cwd = vim.call('copilot#job#Cwd'),
name = 'copilot', name = 'GitHub Copilot',
capabilities = capabilities, init_options = opts.initializationOptions,
workspace_folders = workspace_folders,
settings = settings,
handlers = handlers, handlers = handlers,
get_language_id = function(bufnr, filetype)
return vim.call('copilot#doc#LanguageForFileType', filetype)
end,
on_init = function(client, initialize_result) on_init = function(client, initialize_result)
vim.call('copilot#agent#LspInit', client.id, initialize_result) vim.call('copilot#client#LspInit', client.id, initialize_result)
if vim.fn.has('nvim-0.8') == 0 then
client.notify('workspace/didChangeConfiguration', { settings = settings })
end
end, end,
on_exit = function(code, signal, client_id) on_exit = function(code, signal, client_id)
vim.schedule(function() vim.schedule(function()
vim.call('copilot#agent#LspExit', client_id, code, signal) vim.call('copilot#client#LspExit', client_id, code, signal)
end) end)
end, end,
}) })
return id return id
end end
copilot.lsp_request = function(client_id, method, params) copilot.lsp_request = function(client_id, method, params, bufnr)
local client = vim.lsp.get_client_by_id(client_id) local client = vim.lsp.get_client_by_id(client_id)
if not client then if not client then
return return
end end
pcall(vim.lsp.buf_attach_client, 0, client_id) if bufnr == vim.NIL then
for _, doc in ipairs({ params.doc, params.textDocument }) do bufnr = nil
if doc and type(doc.uri) == 'number' then
local bufnr = doc.uri
pcall(vim.lsp.buf_attach_client, bufnr, client_id)
doc.uri = vim.uri_from_bufnr(bufnr)
doc.version = vim.lsp.util.buf_versions[bufnr]
end
end end
local _, id local _, id
_, id = client.request(method, params, function(err, result) _, id = client.request(method, params, function(err, result)
vim.call('copilot#agent#LspResponse', client_id, { id = id, error = err, result = result }) vim.call('copilot#client#LspResponse', client_id, { id = id, error = err, result = result })
end) end, bufnr)
return id return id
end end
@ -67,7 +73,7 @@ copilot.rpc_request = function(client_id, method, params)
end end
local _, id local _, id
_, id = client.rpc.request(method, params, function(err, result) _, id = client.rpc.request(method, params, function(err, result)
vim.call('copilot#agent#LspResponse', client_id, { id = id, error = err, result = result }) vim.call('copilot#client#LspResponse', client_id, { id = id, error = err, result = result })
end) end)
return id return id
end end

View File

@ -7,7 +7,7 @@ scriptencoding utf-8
command! -bang -nargs=? -range=-1 -complete=customlist,copilot#CommandComplete Copilot exe copilot#Command(<line1>, <count>, +"<range>", <bang>0, "<mods>", <q-args>) command! -bang -nargs=? -range=-1 -complete=customlist,copilot#CommandComplete Copilot exe copilot#Command(<line1>, <count>, +"<range>", <bang>0, "<mods>", <q-args>)
if v:version < 800 || !exists('##CompleteChanged') if v:version < 800 || !exists('##InsertLeavePre')
finish finish
endif endif
@ -15,9 +15,9 @@ function! s:ColorScheme() abort
if &t_Co == 256 if &t_Co == 256
hi def CopilotSuggestion guifg=#808080 ctermfg=244 hi def CopilotSuggestion guifg=#808080 ctermfg=244
else else
hi def CopilotSuggestion guifg=#808080 ctermfg=8 hi def CopilotSuggestion guifg=#808080 ctermfg=12
endif endif
hi def link CopilotAnnotation Normal hi def link CopilotAnnotation MoreMsg
endfunction endfunction
function! s:MapTab() abort function! s:MapTab() abort
@ -26,7 +26,7 @@ function! s:MapTab() abort
endif endif
let tab_map = maparg('<Tab>', 'i', 0, 1) let tab_map = maparg('<Tab>', 'i', 0, 1)
if !has_key(tab_map, 'rhs') if !has_key(tab_map, 'rhs')
imap <script><silent><nowait><expr> <Tab> copilot#Accept() imap <script><silent><nowait><expr> <Tab> empty(get(g:, 'copilot_no_tab_map')) ? copilot#Accept() : "\t"
elseif tab_map.rhs !~# 'copilot' elseif tab_map.rhs !~# 'copilot'
if tab_map.expr if tab_map.expr
let tab_fallback = '{ -> ' . tab_map.rhs . ' }' let tab_fallback = '{ -> ' . tab_map.rhs . ' }'
@ -52,17 +52,20 @@ endfunction
augroup github_copilot augroup github_copilot
autocmd! autocmd!
autocmd InsertLeave * call s:Event('InsertLeave') autocmd FileType * call s:Event('FileType')
autocmd BufLeave * if mode() =~# '^[iR]'|call s:Event('InsertLeave')|endif autocmd InsertLeavePre * call s:Event('InsertLeavePre')
autocmd BufLeave * if mode() =~# '^[iR]'|call s:Event('InsertLeavePre')|endif
autocmd InsertEnter * call s:Event('InsertEnter') autocmd InsertEnter * call s:Event('InsertEnter')
autocmd BufEnter * if mode() =~# '^[iR]'|call s:Event('InsertEnter')|endif autocmd BufEnter * if mode() =~# '^[iR]'|call s:Event('InsertEnter')|endif
autocmd BufEnter * call s:Event('BufEnter')
autocmd CursorMovedI * call s:Event('CursorMovedI') autocmd CursorMovedI * call s:Event('CursorMovedI')
autocmd CompleteChanged * call s:Event('CompleteChanged') autocmd CompleteChanged * call s:Event('CompleteChanged')
autocmd ColorScheme,VimEnter * call s:ColorScheme() autocmd ColorScheme,VimEnter * call s:ColorScheme()
autocmd VimEnter * call s:MapTab() autocmd VimEnter * call s:MapTab() | call copilot#Init()
autocmd BufUnload * call s:Event('BufUnload') autocmd BufUnload * call s:Event('BufUnload')
autocmd VimLeavePre * call s:Event('VimLeavePre') autocmd VimLeavePre * call s:Event('VimLeavePre')
autocmd BufReadCmd copilot://* setlocal buftype=nofile bufhidden=wipe nobuflisted readonly nomodifiable autocmd BufReadCmd copilot://* setlocal buftype=nofile bufhidden=wipe nobuflisted nomodifiable
autocmd BufReadCmd copilot:///log call copilot#logger#BufReadCmd() | setfiletype copilotlog
augroup END augroup END
call s:ColorScheme() call s:ColorScheme()
@ -81,7 +84,7 @@ if !get(g:, 'copilot_no_maps')
if !has('nvim') && &encoding ==# 'utf-8' if !has('nvim') && &encoding ==# 'utf-8'
" avoid 8-bit meta collision with UTF-8 characters " avoid 8-bit meta collision with UTF-8 characters
let s:restore_encoding = 1 let s:restore_encoding = 1
set encoding=cp949 silent noautocmd set encoding=cp949
endif endif
if empty(mapcheck('<M-]>', 'i')) if empty(mapcheck('<M-]>', 'i'))
imap <M-]> <Plug>(copilot-next) imap <M-]> <Plug>(copilot-next)
@ -96,17 +99,15 @@ if !get(g:, 'copilot_no_maps')
imap <M-Right> <Plug>(copilot-accept-word) imap <M-Right> <Plug>(copilot-accept-word)
endif endif
if empty(mapcheck('<M-C-Right>', 'i')) if empty(mapcheck('<M-C-Right>', 'i'))
imap <M-Down> <Plug>(copilot-accept-line) imap <M-C-Right> <Plug>(copilot-accept-line)
endif endif
finally finally
if exists('s:restore_encoding') if exists('s:restore_encoding')
set encoding=utf-8 silent noautocmd set encoding=utf-8
endif endif
endtry endtry
endif endif
call copilot#Init()
let s:dir = expand('<sfile>:h:h') let s:dir = expand('<sfile>:h:h')
if getftime(s:dir . '/doc/copilot.txt') > getftime(s:dir . '/doc/tags') if getftime(s:dir . '/doc/copilot.txt') > getftime(s:dir . '/doc/tags')
silent! execute 'helptags' fnameescape(s:dir . '/doc') silent! execute 'helptags' fnameescape(s:dir . '/doc')

View File

@ -6,12 +6,12 @@ endif
let s:subtype = matchstr(&l:filetype, '\<copilot\.\zs[[:alnum:]_-]\+') let s:subtype = matchstr(&l:filetype, '\<copilot\.\zs[[:alnum:]_-]\+')
if !empty(s:subtype) && s:subtype !=# 'copilot' if !empty(s:subtype) && s:subtype !=# 'copilot'
exe 'syn include @copilotLanguageTop syntax/' . s:subtype . '.vim' silent! exe 'syn include @copilotLanguageTop syntax/' . s:subtype . '.vim'
unlet! b:current_syntax unlet! b:current_syntax
endif endif
syn region copilotHeader start="\%^" end="^─\@=" syn region copilotHeader start="\%^" end="^─\@="
syn region copilotSolution matchgroup=copilotSeparator start="^─\{9,}$" end="\%(^─\{9,\}$\)\@=\|\%$" keepend contains=@copilotLanguageTop syn region copilotPanelItem matchgroup=copilotSeparator start="^─\{9,}$" end="\%(^─\{9,\}$\)\@=\|\%$" keepend contains=@copilotLanguageTop
hi def link copilotHeader PreProc hi def link copilotHeader PreProc
hi def link copilotSeparator Comment hi def link copilotSeparator Comment

View File

@ -0,0 +1,25 @@
scriptencoding utf-8
if exists("b:current_syntax")
finish
endif
let s:subtype = matchstr(&l:filetype, '\<copilot\.\zs[[:alnum:]_-]\+')
if !empty(s:subtype) && s:subtype !=# 'copilot'
exe 'syn include @copilotLanguageTop syntax/' . s:subtype . '.vim'
unlet! b:current_syntax
endif
syn match copilotlogError '\[ERROR\]'
syn match copilotlogWarn '\[WARN\]'
syn match copilotlogInfo '\[INFO\]'
syn match copilotlogDebug '\[DEBUG\]'
syn match copilotlogTime '^\[\d\d\d\d-\d\d-\d\d.\d\d:\d\d:\d\d\]' nextgroup=copilotlogError,copilotlogWarn,copilotLogInfo,copilotLogDebug skipwhite
hi def link copilotlogTime NonText
hi def link copilotlogError ErrorMsg
hi def link copilotlogWarn WarningMsg
hi def link copilotlogInfo MoreMsg
hi def link copilotlogDebug ModeMsg
let b:current_syntax = "copilotlog"

View File

@ -1,23 +0,0 @@
==============================================
This is a copy of the MIT license.
==============================================
Copyright (C) 2013 Zc He <farseer90718@gmail.com>
Copyright (C) 2013 David J Patrick <davamundo@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,223 +0,0 @@
vim-taskwarrior
===============
_a vim interface for [taskwarrior](https://taskwarrior.org)_
Taskwarrior is a command-line todo list manager. It helps you manage task lists
with projects, tags, dates, dependencies, annotations, recurrences and apply
complex (or simple) queries with attribute modifiers, boolean, regex filters
and produce any number of reports, built-in or customizable reports, attributes
and color themes. Task keeps data in JSON text files and it's always improving.
Find out more at https://taskwarrior.org and read man task and man taskrc.
vim-taskwarrior is a vim plugin that extends taskwarrior with an interactive
interface. It features a rich set of mappings and commands, is easy to customize,
and makes adding, modifying, sorting, reporting and marking done, fast, easy and fun!
Homepage: https://github.com/farseer90718/vim-taskwarrior, patches welcome!
----
### Prerequisites:
This plugin requires Taskwarrior 2.2.0 or higher, although >2.3.x is required
for taskd sync functions, and recommended in general, and well worth the price;
free :)
see: https://taskwarrior.org/download/
Vim version 7.x is required.
Suggested plugins
* [vim-airline](https://github.com/bling/vim-airline) for [better statusline information](https://github.com/farseer90718/vim-taskwarrior#screenshot).
* [unite.vim](https://github.com/Shougo/unite.vim) for easier bookmark/history operations.
If you experience line-wrapping issues, add the following line to your .vimrc
```
let g:task_rc_override = 'rc.defaultwidth=0'
```
If you experience task truncation (vim-taskwarrior not showing enough tasks), add:
```
let g:task_rc_override = 'rc.defaultheight=0'
```
----
### Screenshot:
![screenshot](https://raw.github.com/farseer90718/vim-taskwarrior/master/screenshot.png)
![vim-taskwarrior animated gif](http://taskextras.org/attachments/download/655/20131110_002753.gif)
### Installing:
Either [download zip file](https://github.com/farseer90718/vim-taskwarrior/archive/master.zip)
and extract in ~/.vim or use your favorite plugin manager.
- [Pathogen](https://github.com/tpope/vim-pathogen)
- `git clone https://github.com/farseer90718/vim-taskwarrior ~/.vim/bundle/vim-taskwarrior`
- [Vundle](https://github.com/gmarik/vundle)
1. Add `Bundle 'farseer90718/vim-taskwarrior'` to .vimrc
2. Run `:BundleInstall`
- [NeoBundle](https://github.com/Shougo/neobundle.vim)
1. Add `NeoBundle 'farseer90718/vim-taskwarrior'` to .vimrc
2. Run `:NeoBundleInstall`
- [vim-plug](https://github.com/junegunn/vim-plug)
1. Add `Plug 'blindFS/vim-taskwarrior'` to .vimrc
2. Run `:PlugInstall`
----
### Default map:
```vim
nnoremap <buffer> A ... " add annotation
nnoremap <buffer> x ... " delete annotation.
nnoremap <buffer> o ... " open the annotation as a file.
nnoremap <buffer> a ... " create new task.
nnoremap <buffer> d ... " set the task in current line done.
nnoremap <buffer> D ... " delete task
nnoremap <buffer> <Del> ... " delete field/annotation/task
nnoremap <buffer> <Space> ... " select/remove current task to selected list
nnoremap <buffer> m ... " modify current field.
nnoremap <buffer> M ... " modify current task.
nnoremap <buffer> f ... " change filter
nnoremap <buffer> r ... " change report type
nnoremap <buffer> c ... " execute a command for selected tasks/current task
nnoremap <buffer> R ... " refresh the report/clear selected list
nnoremap <buffer> q ... " quit buffer.
nnoremap <buffer> X ... " clear all completed task.
nnoremap <buffer> p ... " duplicate selected tasks
nnoremap <buffer> u ... " undo last change.
nnoremap <buffer> + ... " start task
nnoremap <buffer> - ... " stop task
nnoremap <buffer> S ... " sync with taskd server.
nnoremap <buffer> s ... " sort by this column primarily.(if already of the highest priority then switch the polarity)
nnoremap <buffer> < ... " sort by this column increasingly.(if already increasingly then increase its priority)
nnoremap <buffer> > ... " sort by this column decreasingly.(if already decreasingly then decrease its priority)
nnoremap <buffer> H ... " cycle column format left
nnoremap <buffer> L ... " cycle column format right
nnoremap <buffer> J ... " next historical entry
nnoremap <buffer> K ... " previous historical entry
nnoremap <buffer> B ... " create a bookmark for current combination
nnoremap <buffer> <F1> ... " view the documents
nnoremap <buffer> <CR> ... " show task info.
nnoremap <buffer> <TAB> ... " jump to the next column
nnoremap <buffer> <S-TAB> ... " jump to the previous column
nnoremap <buffer> <right> ... " jump to the next non-empty column
nnoremap <buffer> <left> ... " jump to the previous non-empty column
vnoremap <buffer> d ... " set done to all visual selected tasks
vnoremap <buffer> D ... " delete all visual selected tasks
vnoremap <buffer> <CR> ... " show information about visual selected tasks
vnoremap <buffer> <Space> ... " add visual selected tasks to selected list
```
----
### Commands:
```vim
:TW [args] " task [filter report arguments]
:TWUndo " undo the previous modification
:TWEditTaskrc " edit ~/.taskrc
:TWEditVitrc " edit ~/.vitrc
:TWDeleteCompleted " clear all completed tasks
:TWAdd " add new tasks interactively
:TWAnnotate " add an annotation
:TWComplete " mark task done
:TWDelete " deleta a task
:TWDeleteAnnotation " delete an annotation
:TWModifyInteractive " make changes to a task interactively (use with caution!)
:TWReportInfo " run the info report
:TWReportSort [args] " overide the sort method, reset to default if no arguments passed
:TWSync " synchronise with taskd server
:TWToggleReadonly " toggle readonly option
:TWToggleHLField " toggle highlight field option
:TWHistory " list history records using unite.vim
:TWHistoryClear " clear history
:TWBookmark " list bookmarks using unite.vim
:TWBookmarkClear " clear bookmarks
```
----
### Options:
```vim
" default task report type
let g:task_report_name = 'next'
" custom reports have to be listed explicitly to make them available
let g:task_report_command = []
" whether the field under the cursor is highlighted
let g:task_highlight_field = 1
" can not make change to task data when set to 1
let g:task_readonly = 0
" vim built-in term for task undo in gvim
let g:task_gui_term = 1
" allows user to override task configurations. Seperated by space. Defaults to ''
let g:task_rc_override = 'rc.defaultwidth=999'
" default fields to ask when adding a new task
let g:task_default_prompt = ['due', 'description']
" whether the info window is splited vertically
let g:task_info_vsplit = 0
" info window size
let g:task_info_size = 15
" info window position
let g:task_info_position = 'belowright'
" directory to store log files defaults to taskwarrior data.location
let g:task_log_directory = '~/.task'
" max number of historical entries
let g:task_log_max = '20'
" forward arrow shown on statusline
let g:task_left_arrow = ' <<'
" backward arrow ...
let g:task_left_arrow = '>> '
```
----
### Syntax highlightling:
Default scheme:
```vim
highlight default link taskwarrior_tablehead Tabline
highlight default link taskwarrior_field IncSearch
highlight default link taskwarrior_selected Visual
highlight default link taskwarrior_id VarId
highlight default link taskwarrior_project String
highlight default link taskwarrior_Status Include
highlight default link taskwarrior_priority Class
highlight default link taskwarrior_due Todo
highlight default link taskwarrior_end Keyword
highlight default link taskwarrior_description Normal
highlight default link taskwarrior_entry Special
highlight default link taskwarrior_depends Todo
highlight default link taskwarrior_tags Keyword
highlight default link taskwarrior_uuid VarId
highlight default link taskwarrior_urgency Todo
```
Feel free to change any of above by something like:
```vim
hi taskwarrior_xxx guibg = xxx guifg = xxx ctermbg = xxx ctermfg = xxx
```
in your vimrc.
### Acknowledgement:
* [vim-airline](https://github.com/bling/vim-airline) by bling
* [unite.vim](https://github.com/Shougo/unite.vim) by Shougo
* [webapi-vim](https://github.com/mattn/webapi-vim) by mattn
### License:
[MIT](https://raw.github.com/farseer90718/vim-taskwarrior/master/LICENSE.txt)
[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/farseer90718/vim-taskwarrior/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

View File

@ -1,41 +0,0 @@
function! airline#extensions#taskwarrior#apply(...)
if &ft == 'taskreport'
call a:1.add_section('airline_a', ' Taskwarrior ')
call a:1.add_section('airline_b', ' %{b:command} %{&readonly ? g:airline_symbols.readonly : ""}')
call a:1.add_section('airline_b', ' @%{b:context} ')
call a:1.add_section('airline_b', g:task_left_arrow.' %{b:hist > 1 ? g:task_right_arrow : ""}')
call a:1.add_section('airline_c', ' %{b:filter} ')
call a:1.add_section('airline_c', ' %{b:sstring} ')
call a:1.split()
call a:1.add_section('airline_x', ' %{b:now} ')
call a:1.add_section('airline_x', ' %{b:task_report_columns[taskwarrior#data#current_index()]} ')
call a:1.add_section('airline_y', ' %{b:sort} ')
if b:active != '0'
call airline#parts#define_text('active', ' '.b:active.' ')
call airline#parts#define_accent('active', 'orange')
call a:1.add_section('airline_z', airline#section#create(['active']))
endif
call a:1.add_section('airline_z', ' %{b:summary[0]} ')
call airline#parts#define_text('completed', ' '.b:summary[1].' ')
call airline#parts#define_accent('completed', 'green')
call a:1.add_section('airline_z', airline#section#create(['completed']))
call a:1.add_section('airline_z', ' %{b:summary[2]} ')
return 1
elseif &ft == 'taskinfo'
call a:1.add_section('airline_a', ' Taskinfo ')
call a:1.add_section('airline_b', ' %{b:command." ".g:airline_symbols.readonly }')
call a:1.add_section('airline_c', ' %{b:filter} ')
call a:1.split()
return 1
endif
endfunction
function s:context()
let con = split(system('task context show'), '\n')
let con = con =~ 'No context' ? 'none' : con
return con
endfunction
function! airline#extensions#taskwarrior#init(ext)
call a:ext.add_statusline_func('airline#extensions#taskwarrior#apply')
endfunction

View File

@ -1,44 +0,0 @@
function! taskinfo#init(command, filter, info)
if exists('g:task_info')
call taskinfo#quit()
endif
if a:command != 'info'
\ && exists('g:task_info_arg')
\ && g:task_info_arg == [a:command, a:filter]
unlet g:task_info_arg
return
endif
execute g:task_info_position.' '.g:task_info_size.
\ (g:task_info_vsplit ? 'v' : '').'split'
edit taskinfo
let g:task_info = bufnr('%')
let g:task_info_arg = [a:command, a:filter]
setlocal noswapfile
setlocal modifiable
call append(0, a:info)
silent global/^[\t ]*$/delete
silent global/^[ -]\+$/delete
setlocal readonly
setlocal nomodifiable
setlocal buftype=nofile
setlocal nowrap
setlocal filetype=taskinfo
1
let b:command = a:command
let b:filter = a:filter
nnoremap <silent> <buffer> q :call taskinfo#quit()<CR>
nnoremap <silent> <buffer> <enter> :call taskinfo#quit()<CR>
if a:command != 'info'
wincmd W
endif
endfunction
function! taskinfo#quit()
silent! execute g:task_info.'bd!'
unlet g:task_info
endfunction

View File

@ -1,221 +0,0 @@
function! taskwarrior#list(...) abort
setlocal noreadonly
setlocal modifiable
let pos = getpos('.')
%delete
call taskwarrior#buffer_var_init()
let b:command = get(a:, 1, b:command)
let b:filter = get(a:, 2, b:filter)
let b:type = get(a:, 3, b:type)
let b:rc = get(a:, 4, b:rc). ' rc.defaultheight=0'
let b:rc .= ' '.join(filter(split(b:filter, ' '), "v:val =~ '^rc\..*'"))
let b:filter = join(filter(split(b:filter, ' '), "v:val !~ '^rc\..*'"))
let rcs = split(b:rc, ' ')
let b:rc = join(filter(copy(rcs), "match(rcs, matchstr(v:val, '^[^=:]*'), v:key+1) == -1"), ' ')
if b:type == 'special'
setlocal buftype=nofile
call append(0, split(system('task '.b:rc.' '.b:filter.' '.b:command), '\n'))
silent global/^[\t ]*$/delete
execute 'setlocal filetype=task_'.b:command
nnoremap <buffer> q :call taskwarrior#Bclose(bufnr('%'))<CR>
call setpos('.', pos)
return
endif
let b:hist = get(b:, 'hist', 1)
call taskwarrior#log#history('write')
let rcc = matchstr(b:rc, 'rc\.report\.'.b:command.'\.columns.\zs\S*')
let rcl = matchstr(b:rc, 'rc\.report\.'.b:command.'\.labels.\zs\S*')
" let b:task_report_columns = rcc == '' ? split(system("task _get -- rc.report.".b:command.".columns")[0:-2], ',') : split(rcc, ',')
" let b:task_report_labels = rcl == '' ? split(system("task _get -- rc.report.".b:command.".labels")[0:-2], ',') : split(rcl, ',')
let b:task_report_columns = rcc == '' ? split(matchstr(system("task show |grep report.".b:command.".columns")[0:-2], '\S*$'), ',') : split(rcc, ',')
let b:task_report_labels = rcl == '' ? split(matchstr(system("task show |grep report.".b:command.".labels")[0:-2], '\S*$'), ',') : split(rcl, ',')
let line1 = join(b:task_report_labels, ' ')
let context = split(substitute(
\ system('task '.b:rc.' '.b:filter.' '.b:command),
\ '\[[0-9;]\+m',
\ '', 'g'),
\ '\n')
let split_lineno = match(context, '^[ -]\+$')
if split_lineno == -1
call append(0, line1)
else
let end = len(context)-match(reverse(copy(context)), '^$')
call append(0, context[split_lineno-1:end-1])
silent global/^[\t ]*$/delete
silent global/^[ -]\+$/delete
endif
call filter(b:task_report_columns, "index(split(getline(1), ' '), b:task_report_labels[v:key]) != -1")
call filter(b:task_report_labels, "index(split(getline(1), ' '), v:val) != -1")
let b:task_columns = []
let ci = 0
1
while ci != -1
let b:task_columns += [ci]
let ci = search('\s\S', 'W', 1)
let ci = ci > 0 ? virtcol('.') : -1
endwhile
let b:task_columns += [999]
let b:summary = taskwarrior#data#global_stats()
let b:sort = taskwarrior#sort#order_list()[0]
let a_tasks = split(system('task active limit:1 rc.verbose:nothing
\ rc.report.active.sort=start-
\ rc.report.active.columns=start.active,start.age,id,description.desc
\ rc.report.active.labels=A,Age,ID,Description'), '\n')
let b:now = len(a_tasks) > 0 ? a_tasks[-1] : ''
let b:active = split(system('task start.any: count'), '\n')[0]
let b:selected = []
let b:sline = []
let b:sstring = ''
let con = split(system('task context show'), '\n')[0]
let b:context = con =~ 'No context' ? 'none' :
\ matchstr(con, 'Context .\zs\S*\ze. ')
setlocal filetype=taskreport
if exists('b:ct')
for l in range(line('$'))
if taskwarrior#data#get_uuid(l) == b:ct
let pos[1] = l
break
endif
endfor
endif
call setpos('.', pos)
endfunction
function! taskwarrior#buffer_var_init()
let b:command = get(b:, 'command', g:task_report_name)
let b:filter = get(b:, 'filter', '')
let b:type = get(b:, 'type', 'report')
let b:rc = get(b:, 'rc', g:task_rc_override)
endfunction
function! taskwarrior#init(...)
if exists(':TagbarClose')
TagbarClose
endif
let argstring = join(a:000, ' ')
let [command, filter, type] = taskwarrior#command_type(argstring)
let rc = g:task_rc_override
if type == 'interactive'
if !g:task_readonly
execute '!task '.argstring
call taskwarrior#refresh()
endif
return
endif
execute 'edit task\ '.command.'\ '.type
if exists('g:task_view')
let g:task_view += [bufnr('%')]
else
let g:task_view = [bufnr('%')]
endif
setlocal noswapfile
call taskwarrior#list(command, filter, type, rc)
endfunction
function! taskwarrior#refresh()
if exists('g:task_view')
for bufn in g:task_view
execute bufn.'buffer'
call taskwarrior#list()
endfor
else
call taskwarrior#init()
endif
endfunction
function! taskwarrior#Bclose(buffer)
if a:buffer =~ '^\d\+$'
let btarget = bufnr(str2nr(a:buffer))
else
let btarget = bufnr(a:buffer)
endif
if bufname(btarget) == ''
bdelete
return
endif
" Numbers of windows that view target buffer which we will delete.
let wnums = filter(range(1, winnr('$')), 'winbufnr(v:val) == btarget')
let wcurrent = winnr()
for w in wnums
execute w.'wincmd w'
let prevbuf = bufnr('#')
if prevbuf > 0 && buflisted(prevbuf) && prevbuf != w
buffer #
else
bprevious
endif
if btarget == bufnr('%')
" Numbers of listed buffers which are not the target to be deleted.
let blisted = filter(range(1, bufnr('$')), 'buflisted(v:val) && v:val != btarget')
" Listed, not target, and not displayed.
let bhidden = filter(copy(blisted), 'bufwinnr(v:val) < 0')
" Take the first buffer, if any (could be more intelligent).
let bjump = (bhidden + blisted + [-1])[0]
if bjump > 0
execute 'buffer '.bjump
else
enew
endif
endif
endfor
execute 'silent! bdelete '.btarget
execute wcurrent.'wincmd w'
endfunction
function! taskwarrior#hi_field()
silent! syntax clear taskwarrior_field
let index = taskwarrior#data#current_index()
execute 'syntax match taskwarrior_field /\%>1l\%'.line('.').'l\%'.(b:task_columns[index]+1).'v.*\%<'.(b:task_columns[index+1]+1).'v/'
endfunction
function! taskwarrior#quit()
call taskwarrior#Bclose(bufnr('%'))
call remove(g:task_view, index(g:task_view, bufnr('%')))
endfunction
function! taskwarrior#quit_all()
for bufn in g:task_view
call taskwarrior#Bclose(bufn)
endfor
let g:task_view = []
endfunction
function! taskwarrior#system_call(filter, command, args, mode)
if a:mode == 'silent'
call system('task '.a:filter.' '.a:command.' '.a:args)
elseif a:mode == 'echo'
echo "\n----------------\n"
echo system('task '.a:filter.' '.a:command.' '.a:args)
else
execute '!task '.a:filter.' '.a:command.' '.a:args
endif
call taskwarrior#refresh()
endfunction
function! taskwarrior#command_type(string)
for sub in split(a:string, ' ')
if index(g:task_report_command, sub) != -1
return [ sub, substitute(' '.a:string, ' '.sub, '', ''), 'report' ]
elseif index(g:task_interactive_command, sub) != -1
return [ sub, substitute(' '.a:string, ' '.sub, '', ''), 'interactive' ]
elseif index(g:task_all_commands, sub) != -1
return [ sub, substitute(' '.a:string, ' '.sub, '', ''), 'special' ]
endif
endfor
return [ g:task_report_name, a:string, 'report' ]
endfunction

View File

@ -1,370 +0,0 @@
function! taskwarrior#action#new()
call taskwarrior#system_call('', 'add', taskwarrior#data#get_args('add'), 'echo')
endfunction
function! taskwarrior#action#set_done()
call taskwarrior#system_call(taskwarrior#data#get_uuid(), ' done', '', 'silent')
endfunction
function! taskwarrior#action#urgency() abort
let cc = taskwarrior#data#current_column()
let udas = split(system('task _udas'), '\n')
let cmap = { 'start' : 'active',
\ 'entry' : 'age',
\ 'depends' : 'blocked',
\ 'parent' : 'blocking',
\ 'wait' : 'waiting',
\ 'description' : 'annotations'
\ }
let isuda = 0
if has_key(cmap, cc)
let cc = cmap[cc]
elseif index(['due', 'priority', 'project', 'tags', 'scheduled']
\ , cc) == -1
if index(udas, cc) == -1
call taskwarrior#sort#by_arg('urgency-')
return
else
let isuda = 1
endif
endif
let rcfile = $HOME.'/.taskrc'
if filereadable(rcfile)
let cv = taskwarrior#data#get_value_by_column(line('.'), cc)
let option = isuda ? 'urgency.uda.'.cc.'.coefficient' :
\ 'urgency.'.cc.'.coefficient'
if len(cv)
let ctag = expand('<cword>')
if cc == 'tags' && index(split(cv), ctag) != -1
let option = 'urgency.user.tag.'.ctag.'.coefficient'
elseif cc == 'project' && cv =~ '^[^ \t%\\*]\+$'
let pl = split(cv, '\.')
let idx = index(pl, expand('<cword>'))
let option = 'urgency.user.project.'.
\ join(pl[0:idx], '.').'.coefficient'
elseif isuda && cv =~ '^\w\+$'
let option = 'urgency.uda.'.cc.'.'.cv.'.coefficient'
endif
endif
let default_raw = split(system('task _get rc.'.option), '\n')
let default = len(default_raw) ? default_raw[0] : '0'
let new = input(option.' : ', default)
let lines = readfile(rcfile)
let index = match(lines, option)
if str2float(new) == str2float(default)
elseif str2float(new) == 0
call filter(lines, 'v:val !~ option')
elseif index == -1
call add(lines, option.'='.new)
else
let lines[index] = option.'='.new
endif
call writefile(lines, rcfile)
endif
call taskwarrior#sort#by_arg('urgency-')
execute 'normal! :\<Esc>'
endfunction
function! taskwarrior#action#modify(mode)
let uuid = taskwarrior#data#get_uuid()
if uuid == ''
return
endif
if a:mode == 'current'
let field = taskwarrior#data#current_column()
if index(['id', 'uuid', 'status', 'urgency'], field) != -1
return
elseif field == 'description'
call taskwarrior#system_call(uuid, 'modify', taskwarrior#data#get_args('modify', [field]), 'external')
else
call taskwarrior#system_call(uuid, 'modify', taskwarrior#data#get_args('modify', [field]), 'silent')
endif
else
call taskwarrior#system_call(uuid, 'modify', taskwarrior#data#get_args('modify'), 'external')
endif
endfunction
function! taskwarrior#action#delete()
let uuid = taskwarrior#data#get_uuid()
if uuid == ''
call taskwarrior#action#annotate('del')
else
let ccol = taskwarrior#data#current_column()
if index(['project', 'tags', 'due', 'priority', 'start', 'depends'], ccol) != -1
call taskwarrior#system_call(uuid, 'modify', ccol.':', 'silent')
else
execute '!task '.uuid.' delete'
endif
endif
call taskwarrior#refresh()
endfunction
function! taskwarrior#action#remove()
execute '!task '.taskwarrior#data#get_uuid().' delete'
call taskwarrior#list()
endfunction
function! taskwarrior#action#annotate(op)
let ln = line('.')
let offset = -1
while ln > 1 && taskwarrior#data#get_uuid(ln) == ''
let ln -= 1
let offset += 1
endwhile
let uuid = taskwarrior#data#get_uuid(ln)
if uuid == ''
return
endif
if a:op == 'add'
let annotation = input('new annotation:', '', 'file')
call taskwarrior#system_call(uuid, ' annotate ', annotation, 'silent')
elseif a:op == 'del'
let annotation = input('annotation pattern to delete:')
call taskwarrior#system_call(uuid, ' denotate ', annotation, 'silent')
elseif offset >= 0
let taskobj = taskwarrior#data#get_query(uuid)
if exists('taskobj.annotations[offset].description')
let file = substitute(taskobj.annotations[offset].description, '\s*\/\s*', '/', 'g')
let file = escape(file, ' ')
let ft = 'text'
if executable('file')
let ft = system('file '.file)[:-2]
endif
if ft =~ 'text$'
execute 'e '.file
elseif ft !~ '(No such file or directory)' || file =~ '[a-z]*:\/\/[^ >,;]*'
if executable('xdg-open')
call system('xdg-open '.file.'&')
elseif executable('open')
call system('open '.file.'&')
endif
endif
endif
endif
endfunction
function! taskwarrior#action#filter()
let column = taskwarrior#data#current_column()
if index(['project', 'tags', 'status', 'priority'], column) != -1 && line('.') > 1
let filter = substitute(substitute(taskwarrior#data#get_args('modify', [column]), 'tags:', '+', ''), '\v^\s*\+(\s|$)', '', '')
elseif column =~ '\v^(entry|end|due)$'
let filter = column.'.before:'.input(column.'.before:', taskwarrior#data#get_value_by_column('.', column))
elseif column == 'description'
let filter = 'description:'.input('description:', taskwarrior#data#get_value_by_column('.', column) )
else
let filter = input('new filter:', b:filter, 'customlist,taskwarrior#complete#filter')
endif
let filter = substitute(filter, 'status:\(\s\|$\)', 'status.any: ', 'g')
if filter != b:filter
let b:filter = filter
let b:hist = 1
call taskwarrior#list()
endif
endfunction
function! taskwarrior#action#command()
if len(b:selected) == 0
let filter = taskwarrior#data#get_uuid()
else
let filter = join(b:selected, ',')
endif
let command = input('task '.filter.':', '', 'customlist,taskwarrior#complete#command')
if index(g:task_all_commands, b:command) == -1
return
endif
call taskwarrior#system_call(filter, command, '', 'interactive')
endfunction
function! taskwarrior#action#report()
let command = input('new report:', g:task_report_name, 'customlist,taskwarrior#complete#report')
if index(g:task_report_command, command) != -1 && command != b:command
let b:command = command
let b:hist = 1
call taskwarrior#list()
endif
endfunction
function! taskwarrior#action#paste()
if len(b:selected) == 0
return
elseif len(b:selected) < 3
call taskwarrior#system_call(join(b:selected, ','), 'duplicate', '', 'echo')
else
call taskwarrior#system_call(join(b:selected, ','), 'duplicate', '', 'interactive')
endif
endfunction
function! taskwarrior#action#columns_format_change(direction)
let ccol = taskwarrior#data#current_column()
if !exists('g:task_columns_format[ccol]')
return
endif
let clist = g:task_columns_format[ccol]
if len(clist) == 1
return
endif
let ccol_ful = b:task_report_columns[taskwarrior#data#current_index()]
let ccol_sub = matchstr(ccol_ful, '\.\zs.*')
let rcl = matchstr(b:rc, 'rc\.report\.'.b:command.'\.columns.\zs\S*')
" let dfl = system('task _get -- rc.report.'.b:command.'.columns')[0:-2]
let dfl = matchstr(system('task show | grep report.'.b:command.'.columns')[0:-2], '\S*$')
let index = index(clist, ccol_sub)
let index = index == -1 ? 0 : index
if a:direction == 'left'
let index -= 1
else
let index += 1
if index == len(clist)
let index = 0
endif
endif
let newsub = index == 0 ? '' : '.'.clist[index]
let b:rc .= ' rc.report.'.b:command.'.columns:'.
\ substitute(
\ rcl == '' ? dfl : rcl,
\ '[=:,]\zs'.ccol_ful.'\ze\(,\|$\)',
\ ccol.newsub, ''
\ )
let b:hist = 1
call taskwarrior#list()
endfunction
function! taskwarrior#action#date(count)
let ccol = taskwarrior#data#current_column()
if index(['due', 'end', 'entry'], ccol) == -1
return
endif
setlocal modifiable
if exists('g:loaded_speeddating')
call speeddating#increment(a:count)
elseif a:count > 0
execute 'normal! '.a:count.''
else
execute 'normal! '.-a:count.''
endif
let b:ct = taskwarrior#data#get_uuid()
call taskwarrior#system_call(b:ct, 'modify', ccol.':'.taskwarrior#data#get_value_by_column('.', ccol, 'temp'), 'silent')
endfunction
function! taskwarrior#action#visual(action) range
let line1 = getpos("'<")[1]
let line2 = getpos("'>")[1]
let fil = []
let lin = []
for l in range(line1, line2)
let uuid = taskwarrior#data#get_uuid(l)
if uuid !~ '^\s*$'
let fil += [uuid]
let lin += [l]
endif
endfor
let filter = join(fil, ',')
if a:action == 'done'
call taskwarrior#system_call(filter, 'done', '', 'interactive')
elseif a:action == 'delete'
call taskwarrior#system_call(filter, 'delete', '', 'interactive')
elseif a:action == 'info'
call taskinfo#init('information', filter, split(system('task rc.color=no information '.filter), '\n'))
elseif a:action == 'select'
for var in fil
let index = index(b:selected, var)
if index == -1
let b:selected += [var]
let b:sline += [lin[index(fil, var)]]
else
call remove(b:selected, index)
call remove(b:sline, index)
endif
endfor
let b:sstring = join(b:selected, ' ')
setlocal syntax=taskreport
endif
endfunction
function! taskwarrior#action#move_cursor(direction, mode)
let ci = taskwarrior#data#current_index()
if ci == -1 || (ci == 0 && a:direction == 'left') || (ci == len(b:task_columns)-1 && a:direction == 'right')
return
endif
if a:direction == 'left'
call search('\%'.(b:task_columns[ci-1]+1).'v', 'be')
else
call search('\%'.(b:task_columns[ci+1]+1).'v', 'e')
endif
if a:mode == 'skip' && taskwarrior#data#get_value_by_index('.', taskwarrior#data#current_index()) =~ '^\s*$'
call taskwarrior#action#move_cursor(a:direction, 'skip')
endif
endfunction
function! taskwarrior#action#undo()
if has("gui_running")
if exists('g:task_gui_term') && g:task_gui_term == 1
!task rc.color=off undo
elseif executable('xterm')
silent !xterm -e 'task undo'
elseif executable('urxvt')
silent !urxvt -e task undo
elseif executable('gnome-terminal')
silent !gnome-terminal -e 'task undo'
endif
else
sil !clear
!task undo
endif
call taskwarrior#refresh()
endfunction
function! taskwarrior#action#clear_completed()
!task status:completed delete
call taskwarrior#refresh()
endfunction
function! taskwarrior#action#sync(action)
execute '!task '.a:action.' '
call taskwarrior#refresh()
endfunction
function! taskwarrior#action#select()
let uuid = taskwarrior#data#get_uuid()
if uuid == ''
return
endif
let index = index(b:selected, uuid)
if index == -1
let b:selected += [uuid]
let b:sline += [line('.')]
else
call remove(b:selected, index)
call remove(b:sline, index)
endif
let b:sstring = join(b:selected, ' ')
setlocal syntax=taskreport
endfunction
function! taskwarrior#action#show_info(...)
if a:0 > 0
let command = 'info'
let filter = a:1
else
let ccol = taskwarrior#data#current_column()
let dict = { 'project': 'projects',
\ 'tags': 'tags',
\ 'id': 'stats',
\ 'depends': 'blocking',
\ 'recur': 'recurring',
\ 'due': 'overdue',
\ 'wait': 'waiting',
\ 'urgency': 'ready',
\ 'entry': 'history.monthly',
\ 'end': 'history.monthly'}
let command = get(dict, ccol, 'summary')
let uuid = taskwarrior#data#get_uuid()
if uuid !~ '^\s*$'
let command = substitute(command, '\v(summary|stats)', 'information', '')
let filter = taskwarrior#data#get_uuid()
else
let filter = b:filter
endif
endif
call taskinfo#init(command, filter, split(system('task rc.color=no '.command.' '.filter), '\n'))
endfunction

View File

@ -1,54 +0,0 @@
function! taskwarrior#complete#TW(A, L, P)
let command = copy(g:task_all_commands)
let filter = copy(g:task_filter)
let config = copy(g:task_all_configurations)
let contexts = split(system('task _context'), '\n')
let context_cmd = ['define', 'show', 'list', 'delete']
let words = split(a:L, ' ')
if len(words) > 1 && words[1] == 'context'
if len(words) == 2 || index(context_cmd, words[2]) == -1
return filter(context_cmd + contexts + ['none'],
\ 'match(v:val, a:A) != -1')
elseif words[2] == 'delete'
return filter(contexts, 'match(v:val, a:A) != -1')
else
return []
endif
endif
for ph in words
if ph == 'config' || ph == 'show'
return filter(config, 'match(v:val, a:A) != -1')
elseif ph =~ '^rc\..*'
return map(filter(config, 'match(v:val, a:A[3:]) != -1'),
\ "'rc.'.v:val")
elseif index(command, ph) != -1
return filter(filter, 'match(v:val, a:A) != -1')
endif
endfor
return filter(command+filter, 'match(v:val, a:A) != -1')
endfunction
function! taskwarrior#complete#sort(A, L, P)
let cols = map(split(system('task _columns'), '\n'),
\ 'matchstr(v:val, "^\\w*")')
return filter(cols, 'match(v:val, a:A) != -1')
endfunction
function! taskwarrior#complete#filter(A, L, P)
let lead = matchstr(a:A, '\S*$')
let lead = lead == '' ? '.*' : lead
let dict = copy(g:task_filter)
for ph in split(a:L, ' ')
call remove(dict, index(dict, matchstr(ph, '.*:\ze')))
endfor
return map(filter(dict, 'match(v:val, lead) != -1'),
\ "matchstr(a:L, '.*\\ze\\s\\+\\S*').' '.v:val")
endfunction
function! taskwarrior#complete#command(A, L, P)
return filter(copy(g:task_all_commands), 'match(v:val, a:A) != -1')
endfunction
function! taskwarrior#complete#report(A, L, P)
return filter(copy(g:task_report_command), 'match(v:val, a:A) != -1')
endfunction

View File

@ -1,132 +0,0 @@
function! taskwarrior#data#get_uuid(...)
let line = a:0 == 0 ? '.' : a:1
let vol = taskwarrior#data#get_value_by_column(line, 'uuid')
let vol = vol =~ '[0-9a-f]\{8}\(-[0-9a-f]\{4}\)\{3}-[0-9a-f]\{12}' ?
\ vol : taskwarrior#data#get_value_by_column(line, 'id')
return vol =~ '^\s*-*\s*$' ? '' : vol
endfunction
function! taskwarrior#data#get_args(...)
if a:0 == 0
return
elseif a:0 == 1
return taskwarrior#data#get_args(a:1, g:task_default_prompt)
endif
let arg = ' '
for key in a:2
let default = a:1 == 'modify' ?
\ taskwarrior#data#get_value_by_column('.', key)
\ : ''
let temp = shellescape(input(key.":", default), 1)
if key == 'description'
let arg .= ' '.temp
elseif temp !~ '^[ \t]*$' || a:1 == 'modify'
let arg .= ' '.key.':'.temp
endif
endfor
echom arg
return arg
endfunction
function! taskwarrior#data#get_value_by_column(line, column, ...)
if a:line == 1 || (a:line == '.' && line('.') == 1)
return ''
endif
if a:column == 'id' || a:column == 'uuid' || exists('a:1')
let index = match(b:task_report_columns, '^'.a:column.'.*')
return taskwarrior#data#get_value_by_index(a:line, index(b:task_report_columns, a:column))
else
let dict = taskwarrior#data#get_query()
let val = get(dict, a:column, '')
if type(val) == type('')
return val
elseif type(val) == type([])
return join(val, ' ')
else
return string(val)
endif
endif
endfunction
function! taskwarrior#data#get_value_by_index(line, index)
if exists('b:task_columns[a:index]')
return substitute(getline(a:line)[b:task_columns[a:index]:b:task_columns[a:index+1]-1], '\(\s*$\|^\s*\)', '', 'g')
endif
return ''
endfunction
function! taskwarrior#data#current_index()
let i = 0
while i < len(b:task_columns) && virtcol('.') >= b:task_columns[i]
let i += 1
endwhile
return i-1
endfunction
function! taskwarrior#data#current_column()
return matchstr(b:task_report_columns[taskwarrior#data#current_index()], '^\w\+')
endfunction
function! taskwarrior#data#get_stats(method)
let dict = {}
if a:method != 'current'
let stat = split(system('task '.a:method.' stats'), '\n')
else
let uuid = taskwarrior#data#get_uuid()
let stat = split(system('task '.taskwarrior#data#get_uuid().' stats'), '\n')
if uuid == '' || len(stat) < 5
return {}
endif
endif
for line in stat[2:-1]
if line !~ '^\W*$'
let dict[split(line, '\s\s')[0]] = substitute(split(line, '\s\s')[-1], '^\s*', '', '')
endif
endfor
return dict
endfunction
function! taskwarrior#data#get_query(...)
let uuid = get(a:, 1, taskwarrior#data#get_uuid())
if uuid == ''
return {}
endif
let obj = webapi#json#decode(substitute(system(
\ 'task rc.verbose=off '.uuid.' export'),
\ '\nConfiguration.*', '', ''))
return type(obj) == 3 ? obj[0] : obj
endfunction
function! taskwarrior#data#global_stats()
let dict = taskwarrior#data#get_stats(b:filter)
return [
\ get(dict, 'Pending', 0),
\ get(dict, 'Completed', 0),
\ get(taskwarrior#data#get_stats(''), 'Pending', 0)
\ ]
endfunction
function! taskwarrior#data#category()
let dict = {}
let dict.Pending = []
let dict.Waiting = []
let dict.Recurring = []
let dict.Completed = []
for i in range(2, line('$'))
let uuid = taskwarrior#data#get_uuid(i)
if uuid == ''
continue
endif
let subdict = taskwarrior#data#get_stats(uuid)
if subdict.Pending == '1'
let dict.Pending += [i]
elseif subdict.Waiting == '1'
let dict.Waiting += [i]
elseif subdict.Recurring == '1'
let dict.Recurring += [i]
elseif subdict.Completed == '1'
let dict.Completed += [i]
endif
endfor
return dict
endfunction

View File

@ -1,68 +0,0 @@
if !isdirectory(expand(g:task_log_directory))
call mkdir(expand(g:task_log_directory), 'p')
endif
let s:history_file = expand(g:task_log_directory.'/.vim_tw.history')
let s:bookmark_file = expand(g:task_log_directory.'/.vim_tw.bookmark')
function! taskwarrior#log#history(action)
if findfile(s:history_file) == ''
call writefile([], s:history_file)
endif
if a:action == 'write' && filewritable(s:history_file) && b:hist == 1
let fl = readfile(s:history_file)
let numb = len(fl)
let last = numb ? substitute(fl[-1], '\v($|\n|\t|\s)', '', 'g') : ''
let current = join([b:command, b:filter, b:rc], ' ')
if last == substitute(current, '[\t ]', '', 'g')
return
endif
call add(fl, current)
if numb >= g:task_log_max
call remove(fl, 0)
endif
call writefile(fl, s:history_file)
elseif a:action == 'read' && filereadable(s:history_file)
call taskwarrior#init(join(split(readfile(s:history_file)[-1], ' '), ' '))
elseif a:action == 'clear'
call writefile([], s:history_file)
elseif a:action != 'write'
let hists = readfile(s:history_file)
if a:action == 'previous'
if b:hist >= len(hists)
return
endif
let b:hist += 1
elseif a:action == 'next'
if b:hist == 1
return
endif
let b:hist -= 1
endif
let hlist = split(substitute(hists[-b:hist], '\v($|\n)', ' ', ''), ' ')
if len(hlist) != 3
return
endif
let [b:command, b:filter, b:rc] = hlist
call taskwarrior#list()
endif
endfunction
function! taskwarrior#log#bookmark(action)
if findfile(s:bookmark_file) == ''
call writefile([], s:bookmark_file)
endif
if a:action == 'new' && filewritable(s:bookmark_file)
let now = b:command.' '.b:filter.' '.b:rc
let ext = readfile(s:bookmark_file)
if index(ext, now) == -1
execute 'redir >> '.s:bookmark_file
silent! echo now
redir END
echohl String
echomsg 'New bookmark added.'
echohl None
endif
elseif a:action == 'clear'
call writefile([], s:bookmark_file)
endif
endfunction

View File

@ -1,88 +0,0 @@
function! taskwarrior#sort#by_arg(...)
let args = substitute(join(a:000, ' '), '\s\+', ',', 'g')
let args = substitute(args, '\w\zs,', '-,', 'g')
let args = substitute(args, '\w\zs$', '-', '')
if args =~ '^\s*$'
let b:rc = substitute(b:rc, 'rc.report.'.b:command.'.sort[:=]\S*', '', 'g')
else
let b:rc .= args == '' ? '' : ' rc.report.'.b:command.'.sort:'.args
endif
let b:hist = 1
call taskwarrior#list()
endfunction
function! taskwarrior#sort#by_column(polarity, column)
let fromrc = matchstr(b:rc, 'rc\.report\.'.b:command.'\.sort.\zs\S*')
" let default = system('task _get -- rc.report.'.b:command.'.sort')[0:-2]
let default = matchstr(system('task show | grep report.'.b:command.'.sort')[0:-2], '\S*$')
let colshort = map(copy(b:task_report_columns), 'matchstr(v:val, "^\\w*")')
let ccol = index(colshort, a:column) == -1 ?
\ taskwarrior#data#current_column() :
\ a:column
let list = split(fromrc, ',')
let ind = index(split(fromrc, '[-+],\='), ccol)
let dlist = split(default, ',')
let dind = index(split(default, '[-+],\='), ccol)
if fromrc == ''
if dind != -1
if a:polarity == 'm'
if dind == 0
let dlist[0] = dlist[0][0:-2].(dlist[0][-1:-1] == '+' ? '-' : '+')
endif
call insert(dlist, remove(dlist, dind))
elseif dlist[dind] == ccol.a:polarity
return
else
let dlist[dind] = ccol.a:polarity
endif
let b:rc .= ' rc.report.'.b:command.'.sort:'.join(dlist, ',')
else
let polarity = a:polarity == 'm' ? '-' : a:polarity
let b:rc .= ' rc.report.'.b:command.'.sort:'.ccol.polarity.','.default
endif
elseif ind != -1
if a:polarity == 'm'
if ind == 0
let list[0] = list[0][0:-2].(list[0][-1:-1] == '+' ? '-' : '+')
else
call insert(list, remove(list, ind))
endif
elseif list[ind] == ccol.a:polarity
if a:polarity == '+'
call insert(list, remove(list, ind), ind > 1 ? ind-1 : 0)
else
if ind > len(list)-3
call add(list, remove(list, ind))
else
call insert(list, remove(list, ind), ind+1)
endif
endif
else
let list[ind] = ccol.a:polarity
endif
let g:listabc = list
let b:rc = substitute(b:rc, 'report\.'.b:command.'\.sort.'.fromrc, 'report.'.b:command.'.sort:'.join(list, ','), '')
else
let polarity = a:polarity == 'm' ? '-' : a:polarity
let b:rc = substitute(b:rc, 'report\.'.b:command.'\.sort.', 'report.'.b:command.'.sort:'.ccol.polarity.',', '')
endif
let b:hist = 1
call taskwarrior#list()
endfunction
function! taskwarrior#sort#order_list()
let fromrc = matchstr(b:rc, 'rc\.report\.'.b:command.'\.sort.\zs\S*')
if fromrc == ''
" let list = split(system('task _get -- rc.report.'.b:command.'.sort')[0:-2], ',')
let list = split(matchstr(system('task show | grep report.'.b:command.'.sort')[0:-2], '\S*$'), ',')
else
let list = split(fromrc, ',')
endif
while exists('list[0]') && match(b:task_report_columns, list[0][0:-2]) == -1 && system('task count '.list[0][0:-2].'.any:')[0] == '0'
call remove(list, 0)
endwhile
if len(list) == 0
let list = ['status-']
endif
return list
endfunction

View File

@ -1,23 +0,0 @@
let s:save_cpo = &cpo
set cpo&vim
function! unite#kinds#task#define()
return s:kind
endfunction
let s:kind = {
\ 'name' : 'task',
\ 'default_action' : 'show',
\ 'action_table': {},
\}
let s:kind.action_table.show = {
\ 'description' : 'Show report',
\ }
function! s:kind.action_table.show.func(candidate)
call taskwarrior#init(join(split(a:candidate.word, '[ \t]'), ' '))
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo

View File

@ -1,68 +0,0 @@
let s:save_cpo = &cpo
set cpo&vim
let s:template = {
\ 'name' : 'task/',
\ 'description' : 'vim-taskwarrior ',
\ 'filters' : ['matcher_regexp'],
\ 'action_table': {},
\ 'hooks' : {},
\ }
let s:bookmark = {
\ 'name' : 'bookmark',
\ 'logfile' : expand(g:task_log_directory.'/.vim_tw.bookmark')
\ }
let s:history = {
\ 'name' : 'history',
\ 'logfile' : expand(g:task_log_directory.'/.vim_tw.history')
\ }
function! s:make_source(dict)
let source = deepcopy(s:template)
let source.name .= a:dict.name
let source.description .= a:dict.name
let source.logfile = a:dict.logfile
function! source.hooks.on_syntax(args, context)
syntax match uniteSource__task_rc /rc.*/ contained containedin=ALL contains=uniteCandidateInputKeyword
syntax match uniteSource__task_report /\w\+\ze[ \t]\+/ contained containedin=ALL
highlight default link uniteSource__task_rc String
highlight default link uniteSource__task_report Keyword
endfunction
function! source.gather_candidates(args, context)
if findfile(self.logfile) == ''
call writefile([], self.logfile)
endif
return map(reverse(readfile(self.logfile)),
\ '{"word": v:val,
\ "kind": "task",
\ "source": "task/" . self.name,
\ }')
endfunction
let source.action_table.delete = {
\ 'description' : 'remove the item',
\ }
function! source.action_table.delete.func(candidate)
let current = substitute(a:candidate.word, '\s', '', 'g')
let lfile = g:task_log_directory.'/.vim_tw.bookmark'
let all = readfile(lfile)
let allns = map(copy(all), "substitute(v:val, '[ \t]', '', 'g')")
call remove(all, index(allns, current))
call writefile(all, lfile)
endfunction
return source
endfunction
function! unite#sources#task#define()
return map([s:bookmark, s:history], 's:make_source(v:val)')
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo

View File

@ -1,135 +0,0 @@
" json
" Last Change: 2012-03-08
" Maintainer: Yasuhiro Matsumoto <mattn.jp@gmail.com>
" License: This file is placed in the public domain.
" Reference:
"
let s:save_cpo = &cpo
set cpo&vim
function! webapi#json#null()
return 0
endfunction
function! webapi#json#true()
return 1
endfunction
function! webapi#json#false()
return 0
endfunction
function! s:nr2byte(nr)
if a:nr < 0x80
return nr2char(a:nr)
elseif a:nr < 0x800
return nr2char(a:nr/64+192).nr2char(a:nr%64+128)
else
return nr2char(a:nr/4096%16+224).nr2char(a:nr/64%64+128).nr2char(a:nr%64+128)
endif
endfunction
function! s:nr2enc_char(charcode)
if &encoding == 'utf-8'
return nr2char(a:charcode)
endif
let char = s:nr2byte(a:charcode)
if strlen(char) > 1
let char = strtrans(iconv(char, 'utf-8', &encoding))
endif
return char
endfunction
function! s:fixup(val, tmp)
if type(a:val) == 0
return a:val
elseif type(a:val) == 1
if a:val == a:tmp.'null'
return function('webapi#json#null')
elseif a:val == a:tmp.'true'
return function('webapi#json#true')
elseif a:val == a:tmp.'false'
return function('webapi#json#false')
endif
return a:val
elseif type(a:val) == 2
return a:val
elseif type(a:val) == 3
return map(a:val, 's:fixup(v:val, a:tmp)')
elseif type(a:val) == 4
return map(a:val, 's:fixup(v:val, a:tmp)')
else
return string(a:val)
endif
endfunction
function! webapi#json#decode(json)
let json = iconv(a:json, "utf-8", &encoding)
if get(g:, 'webapi#json#parse_strict', 1) == 1 && substitute(substitute(substitute(
\ json,
\ '\\\%(["\\/bfnrt]\|u[0-9a-fA-F]\{4}\)', '\@', 'g'),
\ '"[^\"\\\n\r]*\"\|true\|false\|null\|-\?\d\+'
\ . '\%(\.\d*\)\?\%([eE][+\-]\{-}\d\+\)\?', ']', 'g'),
\ '\%(^\|:\|,\)\%(\s*\[\)\+', '', 'g') !~ '^[\],:{} \t\n]*$'
throw json
endif
let json = substitute(json, '\n', '', 'g')
let json = substitute(json, '\\u34;', '\\"', 'g')
if v:version >= 703 && has('patch780')
let json = substitute(json, '\\u\(\x\x\x\x\)', '\=iconv(nr2char(str2nr(submatch(1), 16), 1), "utf-8", &encoding)', 'g')
else
let json = substitute(json, '\\u\(\x\x\x\x\)', '\=s:nr2enc_char("0x".submatch(1))', 'g')
endif
if get(g:, 'webapi#json#allow_nil', 0) != 0
let tmp = '__WEBAPI_JSON__'
while 1
if stridx(json, tmp) == -1
break
endif
let tmp .= '_'
endwhile
let [null,true,false] = [
\ tmp.'null',
\ tmp.'true',
\ tmp.'false']
sandbox let ret = eval(json)
call s:fixup(ret, tmp)
else
let [null,true,false] = [0,1,0]
sandbox let ret = eval(json)
endif
return ret
endfunction
function! webapi#json#encode(val)
if type(a:val) == 0
return a:val
elseif type(a:val) == 1
let json = '"' . escape(a:val, '\"') . '"'
let json = substitute(json, "\r", '\\r', 'g')
let json = substitute(json, "\n", '\\n', 'g')
let json = substitute(json, "\t", '\\t', 'g')
let json = substitute(json, '\([[:cntrl:]]\)', '\=printf("\x%02d", char2nr(submatch(1)))', 'g')
return iconv(json, &encoding, "utf-8")
elseif type(a:val) == 2
let s = string(a:val)
if s == "function('webapi#json#null')"
return 'null'
elseif s == "function('webapi#json#true')"
return 'true'
elseif s == "function('webapi#json#false')"
return 'false'
endif
elseif type(a:val) == 3
return '[' . join(map(copy(a:val), 'webapi#json#encode(v:val)'), ',') . ']'
elseif type(a:val) == 4
return '{' . join(map(keys(a:val), 'webapi#json#encode(v:val).":".webapi#json#encode(a:val[v:val])'), ',') . '}'
else
return string(a:val)
endif
endfunction
let &cpo = s:save_cpo
unlet s:save_cpo
" vim:set et:

View File

@ -1,262 +0,0 @@
*vim-tw.txt* a vim interface for |Taskwarrior| version 1.1 ~
_ _ _ _ ~
__ _(_)_ __ ___ | |_ __ _ __| |____ __ ____ _ _ _ _ _(_)___ _ _ ~
\ V / | ' \ |___| | _/ _` (_-< / /\ V V / _` | '_| '_| / _ \ '_| ~
\_/|_|_|_|_| \__\__,_/__/_\_\ \_/\_/\__,_|_| |_| |_\___/_| ~
~
~
==============================================================================
QUICK-REFERENCE *tw-quickref*
| a = add task | m = modify | S = taskd sync | <F1> = this help |
| A = annotate | M = mod prompts | s = sort col | <CR> = task info |
| d = task done | q = quit buffer | < = sort incr | <C>c = abort |
| D = task delete | r = new report | > = sort decr | <Del> = task/anno |
| c = task cmd | u = undo last | <TAB> = next col | <Space> = (de)select |
| f = add filter | x = del annot | <S-TAB>= prev col | p = dupe select |
| H = cycle fmt l | + = start task | <right>= r field | R = refresh |
| L = cycle fmt r | - = stop task | <left> = l field | X = clear done |
| J = next entry | K = prev entry | B = new bookmark | o = open annotation |
==============================================================================
CONTENTS *tw-contents*
01. Intro ........................................................ |Taskwarrior|
02. Prerequisites ........................................... |tw-prerequisites|
03. Mappings ..................................................... |tw-mappings|
04. Commands ..................................................... |tw-commands|
4.1 Global Commands ....................................... |tw-gl-commands|
4.2 Local Commands ........................................ |tw-lc-commands|
05. Customization ........................................... |tw-customization|
06. Troubleshooting ....................................... |tw-troubleshooting|
07. Contributions ........................................... |tw-contributions|
08. License ....................................................... |tw-license|
09. Changelog ................................................... |tw-changelog|
==============================================================================
1. INTRODUCTION *Taskwarrior*
Taskwarrior is a command-line todo list manager. It helps you manage task lists
with projects, tags, dates, dependencies, annotations, recurrences and apply
complex (or simple) queries with attribute-modifiers, booleans, regex filters,
custom attributes and color themes, and any number of built-in or customizable
reports. Task keeps data in JSON text files and it's always improving.
Find out more at https://taskwarrior.org and read man task and man taskrc.
vim-taskwarrior is a vim plugin that extends taskwarrior with an interactive
interface. It features a rich set of mappings and commands, is easy to customize
and makes adding, modifying, sorting and marking tasks done, fast, easy and fun!
Homepage: https://github.com/farseer90718/vim-taskwarrior, patches welcome!
==============================================================================
2. PREREQUISITES *tw-prerequisites*
This plugin requires Taskwarrior 2.2.0 or higher, although > 2.3.x is required
for taskd sync functions, and recommended in general, and well worth the price;
free :) see: https://taskwarrior.org/download/
Vim version 7.x is required.
the vim-airline plugin (https://github.com/bling/vim-airline) is not required
but it greatly enhances the status-bar and takes the guess-work out of reports.
If you experience line-wrapping issues, add the following line to your .vimrc
let g:task_rc_override = 'defaultwidth=999'
==============================================================================
3. MAPPING *tw-mappings*
<Plug>(taskwarrior_quickref) :quick reference
<Plug>(taskwarrior_quit) :quit the buffer
<Plug>(taskwarrior_skip_left) :move the cursor to the left field, skipping blanks
<Plug>(taskwarrior_step_left) :move the cursor to the left field without skipping
<Plug>(taskwarrior_skip_right) :... right ...
<Plug>(taskwarrior_step_right) :... ...
<Plug>(taskwarrior_sort_increase) :increase the priority of current field in sorting
<Plug>(taskwarrior_sort_decrease) :decrease ...
<Plug>(taskwarrior_sort_inverse) :invert the sorting method of the main field
<Plug>(taskwarrior_show_info) :show information
<Plug>(taskwarrior_filter) :apply a new filter to the tasks
<Plug>(taskwarrior_next_format) :change the format of current field to the next one
<Plug>(taskwarrior_previous_format) :... previous ...
<Plug>(taskwarrior_next_history) :next history of report
<Plug>(taskwarrior_previous_history) :previous ...
<Plug>(taskwarrior_new_bookmark) :add a new bookmark for current setup
<Plug>(taskwarrior_visual_show_info) :show informations about selected tasks
<Plug>(taskwarrior_annotate) :add a new annotation
<Plug>(taskwarrior_denotate) :delete the annotation
<Plug>(taskwarrior_open_annotate) :open the annotation as a file or url
<Plug>(taskwarrior_remove) :remove the task
<Plug>(taskwarrior_delete) :remove the task/annotation
<Plug>(taskwarrior_new) :new task
<Plug>(taskwarrior_command) :apply a command to selected tasks
<Plug>(taskwarrior_done) :set the selected tasks done
<Plug>(taskwarrior_report) :change report type
<Plug>(taskwarrior_refresh) :refresh the buffer
<Plug>(taskwarrior_clear_completed) :clear all completed tasks
<Plug>(taskwarrior_undo) :undo
<Plug>(taskwarrior_sync) :synchronise with the remote server
<Plug>(taskwarrior_modify_field) :modify current field of the task
<Plug>(taskwarrior_modify_task) :modify the fields specified in |g:task_default_prompt|
<Plug>(taskwarrior_paste) :duplicate the selected tasks
<Plug>(taskwarrior_start_task) :start a task
<Plug>(taskwarrior_stop_task) :stop a task
<Plug>(taskwarrior_select) :select a task
<Plug>(taskwarrior_increase) :increase a number/date
<Plug>(taskwarrior_decrease) :decrease ...
<Plug>(taskwarrior_visual_done) :set visual selected tasks done
<Plug>(taskwarrior_visual_delete) :delete visual selected tasks
<Plug>(taskwarrior_visual_select) :select visual selected tasks
Default ones
------------------------------------------------------------------------------
<F1> <Plug>(taskwarrior_quickref)
q <Plug>(taskwarrior_quit)
<left> <Plug>(taskwarrior_skip_left)
<S-tab> <Plug>(taskwarrior_step_left)
<right> <Plug>(taskwarrior_skip_right)
<tab> <Plug>(taskwarrior_step_right)
< <Plug>(taskwarrior_sort_increase)
> <Plug>(taskwarrior_sort_decrease)
s <Plug>(taskwarrior_sort_inverse)
<CR> <Plug>(taskwarrior_show_info)
f <Plug>(taskwarrior_filter)
H <Plug>(taskwarrior_next_format)
L <Plug>(taskwarrior_previous_format)
J <Plug>(taskwarrior_next_history)
K <Plug>(taskwarrior_previous_history)
B <Plug>(taskwarrior_new_bookmark)
<CR> <Plug>(taskwarrior_visual_show_info)
A <Plug>(taskwarrior_annotate)
x <Plug>(taskwarrior_denotate)
o <Plug>(taskwarrior_open_annotate)
D <Plug>(taskwarrior_remove)
<Del> <Plug>(taskwarrior_delete)
a <Plug>(taskwarrior_new)
c <Plug>(taskwarrior_command)
d <Plug>(taskwarrior_done)
r <Plug>(taskwarrior_report)
R <Plug>(taskwarrior_refresh)
X <Plug>(taskwarrior_clear_completed)
u <Plug>(taskwarrior_undo)
S <Plug>(taskwarrior_sync)
m <Plug>(taskwarrior_modify_field)
M <Plug>(taskwarrior_modify_task)
p <Plug>(taskwarrior_paste)
+ <Plug>(taskwarrior_start_task)
- <Plug>(taskwarrior_stop_task)
<Space> <Plug>(taskwarrior_select)
<C-A> <Plug>(taskwarrior_increase)
<C-X> <Plug>(taskwarrior_decrease)
d <Plug>(taskwarrior_visual_done)
D <Plug>(taskwarrior_visual_delete)
<Del> <Plug>(taskwarrior_visual_delete)
<Space> <Plug>(taskwarrior_visual_select)
How to change
------------------------------------------------------------------------------
Add something like these to your configuration files
augroup TaskwarriorMapping
autocmd!
autocmd FileType taskreport nmap <buffer> {key}
\ <Plug>(taskwarrior_...)
autocmd FileType taskreport nunmap <buffer> {key}
augroup END
==============================================================================
4. COMMAND *tw-commands*
4.1 Global Commands *tw-gl-commands*
------------------------------------------------------------------------------
Almost the same as the shell command 'task' *:TW*
Undo last move *:TWUndo*
Edit taskrc right away *:TWEditTaskrc*
Edit vitrc *:TWEditVitrc*
Delete completed tasks *:TWDeleteCompleted*
list history records using |unite.vim| *:TWHistory*
clear history *:TWHistoryClear*
list bookmarks using |unite.vim| *:TWBookmark*
clear bookmarks *:TWBookmarkClear*
4.2 Local Commands *tw-lc-commands*
------------------------------------------------------------------------------
Add an annotation *:TWAnnotate*
Mark task done *:TWComplete*
Delete a task *:TWDelete*
Delete an annotation *:TWDeleteAnnotation*
Make changes to a task interactively *:TWModifyInteractive*
Run the info report *:TWReportInfo*
Overide the sort method *:TWReportSort*
Synchronise with taskd server *:TWSync*
Toggle readonly option *:TWToggleReadonly*
Toggle highlight field option *:TWToggleHLField*
==============================================================================
5. CUSTOMIZATION *tw-customization*
*g:task_report_name*
Default task report type.
Default value is 'next'.
*g:task_highlight_field*
Whether the field under the cursor is highlighted.
Default value is 1.
*g:task_readonly*
Can not make change to task data when set to 1.
Default value is 0.
*g:task_rc_override*
Allows user to override task configurations. Seperated by space.
Default value is ''.
*g:task_default_prompt*
Default fields to ask when adding a new task.
Default value is:
['due', 'project', 'priority', 'description', 'tag', 'depends'].
*g:task_info_vsplit*
Whether the info window is splited vertically.
Default value is 0.
*g:task_info_size*
Info window size.
Default value is 15 for horizontal and 50 for vertical.
*g:task_info_position*
Info window position.
Default value is 'belowright'.
*g:task_log_directory*
Directory to store log files defaults to taskwarrior data.location.
Default value is taskwarrior data.location.
*g:task_log_max*
Max number of historical entries.
Default value is 10.
*g:task_left_arrow*
Forward arrow shown on statusline.
Default value is ' <<'
*g:task_right_arrow*
Backward arrow shown on statusline.
Default value is '>> '
*g:task_gui_term*
Uses gvim's dumb terminal for undo commands when set to 1.
Default value is 1
==============================================================================
6. TROUBLESHOOTING *tw-troubleshooting*
==============================================================================
7. CONTRIBUTIONS *tw-contributions*
Contributions and pull requests are welcome.
==============================================================================
8. LICENSE *tw-license*
MIT License.
Copyright © 2013 Zc He.
Copyright © 2013 David J Patrick.
==============================================================================
9. CHANGELOG *tw-changelog*
.. in progress!

View File

@ -1,145 +0,0 @@
setlocal buftype=nofile
setlocal nomodifiable
setlocal cursorline
setlocal startofline
setlocal nowrap
nnoremap <silent> <buffer> <Plug>(taskwarrior_quickref) :h tw-quickref<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_quit) :call taskwarrior#quit()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_quit_all) :call taskwarrior#quit_all()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_skip_left) :call taskwarrior#action#move_cursor('left', 'skip')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_step_left) :call taskwarrior#action#move_cursor('left', 'step')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_skip_right) :call taskwarrior#action#move_cursor('right', 'skip')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_step_right) :call taskwarrior#action#move_cursor('right', 'step')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_sort_increase) :call taskwarrior#sort#by_column('+', '')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_sort_decrease) :call taskwarrior#sort#by_column('-', '')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_sort_inverse) :call taskwarrior#sort#by_column('m', '')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_show_info) :call taskwarrior#action#show_info()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_filter) :call taskwarrior#action#filter()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_next_format) :call taskwarrior#action#columns_format_change('left')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_previous_format) :call taskwarrior#action#columns_format_change('right')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_next_history) :call taskwarrior#log#history('next')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_previous_history) :call taskwarrior#log#history('previous')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_new_bookmark) :call taskwarrior#log#bookmark('new')<CR>
vnoremap <silent> <buffer> <Plug>(taskwarrior_visual_show_info) :call taskwarrior#action#visual('info')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_annotate) :call taskwarrior#action#annotate('add')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_denotate) :call taskwarrior#action#annotate('del')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_open_annotate) :call taskwarrior#action#annotate('open')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_remove) :call taskwarrior#action#remove()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_delete) :call taskwarrior#action#delete()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_new) :call taskwarrior#action#new()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_command) :call taskwarrior#action#command()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_done) :call taskwarrior#action#set_done()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_report) :call taskwarrior#action#report()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_refresh) :call taskwarrior#list()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_clear_completed) :call taskwarrior#action#clear_completed()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_undo) :call taskwarrior#action#undo()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_urgency) :call taskwarrior#action#urgency()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_sync) :call taskwarrior#action#sync('sync')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_modify_field) :call taskwarrior#action#modify('current')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_modify_task) :call taskwarrior#action#modify('')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_paste) :call taskwarrior#action#paste()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_start_task) :call taskwarrior#system_call(taskwarrior#data#get_uuid(), 'start', '', 'silent')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_stop_task) :call taskwarrior#system_call(taskwarrior#data#get_uuid(), 'stop', '', 'silent')<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_select) :call taskwarrior#action#select()<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_increase) :<C-U>call taskwarrior#action#date(v:count1)<CR>
nnoremap <silent> <buffer> <Plug>(taskwarrior_decrease) :<C-U>call taskwarrior#action#date(-v:count1)<CR>
vnoremap <silent> <buffer> <Plug>(taskwarrior_visual_done) :call taskwarrior#action#visual('done')<CR>
vnoremap <silent> <buffer> <Plug>(taskwarrior_visual_delete) :call taskwarrior#action#visual('delete')<CR>
vnoremap <silent> <buffer> <Plug>(taskwarrior_visual_select) :call taskwarrior#action#visual('select')<CR>
nmap <buffer> <F1> <Plug>(taskwarrior_quickref)
nmap <buffer> Q <Plug>(taskwarrior_quit_all)
nmap <buffer> q <Plug>(taskwarrior_quit)
nmap <buffer> <left> <Plug>(taskwarrior_skip_left)
nmap <buffer> <S-tab> <Plug>(taskwarrior_step_left)
nmap <buffer> <right> <Plug>(taskwarrior_skip_right)
nmap <buffer> <tab> <Plug>(taskwarrior_step_right)
nmap <buffer> < <Plug>(taskwarrior_sort_increase)
nmap <buffer> > <Plug>(taskwarrior_sort_decrease)
nmap <buffer> s <Plug>(taskwarrior_sort_inverse)
nmap <buffer> <CR> <Plug>(taskwarrior_show_info)
nmap <buffer> f <Plug>(taskwarrior_filter)
nmap <buffer> H <Plug>(taskwarrior_next_format)
nmap <buffer> L <Plug>(taskwarrior_previous_format)
nmap <buffer> J <Plug>(taskwarrior_next_history)
nmap <buffer> K <Plug>(taskwarrior_previous_history)
nmap <buffer> B <Plug>(taskwarrior_new_bookmark)
vmap <buffer> <CR> <Plug>(taskwarrior_visual_show_info)
if g:task_highlight_field
autocmd CursorMoved <buffer> :call taskwarrior#hi_field()
else
autocmd! CursorMoved <buffer>
endif
if g:task_readonly
setlocal readonly
if hasmapto('<Plug>(taskwarrior_undo)')
nunmap <silent> <buffer> A
nunmap <silent> <buffer> x
nunmap <silent> <buffer> o
nunmap <silent> <buffer> D
nunmap <silent> <buffer> <Del>
nunmap <silent> <buffer> a
nunmap <silent> <buffer> c
nunmap <silent> <buffer> d
nunmap <silent> <buffer> r
nunmap <silent> <buffer> R
nunmap <silent> <buffer> X
nunmap <silent> <buffer> u
nunmap <silent> <buffer> S
nunmap <silent> <buffer> m
nunmap <silent> <buffer> M
nunmap <silent> <buffer> p
nunmap <silent> <buffer> +
nunmap <silent> <buffer> -
nunmap <silent> <buffer> <Space>
nunmap <silent> <buffer> <C-A>
nunmap <silent> <buffer> <C-X>
vunmap <silent> <buffer> d
vunmap <silent> <buffer> D
vunmap <silent> <buffer> <Del>
vunmap <silent> <buffer> <Space>
endif
else
nmap <silent> <buffer> A <Plug>(taskwarrior_annotate)
nmap <silent> <buffer> x <Plug>(taskwarrior_denotate)
nmap <silent> <buffer> o <Plug>(taskwarrior_open_annotate)
nmap <silent> <buffer> D <Plug>(taskwarrior_remove)
nmap <silent> <buffer> <Del> <Plug>(taskwarrior_delete)
nmap <silent> <buffer> a <Plug>(taskwarrior_new)
nmap <silent> <buffer> c <Plug>(taskwarrior_command)
nmap <silent> <buffer> d <Plug>(taskwarrior_done)
nmap <silent> <buffer> r <Plug>(taskwarrior_report)
nmap <silent> <buffer> R <Plug>(taskwarrior_refresh)
nmap <silent> <buffer> X <Plug>(taskwarrior_clear_completed)
nmap <silent> <buffer> u <Plug>(taskwarrior_undo)
nmap <silent> <buffer> U <Plug>(taskwarrior_urgency)
nmap <silent> <buffer> S <Plug>(taskwarrior_sync)
nmap <silent> <buffer> m <Plug>(taskwarrior_modify_field)
nmap <silent> <buffer> M <Plug>(taskwarrior_modify_task)
nmap <silent> <buffer> p <Plug>(taskwarrior_paste)
nmap <silent> <buffer> + <Plug>(taskwarrior_start_task)
nmap <silent> <buffer> - <Plug>(taskwarrior_stop_task)
nmap <silent> <buffer> <Space> <Plug>(taskwarrior_select)
nmap <silent> <buffer> <C-A> <Plug>(taskwarrior_increase)
nmap <silent> <buffer> <C-X> <Plug>(taskwarrior_decrease)
vmap <silent> <buffer> d <Plug>(taskwarrior_visual_done)
vmap <silent> <buffer> D <Plug>(taskwarrior_visual_delete)
vmap <silent> <buffer> <Del> <Plug>(taskwarrior_visual_delete)
vmap <silent> <buffer> <Space> <Plug>(taskwarrior_visual_select)
command! -buffer TWAdd :call taskwarrior#action#new()
command! -buffer TWAnnotate :call taskwarrior#action#annotate('add')
command! -buffer TWComplete :call taskwarrior#action#set_done()
command! -buffer TWDelete :call taskwarrior#action#delete()
command! -buffer TWDeleteAnnotation :call taskwarrior#action#annotate('del')
command! -buffer TWModifyInteractive :call taskwarrior#modify('')
command! -buffer TWSync :call taskwarrior#action#sync('sync')
endif
command! -buffer TWToggleReadonly :let g:task_readonly = (g:task_readonly ? 0 : 1) | call taskwarrior#refresh()
command! -buffer TWToggleHLField :let g:task_highlight_field = (g:task_highlight_field ? 0 : 1) | call taskwarrior#refresh()
command! -buffer -nargs=? -complete=customlist,taskwarrior#complete#sort TWReportSort :call taskwarrior#action#sort_by_arg(<q-args>)

View File

@ -1,110 +0,0 @@
if exists('g:loaded_taskwarrior') && g:loaded_taskwarrior
finish
endif
if !executable('task')
echoerr "This plugin depends on taskwarrior(https://taskwarrior.org)."
finish
endif
let g:task_report_command = get(g:, 'task_report_command', [])
let s:task_report_command = ['active', 'all', 'blocked', 'blocking', 'completed', 'list', 'long', 'ls', 'minimal', 'newest', 'next', 'oldest', 'overdue', 'ready', 'recurring', 'unblocked', 'waiting']
let g:task_report_command = extend(s:task_report_command, g:task_report_command)
let g:task_interactive_command = ['annotate', 'denotate', 'execute', 'duplicate',
\ 'append', 'prepend', 'stop', 'delete', 'done', 'undo',
\ 'config', 'edit', 'start', 'sync', 'synchronize', 'add',
\ 'modify', 'import', 'colors', 'color', 'logo', 'context']
let g:task_filter = ['description:', 'proj:', 'pri:', 'status:', 'tag:', 'due.before:', 'due.after:', 'entry.before', 'entry.after', 'end.before', 'end.after', '+']
let g:task_all_commands = split(system('task _command'), '\n')
let g:task_all_configurations = split(system('task _config'), '\n')
let g:task_report_name = index(g:task_report_command, get(g:, 'task_report_name')) != -1 ? get(g:, 'task_report_name') : 'next'
let g:task_highlight_field = get(g:, 'task_highlight_field', 1)
let g:task_readonly = get(g:, 'task_readonly', 0)
let g:task_rc_override = get(g:, 'task_rc_override', '')
let g:task_default_prompt = get(g:, 'task_default_prompt', ['due', 'project', 'priority', 'description', 'tag', 'depends'])
let g:task_info_vsplit = get(g:, 'task_info_vsplit', 0)
let g:task_info_size = get(g:, 'task_info_size', g:task_info_vsplit? 50 : 15)
let g:task_info_position = get(g:, 'task_info_position', 'belowright')
" let g:task_log_directory = get(g:, 'task_log_file', system('task _get -- rc.data.location')[0:-2])
let g:task_log_directory = get(g:, 'task_log_file', matchstr(system('task show | grep data.location')[0:-2], '\S*$'))
let g:task_log_max = get(g:, 'task_log_max', 10)
let g:task_left_arrow = get(g:, 'task_left_arrow', ' <<')
let g:task_right_arrow = get(g:, 'task_right_arrow', '>> ')
let g:task_readonly_symbol = get(g:, 'task_readonly_symbol', '  ')
let g:task_gui_term = get(g:, 'task_gui_term', 1)
let g:task_columns_format = {
\ 'depends': ['list', 'count', 'indicator'],
\ 'description': ['combined', 'desc', 'oneline', 'truncated', 'count'],
\ 'due': ['formatted', 'julian', 'epoch', 'iso', 'age', 'countdown'],
\ 'end': ['formatted', 'julian', 'epoch', 'iso', 'age', 'countdown'],
\ 'entry': ['formatted', 'julian', 'epoch', 'iso', 'age', 'countdown'],
\ 'id': ['number'],
\ 'imask': ['number'],
\ 'mask': ['default'],
\ 'modified': ['formatted', 'julian', 'epoch', 'iso', 'age', 'countdown'],
\ 'parent': ['long', 'short'],
\ 'priority': ['short', 'long'],
\ 'project': ['full', 'parent', 'indented'],
\ 'recur': ['duration', 'indicator'],
\ 'scheduled': ['formatted', 'julian', 'epoch', 'iso', 'age', 'countdown'],
\ 'start': ['formatted', 'julian', 'epoch', 'iso', 'age', 'countdown', 'active'],
\ 'status': ['long', 'short'],
\ 'tags': ['list', 'indicator', 'count'],
\ 'until': ['formatted', 'julian', 'epoch', 'iso', 'age', 'countdown'],
\ 'urgency': ['real', 'integer'],
\ 'uuid': ['long', 'short'],
\ 'wait': ['formatted', 'julian', 'epoch', 'iso', 'age', 'countdown'] }
"
"commented out pending taskd collision avoidance
"command! TaskPush call tw#remote('push')
"command! TaskPull call tw#remote('pull')
"command! TaskMerge call tw#remote('merge')
"
"commands;
"
command! -nargs=? -complete=customlist,taskwarrior#complete#TW TW :call taskwarrior#init(<q-args>)
command! -nargs=? TWReportInfo :call taskwarrior#action#show_info(<q-args>)
"command! TWConfigColor
command! TWDeleteCompleted :call taskwarrior#action#clear_completed()
"command! TWDeleteNote
"command! TWEdit
"command! TWEditAnnotation
"command! TWEditDescription
command! TWEditTaskrc :execute "e ".$HOME."/.taskrc"
command! TWEditVitrc :execute "e ".$HOME."/.vitrc"
command! TWEditTaskopenrc :execute "e ".$HOME."/.taskopenrc"
"command! TWExport
"command! TWHelp
command! TWHistory :Unite task/history
command! TWHistoryClear :call taskwarrior#log#history('clear')
command! TWBookmark :Unite task/bookmark
command! TWBookmarkClear :call taskwarrior#log#bookmark('clear')
"command! TWInsert
"command! TWImport
"command! TWNote
"command! TWOpen
"command! TWOpenInline
"command! TWReport
"command! TWReportAgenda
"command! TWReportBreak
"command! TWReportCalendar
"command! TWReportDesc
"command! TWReportEdit
"command! TWReportGantt
"command! TWReportProjects
"command! TWReportTags
"command! TWSyncFiles
"command! TWSyncStatus
"command! TWTheme
"command! TWThemeEdit
"command! TWThemeShow
command! TWUndo :call taskwarrior#action#undo()
"command! TWWiki
"command! TWWikiDiary
"command! TWWikiDiaryAdd
"command! TWWikiGenIndex
"command! TWWikiGenProject
"command! TWWikiGenTag
"command! TWWikiIndex
let g:loaded_taskwarrior = 1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1,8 +0,0 @@
if exists("b:current_syntax")
finish
endif
syntax match taskinfo_head /.*\%1l/
highlight default link taskinfo_head Tabline
let b:current_syntax = 'taskinfo'

View File

@ -1,43 +0,0 @@
if exists("b:current_syntax")
finish
endif
if exists('b:task_report_labels')
syntax match taskwarrior_tablehead /.*\%1l/
endif
for n in b:sline
execute 'syntax match taskwarrior_selected /.*\%'.n.'l/ contains=ALL'
endfor
if search('[^\x00-\xff]') == 0
let exp = 'syntax match taskwarrior_%s /\%%>1l\%%%dc.*\%%<%dc/'
else
let exp = 'syntax match taskwarrior_%s /\%%>1l\%%%dv.*\%%<%dv/'
endif
if exists('b:task_columns') && exists('b:task_report_columns')
for i in range(0, len(b:task_report_columns)-1)
if exists('b:task_columns['.(i+1).']')
execute printf(exp, matchstr(b:task_report_columns[i], '^\w\+') , b:task_columns[i]+1, b:task_columns[i+1]+1)
endif
endfor
endif
highlight default link taskwarrior_tablehead Tabline
highlight default link taskwarrior_field IncSearch
highlight default link taskwarrior_selected Visual
highlight default link taskwarrior_id VarId
highlight default link taskwarrior_project String
highlight default link taskwarrior_Status Include
highlight default link taskwarrior_priority Class
highlight default link taskwarrior_due Todo
highlight default link taskwarrior_end Keyword
highlight default link taskwarrior_description Normal
highlight default link taskwarrior_entry Special
highlight default link taskwarrior_depends Todo
highlight default link taskwarrior_tags Keyword
highlight default link taskwarrior_uuid VarId
highlight default link taskwarrior_urgency Todo
let b:current_syntax = 'taskreport'