parent
f3aa91c715
commit
9c4b13174e
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Requirements
|
||||
|
||||
1. VIM 9.0+ with `+job` and `+textprop` features enabled. NeoVIM is not supported at the moment.
|
||||
1. VIM 9.0+ with `+job` and `+textprop` features enabled, or NeoVIM 0.6.0+.
|
||||
2. Node.js 16.0+.
|
||||
|
||||
## Getting started
|
||||
|
|
|
|||
|
|
@ -73,17 +73,18 @@ function! tabby#Start()
|
|||
return
|
||||
endif
|
||||
|
||||
if !exists('*job_start') || !exists('*prop_type_add')
|
||||
let s:errmsg = 'Tabby requires Vim 9.0+ with +job and +textprop support.'
|
||||
let check_job = tabby#job#Check()
|
||||
if !check_job.ok
|
||||
let s:errmsg = check_job.message
|
||||
return
|
||||
endif
|
||||
|
||||
hi def TabbyCompletion guifg=#808080 ctermfg=8
|
||||
let s:prop_type = 'TabbyCompletion'
|
||||
if prop_type_get(s:prop_type) != v:null
|
||||
call prop_type_delete(s:prop_type)
|
||||
let check_inline_completion = tabby#inline_completion#Check()
|
||||
if !check_inline_completion.ok
|
||||
let s:errmsg = check_inline_completion.message
|
||||
return
|
||||
endif
|
||||
call prop_type_add(s:prop_type, {'highlight': 'TabbyCompletion'})
|
||||
call tabby#inline_completion#Init()
|
||||
|
||||
if !executable('node')
|
||||
let s:errmsg = 'Tabby requires node to be installed.'
|
||||
|
|
@ -100,7 +101,7 @@ function! tabby#Start()
|
|||
let s:tabby_status = 'connecting'
|
||||
|
||||
let command = 'node ' . node_script
|
||||
let s:tabby = job_start(command, #{
|
||||
let s:tabby = tabby#job#Start(command, #{
|
||||
\ in_mode: 'json',
|
||||
\ out_mode: 'json',
|
||||
\ out_cb: function('s:HandleNotification'),
|
||||
|
|
@ -114,7 +115,7 @@ endfunction
|
|||
|
||||
function! tabby#Stop()
|
||||
if tabby#Running()
|
||||
call job_stop(s:tabby)
|
||||
call tabby#job#Stop(s:tabby)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
|
@ -146,7 +147,7 @@ function! s:UpdateServerUrl()
|
|||
if !tabby#Running()
|
||||
return
|
||||
endif
|
||||
call ch_sendexpr(s:tabby, #{
|
||||
call tabby#job#Send(s:tabby, #{
|
||||
\ func: 'setServerUrl',
|
||||
\ args: [g:tabby_server_url],
|
||||
\ })
|
||||
|
|
@ -161,7 +162,7 @@ function! s:GetCompletion(id)
|
|||
if l:language == 'unknown'
|
||||
return
|
||||
endif
|
||||
call ch_sendexpr(s:tabby, #{
|
||||
call tabby#job#Send(s:tabby, #{
|
||||
\ func: 'getCompletion',
|
||||
\ args: [#{
|
||||
\ prompt: s:GetPrompt(),
|
||||
|
|
@ -179,7 +180,7 @@ function! s:PostEvent(event_type)
|
|||
if !exists('s:completion') || !exists('s:completion_index')
|
||||
return
|
||||
endif
|
||||
call ch_sendexpr(s:tabby, #{
|
||||
call tabby#job#Send(s:tabby, #{
|
||||
\ func: 'postEvent',
|
||||
\ args: [#{
|
||||
\ type: a:event_type,
|
||||
|
|
@ -190,7 +191,7 @@ function! s:PostEvent(event_type)
|
|||
endfunction
|
||||
|
||||
function! s:HandleNotification(channel, data)
|
||||
if a:data.event == 'statusChanged'
|
||||
if has_key(a:data, 'event') && (a:data.event == 'statusChanged')
|
||||
let s:tabby_status = a:data.status
|
||||
endif
|
||||
endfunction
|
||||
|
|
@ -199,10 +200,8 @@ function! s:HandleCompletion(id, channel, data)
|
|||
if !exists('s:trigger_id') || (a:id != s:trigger_id)
|
||||
return
|
||||
endif
|
||||
if a:data == v:null
|
||||
return
|
||||
endif
|
||||
if (type(a:data.choices) == v:t_list) && (len(a:data.choices) > 0)
|
||||
if (type(a:data) == v:t_dict) && has_key(a:data, 'choices') &&
|
||||
\ (type(a:data.choices) == v:t_list) && (len(a:data.choices) > 0)
|
||||
let s:completion = a:data
|
||||
let s:completion_index = 0
|
||||
call tabby#Show()
|
||||
|
|
@ -263,7 +262,7 @@ endfunction
|
|||
" Completion control
|
||||
|
||||
function! tabby#Show()
|
||||
call s:RemoveProp()
|
||||
call s:RemoveCompletion()
|
||||
if !s:CompletionAvailable()
|
||||
return
|
||||
endif
|
||||
|
|
@ -276,22 +275,12 @@ function! tabby#Show()
|
|||
return
|
||||
endif
|
||||
let lines = split(choice.text, "\n")
|
||||
call prop_add(line('.'), col('.'), #{
|
||||
\ type: s:prop_type,
|
||||
\ text: lines[0],
|
||||
\ })
|
||||
for line in lines[1:]
|
||||
call prop_add(line('.'), 0, #{
|
||||
\ type: s:prop_type,
|
||||
\ text: line,
|
||||
\ text_align: 'below',
|
||||
\ })
|
||||
endfor
|
||||
call tabby#inline_completion#Show(lines)
|
||||
let s:prop_shown_lines = lines
|
||||
call s:PostEvent('view')
|
||||
endfunction
|
||||
|
||||
function! tabby#ComsumeInsertion()
|
||||
function! tabby#ConsumeInsertion()
|
||||
if !exists('s:text_to_insert')
|
||||
return ''
|
||||
else
|
||||
|
|
@ -308,14 +297,14 @@ function! tabby#Accept(fallback)
|
|||
let lines = s:prop_shown_lines
|
||||
if len(lines) == 1
|
||||
let s:text_to_insert = lines[0]
|
||||
let insertion = "\<C-R>\<C-O>=tabby#ComsumeInsertion()\<CR>"
|
||||
let insertion = "\<C-R>\<C-O>=tabby#ConsumeInsertion()\<CR>"
|
||||
else
|
||||
let current_line = getbufline('%', line('.'), line('.'))[0]
|
||||
let suffix_chars_to_replace = len(current_line) - col('.') + 1
|
||||
let s:text_to_insert = join(lines, "\n")
|
||||
let insertion = repeat("\<Del>", suffix_chars_to_replace) . "\<C-R>\<C-O>=tabby#ComsumeInsertion()\<CR>"
|
||||
let insertion = repeat("\<Del>", suffix_chars_to_replace) . "\<C-R>\<C-O>=tabby#ConsumeInsertion()\<CR>"
|
||||
endif
|
||||
call s:RemoveProp()
|
||||
call s:RemoveCompletion()
|
||||
call s:PostEvent('select')
|
||||
return insertion
|
||||
endfunction
|
||||
|
|
@ -324,7 +313,7 @@ function! tabby#Dismiss(fallback)
|
|||
if !exists('s:prop_shown_lines')
|
||||
return a:fallback
|
||||
endif
|
||||
call s:RemoveProp()
|
||||
call s:RemoveCompletion()
|
||||
return ''
|
||||
endfunction
|
||||
|
||||
|
|
@ -363,7 +352,7 @@ function! tabby#Prev()
|
|||
endfunction
|
||||
|
||||
function! tabby#Clear()
|
||||
call s:RemoveProp()
|
||||
call s:RemoveCompletion()
|
||||
if exists('s:scheduled')
|
||||
call timer_stop(s:scheduled)
|
||||
unlet s:scheduled
|
||||
|
|
@ -389,11 +378,8 @@ function! s:CompletionAvailable()
|
|||
return v:true
|
||||
endfunction
|
||||
|
||||
function! s:RemoveProp()
|
||||
call prop_remove(#{
|
||||
\ type: s:prop_type,
|
||||
\ all: v:true,
|
||||
\ })
|
||||
function! s:RemoveCompletion()
|
||||
call tabby#inline_completion#Clear()
|
||||
if exists('s:prop_shown_lines')
|
||||
unlet s:prop_shown_lines
|
||||
endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,72 @@
|
|||
if exists('g:autoloaded_tabby_inline_completion')
|
||||
finish
|
||||
endif
|
||||
let g:autoloaded_tabby_inline_completion = 1
|
||||
|
||||
let s:vim = exists('*prop_type_add')
|
||||
let s:nvim = !s:vim && has('nvim') && exists('*nvim_buf_set_extmark')
|
||||
|
||||
function! tabby#inline_completion#Check()
|
||||
return #{
|
||||
\ ok: s:vim || s:nvim,
|
||||
\ message: 'Tabby requires Vim 9.0+ with +textprop feature support, or NeoVim 0.6.0+.',
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! tabby#inline_completion#Init()
|
||||
hi def TabbyCompletion guifg=#808080 ctermfg=8
|
||||
if s:vim
|
||||
let s:prop_type = 'TabbyCompletion'
|
||||
if prop_type_get(s:prop_type) != v:null
|
||||
call prop_type_delete(s:prop_type)
|
||||
endif
|
||||
call prop_type_add(s:prop_type, {'highlight': 'TabbyCompletion'})
|
||||
endif
|
||||
if s:nvim
|
||||
let s:nvim_namespace = nvim_create_namespace('TabbyCompletion')
|
||||
let s:nvim_highlight = 'TabbyCompletion'
|
||||
let s:nvim_extmark_id = 1
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! tabby#inline_completion#Show(lines)
|
||||
if len(a:lines) == 0
|
||||
return
|
||||
endif
|
||||
if s:vim
|
||||
call prop_add(line('.'), col('.'), #{
|
||||
\ type: s:prop_type,
|
||||
\ text: a:lines[0],
|
||||
\ })
|
||||
for line in a:lines[1:]
|
||||
call prop_add(line('.'), 0, #{
|
||||
\ type: s:prop_type,
|
||||
\ text: line,
|
||||
\ text_align: 'below',
|
||||
\ })
|
||||
endfor
|
||||
endif
|
||||
if s:nvim
|
||||
let opt = #{
|
||||
\ id: s:nvim_extmark_id,
|
||||
\ virt_text_win_col: virtcol('.') - 1,
|
||||
\ virt_text: [[a:lines[0], s:nvim_highlight]],
|
||||
\}
|
||||
if len(a:lines) > 1
|
||||
let opt.virt_lines = map(a:lines[1:], { i, l -> [[l, s:nvim_highlight]] })
|
||||
endif
|
||||
call nvim_buf_set_extmark(0, s:nvim_namespace, line('.') - 1, col('.') - 1, opt)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! tabby#inline_completion#Clear()
|
||||
if s:vim
|
||||
call prop_remove(#{
|
||||
\ type: s:prop_type,
|
||||
\ all: v:true,
|
||||
\ })
|
||||
endif
|
||||
if s:nvim
|
||||
call nvim_buf_del_extmark(0, s:nvim_namespace, s:nvim_extmark_id)
|
||||
endif
|
||||
endfunction
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
if exists('g:autoloaded_tabby_job')
|
||||
finish
|
||||
endif
|
||||
let g:autoloaded_tabby_job = 1
|
||||
|
||||
let s:vim = exists('*job_start')
|
||||
let s:nvim = !s:vim && has('nvim') && exists('*jobstart')
|
||||
|
||||
function! tabby#job#Check()
|
||||
return #{
|
||||
\ ok: s:vim || s:nvim,
|
||||
\ message: 'Tabby requires Vim 9.0+ with +job feature support, or NeoVim 0.6.0+.',
|
||||
\}
|
||||
endfunction
|
||||
|
||||
let s:nvim_job_map = {}
|
||||
|
||||
" Assume Json IO
|
||||
" Options 'out_cb', 'err_cb', 'exit_cb' supported
|
||||
" Return job id
|
||||
function! tabby#job#Start(command, ...)
|
||||
let options = get(a:, 1, {})
|
||||
if s:vim
|
||||
let opt = #{
|
||||
\ in_mode: 'json',
|
||||
\ out_mode: 'json',
|
||||
\ }
|
||||
if has_key(options, 'out_cb')
|
||||
let opt.out_cb = options.out_cb
|
||||
endif
|
||||
if has_key(options, 'err_cb')
|
||||
let opt.err_cb = options.err_cb
|
||||
endif
|
||||
if has_key(options, 'exit_cb')
|
||||
let opt.exit_cb = options.exit_cb
|
||||
endif
|
||||
return job_start(a:command, opt)
|
||||
endif
|
||||
if s:nvim
|
||||
let id = jobstart(a:command, #{
|
||||
\ on_stdout: function('s:NvimHandleStdout'),
|
||||
\ on_stderr: function('s:NvimHandleStderr'),
|
||||
\ on_exit: function('s:NvimHandleExit'),
|
||||
\})
|
||||
let s:nvim_job_map[id] = #{
|
||||
\ out_buffer: '',
|
||||
\ requests: {},
|
||||
\}
|
||||
if has_key(options, 'out_cb')
|
||||
let s:nvim_job_map[id].out_cb = options.out_cb
|
||||
endif
|
||||
if has_key(options, 'err_cb')
|
||||
let s:nvim_job_map[id].err_cb = options.err_cb
|
||||
endif
|
||||
if has_key(options, 'exit_cb')
|
||||
let s:nvim_job_map[id].exit_cb = options.exit_cb
|
||||
endif
|
||||
return id
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! tabby#job#Stop(job)
|
||||
if s:vim
|
||||
return job_stop(a:job)
|
||||
endif
|
||||
if s:nvim
|
||||
let ret = jobstop(a:job)
|
||||
call jobwait([a:job], 100)
|
||||
if has_key(s:nvim_job_map, a:job)
|
||||
unlet s:nvim_job_map[a:job]
|
||||
endif
|
||||
return ret
|
||||
endif
|
||||
endfunction
|
||||
|
||||
" Align to Vim's ch_sendexpr
|
||||
" Options 'callback' supported
|
||||
function! tabby#job#Send(job, data, ...)
|
||||
let options = get(a:, 1, {})
|
||||
if s:vim
|
||||
return ch_sendexpr(a:job, a:data, options)
|
||||
endif
|
||||
if s:nvim
|
||||
let id = s:NextRequestId()
|
||||
let request = [id, a:data]
|
||||
let s:nvim_job_map[a:job].requests[id] = {}
|
||||
if has_key(options, 'callback')
|
||||
let s:nvim_job_map[a:job].requests[id].callback = options.callback
|
||||
endif
|
||||
call chansend(a:job, json_encode(request) . "\n")
|
||||
endif
|
||||
endfunction
|
||||
|
||||
let s:request_id = 1
|
||||
function! s:NextRequestId()
|
||||
let s:request_id += 1
|
||||
return s:request_id
|
||||
endfunction
|
||||
|
||||
function! s:NvimHandleStdout(job, data, event)
|
||||
if !has_key(s:nvim_job_map, a:job)
|
||||
return
|
||||
endif
|
||||
let buf = s:nvim_job_map[a:job].out_buffer
|
||||
for data_line in a:data
|
||||
let buf .= data_line
|
||||
try
|
||||
let decoded = json_decode(buf)
|
||||
let buf = ''
|
||||
catch
|
||||
continue
|
||||
endtry
|
||||
call s:NvimHandleOutDecoded(a:job, decoded)
|
||||
endfor
|
||||
let s:nvim_job_map[a:job].out_buffer = buf
|
||||
endfunction
|
||||
|
||||
function! s:NvimHandleOutDecoded(job, decoded)
|
||||
if !has_key(s:nvim_job_map, a:job)
|
||||
return
|
||||
endif
|
||||
if type(a:decoded) != v:t_list || len(a:decoded) < 1 || (type(a:decoded[0]) != v:t_number)
|
||||
return
|
||||
endif
|
||||
let id = a:decoded[0]
|
||||
if len(a:decoded) >= 2
|
||||
let data = a:decoded[1]
|
||||
else
|
||||
let data = {}
|
||||
endif
|
||||
if (id > 0) && has_key(s:nvim_job_map[a:job].requests, id)
|
||||
let request = s:nvim_job_map[a:job].requests[id]
|
||||
if has_key(request, 'callback')
|
||||
call request.callback(a:job, data)
|
||||
endif
|
||||
unlet s:nvim_job_map[a:job].requests[id]
|
||||
else
|
||||
if has_key(s:nvim_job_map[a:job], 'out_cb')
|
||||
call s:nvim_job_map[a:job].out_cb(a:job, data)
|
||||
endif
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:NvimHandleStderr(job, data, event)
|
||||
if !has_key(s:nvim_job_map, a:job)
|
||||
return
|
||||
endif
|
||||
if has_key(s:nvim_job_map[a:job], 'err_cb')
|
||||
call s:nvim_job_map[a:job].err_cb(a:job, join(a:data, "\n"))
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:NvimHandleExit(job, status, event)
|
||||
if !has_key(s:nvim_job_map, a:job)
|
||||
return
|
||||
endif
|
||||
if has_key(s:nvim_job_map[a:job], 'exit_cb')
|
||||
call s:nvim_job_map[a:job].exit_cb(a:job, a:status)
|
||||
endif
|
||||
endfunction
|
||||
Loading…
Reference in New Issue