parent
f3aa91c715
commit
9c4b13174e
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
## Requirements
|
## 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+.
|
2. Node.js 16.0+.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
|
||||||
|
|
@ -73,17 +73,18 @@ function! tabby#Start()
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if !exists('*job_start') || !exists('*prop_type_add')
|
let check_job = tabby#job#Check()
|
||||||
let s:errmsg = 'Tabby requires Vim 9.0+ with +job and +textprop support.'
|
if !check_job.ok
|
||||||
|
let s:errmsg = check_job.message
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
hi def TabbyCompletion guifg=#808080 ctermfg=8
|
let check_inline_completion = tabby#inline_completion#Check()
|
||||||
let s:prop_type = 'TabbyCompletion'
|
if !check_inline_completion.ok
|
||||||
if prop_type_get(s:prop_type) != v:null
|
let s:errmsg = check_inline_completion.message
|
||||||
call prop_type_delete(s:prop_type)
|
return
|
||||||
endif
|
endif
|
||||||
call prop_type_add(s:prop_type, {'highlight': 'TabbyCompletion'})
|
call tabby#inline_completion#Init()
|
||||||
|
|
||||||
if !executable('node')
|
if !executable('node')
|
||||||
let s:errmsg = 'Tabby requires node to be installed.'
|
let s:errmsg = 'Tabby requires node to be installed.'
|
||||||
|
|
@ -100,7 +101,7 @@ function! tabby#Start()
|
||||||
let s:tabby_status = 'connecting'
|
let s:tabby_status = 'connecting'
|
||||||
|
|
||||||
let command = 'node ' . node_script
|
let command = 'node ' . node_script
|
||||||
let s:tabby = job_start(command, #{
|
let s:tabby = tabby#job#Start(command, #{
|
||||||
\ in_mode: 'json',
|
\ in_mode: 'json',
|
||||||
\ out_mode: 'json',
|
\ out_mode: 'json',
|
||||||
\ out_cb: function('s:HandleNotification'),
|
\ out_cb: function('s:HandleNotification'),
|
||||||
|
|
@ -114,7 +115,7 @@ endfunction
|
||||||
|
|
||||||
function! tabby#Stop()
|
function! tabby#Stop()
|
||||||
if tabby#Running()
|
if tabby#Running()
|
||||||
call job_stop(s:tabby)
|
call tabby#job#Stop(s:tabby)
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
|
@ -146,7 +147,7 @@ function! s:UpdateServerUrl()
|
||||||
if !tabby#Running()
|
if !tabby#Running()
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
call ch_sendexpr(s:tabby, #{
|
call tabby#job#Send(s:tabby, #{
|
||||||
\ func: 'setServerUrl',
|
\ func: 'setServerUrl',
|
||||||
\ args: [g:tabby_server_url],
|
\ args: [g:tabby_server_url],
|
||||||
\ })
|
\ })
|
||||||
|
|
@ -161,7 +162,7 @@ function! s:GetCompletion(id)
|
||||||
if l:language == 'unknown'
|
if l:language == 'unknown'
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
call ch_sendexpr(s:tabby, #{
|
call tabby#job#Send(s:tabby, #{
|
||||||
\ func: 'getCompletion',
|
\ func: 'getCompletion',
|
||||||
\ args: [#{
|
\ args: [#{
|
||||||
\ prompt: s:GetPrompt(),
|
\ prompt: s:GetPrompt(),
|
||||||
|
|
@ -179,7 +180,7 @@ function! s:PostEvent(event_type)
|
||||||
if !exists('s:completion') || !exists('s:completion_index')
|
if !exists('s:completion') || !exists('s:completion_index')
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
call ch_sendexpr(s:tabby, #{
|
call tabby#job#Send(s:tabby, #{
|
||||||
\ func: 'postEvent',
|
\ func: 'postEvent',
|
||||||
\ args: [#{
|
\ args: [#{
|
||||||
\ type: a:event_type,
|
\ type: a:event_type,
|
||||||
|
|
@ -190,7 +191,7 @@ function! s:PostEvent(event_type)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:HandleNotification(channel, data)
|
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
|
let s:tabby_status = a:data.status
|
||||||
endif
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
@ -199,10 +200,8 @@ function! s:HandleCompletion(id, channel, data)
|
||||||
if !exists('s:trigger_id') || (a:id != s:trigger_id)
|
if !exists('s:trigger_id') || (a:id != s:trigger_id)
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
if a:data == v:null
|
if (type(a:data) == v:t_dict) && has_key(a:data, 'choices') &&
|
||||||
return
|
\ (type(a:data.choices) == v:t_list) && (len(a:data.choices) > 0)
|
||||||
endif
|
|
||||||
if (type(a:data.choices) == v:t_list) && (len(a:data.choices) > 0)
|
|
||||||
let s:completion = a:data
|
let s:completion = a:data
|
||||||
let s:completion_index = 0
|
let s:completion_index = 0
|
||||||
call tabby#Show()
|
call tabby#Show()
|
||||||
|
|
@ -263,7 +262,7 @@ endfunction
|
||||||
" Completion control
|
" Completion control
|
||||||
|
|
||||||
function! tabby#Show()
|
function! tabby#Show()
|
||||||
call s:RemoveProp()
|
call s:RemoveCompletion()
|
||||||
if !s:CompletionAvailable()
|
if !s:CompletionAvailable()
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
@ -276,22 +275,12 @@ function! tabby#Show()
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
let lines = split(choice.text, "\n")
|
let lines = split(choice.text, "\n")
|
||||||
call prop_add(line('.'), col('.'), #{
|
call tabby#inline_completion#Show(lines)
|
||||||
\ 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
|
|
||||||
let s:prop_shown_lines = lines
|
let s:prop_shown_lines = lines
|
||||||
call s:PostEvent('view')
|
call s:PostEvent('view')
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! tabby#ComsumeInsertion()
|
function! tabby#ConsumeInsertion()
|
||||||
if !exists('s:text_to_insert')
|
if !exists('s:text_to_insert')
|
||||||
return ''
|
return ''
|
||||||
else
|
else
|
||||||
|
|
@ -308,14 +297,14 @@ function! tabby#Accept(fallback)
|
||||||
let lines = s:prop_shown_lines
|
let lines = s:prop_shown_lines
|
||||||
if len(lines) == 1
|
if len(lines) == 1
|
||||||
let s:text_to_insert = lines[0]
|
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
|
else
|
||||||
let current_line = getbufline('%', line('.'), line('.'))[0]
|
let current_line = getbufline('%', line('.'), line('.'))[0]
|
||||||
let suffix_chars_to_replace = len(current_line) - col('.') + 1
|
let suffix_chars_to_replace = len(current_line) - col('.') + 1
|
||||||
let s:text_to_insert = join(lines, "\n")
|
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
|
endif
|
||||||
call s:RemoveProp()
|
call s:RemoveCompletion()
|
||||||
call s:PostEvent('select')
|
call s:PostEvent('select')
|
||||||
return insertion
|
return insertion
|
||||||
endfunction
|
endfunction
|
||||||
|
|
@ -324,7 +313,7 @@ function! tabby#Dismiss(fallback)
|
||||||
if !exists('s:prop_shown_lines')
|
if !exists('s:prop_shown_lines')
|
||||||
return a:fallback
|
return a:fallback
|
||||||
endif
|
endif
|
||||||
call s:RemoveProp()
|
call s:RemoveCompletion()
|
||||||
return ''
|
return ''
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
|
|
@ -363,7 +352,7 @@ function! tabby#Prev()
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! tabby#Clear()
|
function! tabby#Clear()
|
||||||
call s:RemoveProp()
|
call s:RemoveCompletion()
|
||||||
if exists('s:scheduled')
|
if exists('s:scheduled')
|
||||||
call timer_stop(s:scheduled)
|
call timer_stop(s:scheduled)
|
||||||
unlet s:scheduled
|
unlet s:scheduled
|
||||||
|
|
@ -389,11 +378,8 @@ function! s:CompletionAvailable()
|
||||||
return v:true
|
return v:true
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! s:RemoveProp()
|
function! s:RemoveCompletion()
|
||||||
call prop_remove(#{
|
call tabby#inline_completion#Clear()
|
||||||
\ type: s:prop_type,
|
|
||||||
\ all: v:true,
|
|
||||||
\ })
|
|
||||||
if exists('s:prop_shown_lines')
|
if exists('s:prop_shown_lines')
|
||||||
unlet s:prop_shown_lines
|
unlet s:prop_shown_lines
|
||||||
endif
|
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