Vim plugin readability refactor. (#132)

add-tracing
Zhiming Ma 2023-05-16 00:42:16 +08:00 committed by GitHub
parent 666d37ff52
commit 2b34d177ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 231 additions and 155 deletions

View File

@ -3,7 +3,21 @@ if exists('g:autoloaded_tabby')
endif endif
let g:autoloaded_tabby = 1 let g:autoloaded_tabby = 1
" Commands " Table of Contents
" 1. Commands: Implement *:Tabby* commands
" 2. Settings: Handle global *Tabby-options*
" 3. Node Job: Manage node process, implement IO callbacks
" 4. Scheduler: Schedule completion requests
" 5. Completion UI: Show up completion, handle hotkeys
" 6. Utils: Utility functions
" 1. Commands
" See *:Tabby* in help document for more details.
"
" Notable script-local variables:
" - s:commmands
" A dictionary contains all commands. Use name as key and function as value.
"
let s:commands = {} let s:commands = {}
@ -83,7 +97,15 @@ function! tabby#Command(args)
endif endif
endfunction endfunction
" Settings " 2. Settings
" See *Tabby-options* in help document for more details.
"
" Available global options:
" - g:tabby_enabled
" - g:tabby_suggestion_delay
" - g:tabby_filetype_to_languages
" - g:tabby_server_url
"
if !exists('g:tabby_enabled') if !exists('g:tabby_enabled')
let g:tabby_enabled = v:true let g:tabby_enabled = v:true
@ -110,18 +132,29 @@ function! tabby#SetServerUrl(url)
call s:UpdateServerUrl() call s:UpdateServerUrl()
endfunction endfunction
" Node job control " 3. Node Job
"
" Notable script-local variables:
" - s:tabby
" Stores the job id of current node process
"
" - s:tabby_status
" Syncs with status of node agent, updated by notification from agent
"
" - s:errmsg
" Stores error message if self check failed before starting node process
"
function! tabby#Enable() function! tabby#Enable()
let g:tabby_enabled = v:true let g:tabby_enabled = v:true
if !tabby#Running() if !tabby#IsRunning()
call tabby#Start() call tabby#Start()
endif endif
endfunction endfunction
function! tabby#Disable() function! tabby#Disable()
let g:tabby_enabled = v:false let g:tabby_enabled = v:false
if tabby#Running() if tabby#IsRunning()
call tabby#Stop() call tabby#Stop()
endif endif
endfunction endfunction
@ -135,7 +168,7 @@ function! tabby#Toggle()
endfunction endfunction
function! tabby#Start() function! tabby#Start()
if !g:tabby_enabled || tabby#Running() if !g:tabby_enabled || tabby#IsRunning()
return return
endif endif
@ -145,12 +178,12 @@ function! tabby#Start()
return return
endif endif
let check_inline_completion = tabby#inline_completion#Check() let check_virtual_text = tabby#virtual_text#Check()
if !check_inline_completion.ok if !check_virtual_text.ok
let s:errmsg = check_inline_completion.message let s:errmsg = check_virtual_text.message
return return
endif endif
call tabby#inline_completion#Init() call tabby#virtual_text#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.'
@ -180,12 +213,12 @@ function! tabby#Start()
endfunction endfunction
function! tabby#Stop() function! tabby#Stop()
if tabby#Running() if tabby#IsRunning()
call tabby#job#Stop(s:tabby) call tabby#job#Stop(s:tabby)
endif endif
endfunction endfunction
function! tabby#Running() function! tabby#IsRunning()
return exists('s:tabby') return exists('s:tabby')
endfunction endfunction
@ -194,7 +227,7 @@ function! tabby#Status()
echo 'Tabby is disabled' echo 'Tabby is disabled'
return return
endif endif
if tabby#Running() if tabby#IsRunning()
if s:tabby_status == 'ready' if s:tabby_status == 'ready'
echo 'Tabby is online' echo 'Tabby is online'
elseif s:tabby_status == 'connecting' elseif s:tabby_status == 'connecting'
@ -210,7 +243,7 @@ function! tabby#Status()
endfunction endfunction
function! s:UpdateServerUrl() function! s:UpdateServerUrl()
if !tabby#Running() if !tabby#IsRunning()
return return
endif endif
call tabby#job#Send(s:tabby, #{ call tabby#job#Send(s:tabby, #{
@ -220,7 +253,7 @@ function! s:UpdateServerUrl()
endfunction endfunction
function! s:GetCompletion(id) function! s:GetCompletion(id)
if !tabby#Running() if !tabby#IsRunning()
return return
endif endif
@ -237,10 +270,10 @@ function! s:GetCompletion(id)
endfunction endfunction
function! s:PostEvent(event_type) function! s:PostEvent(event_type)
if !tabby#Running() if !tabby#IsRunning()
return return
endif endif
if !exists('s:completion') || !exists('s:completion_index') if !exists('s:completion') || !exists('s:choice_index')
return return
endif endif
call tabby#job#Send(s:tabby, #{ call tabby#job#Send(s:tabby, #{
@ -248,7 +281,7 @@ function! s:PostEvent(event_type)
\ args: [#{ \ args: [#{
\ type: a:event_type, \ type: a:event_type,
\ completion_id: s:completion.id, \ completion_id: s:completion.id,
\ choice_index: s:completion.choices[s:completion_index].index, \ choice_index: s:completion.choices[s:choice_index].index,
\ }], \ }],
\ }) \ })
endfunction endfunction
@ -266,7 +299,7 @@ function! s:HandleCompletion(id, channel, data)
if (type(a:data) == v:t_dict) && has_key(a:data, 'choices') && if (type(a:data) == v:t_dict) && has_key(a:data, 'choices') &&
\ (type(a:data.choices) == v:t_list) && (len(a:data.choices) > 0) \ (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:choice_index = 0
call tabby#Show() call tabby#Show()
endif endif
endfunction endfunction
@ -280,10 +313,19 @@ function! s:HandleExit(channel, data)
endif endif
endfunction endfunction
" Completion trigger " 4. Scheduler
"
" Notable script-local variables:
" - s:scheduled:
" Stores the timer id of current scheduled next trigger.
"
" - s:trigger_id:
" Use a timestamp to identify current triggered completion request. This is
" used to filter out outdated completion results.
"
function! tabby#Schedule() function! tabby#Schedule()
if !tabby#Running() if !tabby#IsRunning()
return return
endif endif
call tabby#Clear() call tabby#Clear()
@ -291,7 +333,7 @@ function! tabby#Schedule()
endfunction endfunction
function! tabby#Trigger(timer) function! tabby#Trigger(timer)
if !tabby#Running() if !tabby#IsRunning()
return return
endif endif
call tabby#Clear() call tabby#Clear()
@ -300,6 +342,165 @@ function! tabby#Trigger(timer)
call s:GetCompletion(id) call s:GetCompletion(id)
endfunction endfunction
" 5. Completion UI
"
" Notable script-local variables:
" - s:completion:
" Stores current completion data, a dictionary that has same struct as server
" returned completion response.
"
" - s:choice_index:
" Stores index of current choice to display. A 0-based index of choice item
" of `s:completion.choices` array, may not equals to the value of `index`
" field `s:completion.choices[s:choice_index].index`.
" A exception is that when `s:choice_index` is equal to the length of
" `s:completion.choices`. In this state, the completion UI should show nothing
" to notice users that they are cycling from last choice forward to first
" choice, or from first choice back to last choice.
" This variable does not change when user dismisses the completion UI.
"
" - s:shown_lines:
" Stores the text that are shown in completion UI. `s:shown_lines` exists or
" not means whether the completion UI is shown or not.
"
" - s:text_to_insert:
" Used as a buffer to store the text that should be inserted when user accepts
" the completion. We hide completion UI first and clear `s:shown_lines` at
" same time, then insert the text, so that we need to store the text in a
" buffer until text is inserted.
function! tabby#Show()
call s:HideCompletion()
if !s:IsCompletionAvailable()
return
endif
if s:choice_index == len(s:completion.choices)
" Show empty to indicate that user is cycling back to first choice.
return
endif
let choice = s:completion.choices[s:choice_index]
if (type(choice.text) != v:t_string) || (len(choice.text) == 0)
return
endif
let lines = split(choice.text, "\n")
call tabby#virtual_text#Show(lines)
let s:shown_lines = lines
call s:PostEvent('view')
endfunction
function! tabby#ConsumeInsertion()
if !exists('s:text_to_insert')
return ''
else
let text = s:text_to_insert
unlet s:text_to_insert
return text
endif
endfunction
" This function is designed to replace <Tab> key input, so we need a fallback
" when completion UI is not shown.
function! tabby#Accept(fallback)
if !exists('s:shown_lines')
return a:fallback
endif
let lines = s:shown_lines
if len(lines) == 1
let s:text_to_insert = lines[0]
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#ConsumeInsertion()\<CR>"
endif
call s:HideCompletion()
call s:PostEvent('select')
return insertion
endfunction
" This function is designed to replace <C-]> key input, so we need a fallback
" when completion UI is not shown.
function! tabby#Dismiss(fallback)
if !exists('s:shown_lines')
return a:fallback
endif
call s:HideCompletion()
return ''
endfunction
function! tabby#Next()
if !s:IsCompletionAvailable()
return
endif
if !exists('s:shown_lines')
if s:choice_index == len(s:completion.choices)
let s:choice_index = 0
endif
else
let s:choice_index += 1
if s:choice_index > len(s:completion.choices)
let s:choice_index = 0
endif
endif
call tabby#Show()
endfunction
function! tabby#Prev()
if !s:IsCompletionAvailable()
return
endif
if !exists('s:shown_lines')
if s:choice_index == len(s:completion.choices)
let s:choice_index = len(s:completion.choices) - 1
endif
else
let s:choice_index -= 1
if s:choice_index < 0
let s:choice_index = len(s:completion.choices)
endif
endif
call tabby#Show()
endfunction
function! tabby#Clear()
call s:HideCompletion()
if exists('s:scheduled')
call timer_stop(s:scheduled)
unlet s:scheduled
endif
if exists('s:trigger_id')
unlet s:trigger_id
endif
if exists('s:completion')
unlet s:completion
endif
if exists('s:choice_index')
unlet s:choice_index
endif
endfunction
function! s:IsCompletionAvailable()
if !exists('s:completion') || !exists('s:choice_index')
return v:false
endif
if (type(s:completion.choices) != v:t_list) || (len(s:completion.choices) == 0)
return v:false
endif
return v:true
endfunction
function! s:HideCompletion()
call tabby#virtual_text#Clear()
if exists('s:shown_lines')
unlet s:shown_lines
endif
endfunction
" 6. Utils
function! s:GetPrompt() function! s:GetPrompt()
let max_lines = 20 let max_lines = 20
let first_line = max([1, line('.') - max_lines]) let first_line = max([1, line('.') - max_lines])
@ -316,129 +517,3 @@ function! s:GetLanguage()
return filetype return filetype
endif endif
endfunction endfunction
" Completion control
function! tabby#Show()
call s:RemoveCompletion()
if !s:CompletionAvailable()
return
endif
if s:completion_index == len(s:completion.choices)
" An empty choice after last and before first
return
endif
let choice = s:completion.choices[s:completion_index]
if (type(choice.text) != v:t_string) || (len(choice.text) == 0)
return
endif
let lines = split(choice.text, "\n")
call tabby#inline_completion#Show(lines)
let s:prop_shown_lines = lines
call s:PostEvent('view')
endfunction
function! tabby#ConsumeInsertion()
if !exists('s:text_to_insert')
return ''
else
let text = s:text_to_insert
unlet s:text_to_insert
return text
endif
endfunction
function! tabby#Accept(fallback)
if !exists('s:prop_shown_lines')
return a:fallback
endif
let lines = s:prop_shown_lines
if len(lines) == 1
let s:text_to_insert = lines[0]
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#ConsumeInsertion()\<CR>"
endif
call s:RemoveCompletion()
call s:PostEvent('select')
return insertion
endfunction
function! tabby#Dismiss(fallback)
if !exists('s:prop_shown_lines')
return a:fallback
endif
call s:RemoveCompletion()
return ''
endfunction
function! tabby#Next()
if !s:CompletionAvailable()
return
endif
if !exists('s:prop_shown_lines')
if s:completion_index == len(s:completion.choices)
let s:completion_index = 0
endif
else
let s:completion_index += 1
if s:completion_index > len(s:completion.choices)
let s:completion_index = 0
endif
endif
call tabby#Show()
endfunction
function! tabby#Prev()
if !s:CompletionAvailable()
return
endif
if !exists('s:prop_shown_lines')
if s:completion_index == len(s:completion.choices)
let s:completion_index = len(s:completion.choices) - 1
endif
else
let s:completion_index -= 1
if s:completion_index < 0
let s:completion_index = len(s:completion.choices)
endif
endif
call tabby#Show()
endfunction
function! tabby#Clear()
call s:RemoveCompletion()
if exists('s:scheduled')
call timer_stop(s:scheduled)
unlet s:scheduled
endif
if exists('s:trigger_id')
unlet s:trigger_id
endif
if exists('s:completion')
unlet s:completion
endif
if exists('s:completion_index')
unlet s:completion_index
endif
endfunction
function! s:CompletionAvailable()
if !exists('s:completion') || !exists('s:completion_index')
return v:false
endif
if (type(s:completion.choices) != v:t_list) || (len(s:completion.choices) == 0)
return v:false
endif
return v:true
endfunction
function! s:RemoveCompletion()
call tabby#inline_completion#Clear()
if exists('s:prop_shown_lines')
unlet s:prop_shown_lines
endif
endfunction

View File

@ -1,19 +1,19 @@
if exists('g:autoloaded_tabby_inline_completion') if exists('g:autoloaded_tabby_virtual_text')
finish finish
endif endif
let g:autoloaded_tabby_inline_completion = 1 let g:autoloaded_tabby_virtual_text = 1
let s:vim = exists('*prop_type_add') let s:vim = exists('*prop_type_add')
let s:nvim = !s:vim && has('nvim') && exists('*nvim_buf_set_extmark') let s:nvim = !s:vim && has('nvim') && exists('*nvim_buf_set_extmark')
function! tabby#inline_completion#Check() function! tabby#virtual_text#Check()
return #{ return #{
\ ok: s:vim || s:nvim, \ ok: s:vim || s:nvim,
\ message: 'Tabby requires Vim 9.0+ with +textprop feature support, or NeoVim 0.6.0+.', \ message: 'Tabby requires Vim 9.0.0534+ with +textprop feature support, or NeoVim 0.6.0+.',
\} \}
endfunction endfunction
function! tabby#inline_completion#Init() function! tabby#virtual_text#Init()
hi def TabbyCompletion guifg=#808080 ctermfg=8 hi def TabbyCompletion guifg=#808080 ctermfg=8
if s:vim if s:vim
let s:prop_type = 'TabbyCompletion' let s:prop_type = 'TabbyCompletion'
@ -29,11 +29,12 @@ function! tabby#inline_completion#Init()
endif endif
endfunction endfunction
function! tabby#inline_completion#Show(lines) function! tabby#virtual_text#Show(lines)
if len(a:lines) == 0 if len(a:lines) == 0
return return
endif endif
if s:vim if s:vim
" virtual text requires 9.0.0534
call prop_add(line('.'), col('.'), #{ call prop_add(line('.'), col('.'), #{
\ type: s:prop_type, \ type: s:prop_type,
\ text: a:lines[0], \ text: a:lines[0],
@ -59,7 +60,7 @@ function! tabby#inline_completion#Show(lines)
endif endif
endfunction endfunction
function! tabby#inline_completion#Clear() function! tabby#virtual_text#Clear()
if s:vim if s:vim
call prop_remove(#{ call prop_remove(#{
\ type: s:prop_type, \ type: s:prop_type,