Después de más investigaciones (y algo de ingeniería inversa).
No puedo explicar por qué la finalización no sigue estrictamente la documentación. Tendré que preguntar en vim_dev, supongo.
De todos modos, parece que la forma de hacerlo consiste en registrar un oyente CursorMovedI
que activará la finalización nuevamente cada vez que se inserte un personaje.
La dificultad es saber cuándo parar.
CompletionDone
no sirve de nada, ya que se activará después de cada pulsación de tecla.
InsertLeave
es un buen comienzo, pero no cubre todos los casos, es decir
- Cuando se escribe un personaje para el que no hay más coincidencias, tenemos que detener
- Cuando el usuario final selecciona un elemento de menú, también tenemos que parar.
No he encontrado otra manera que anular <cr>
, y <c-y>
.
Otras dificultades consisten en detectar cuándo nada ha cambiado para evitar una especie de bucles infinitos, etc.
De cualquier manera, aquí está mi código actual (que se usará en otros complementos). La última versión se mantendrá aquí . Es bastante largo, pero aquí está:
" ## Smart completion {{{2
" Function: lh#icomplete#new(startcol, matches, hook) {{{3
function! lh#icomplete#new(startcol, matches, hook) abort
silent! unlet b:complete_data
let augroup = 'IComplete'.bufnr('%').'Done'
let b:complete_data = lh#on#exit()
\.restore('&completefunc')
\.restore('&complete')
\.restore('&omnifunc')
\.register('au! '.augroup)
\.register('call self.logger.log("finalized! (".getline(".").")")')
set complete=
let b:complete_data.startcol = a:startcol
let b:complete_data.all_matches = map(copy(a:matches), 'type(v:val)==type({}) ? v:val : {"word": v:val}')
let b:complete_data.matches = {'words': [], 'refresh': 'always'}
let b:complete_data.hook = a:hook
let b:complete_data.cursor_pos = []
let b:complete_data.last_content = [line('.'), getline('.')]
let b:complete_data.no_more_matches = 0
let b:complete_data.logger = s:logger.reset()
" keybindings {{{4
call b:complete_data
\.restore_buffer_mapping('<cr>', 'i')
\.restore_buffer_mapping('<c-y>', 'i')
\.restore_buffer_mapping('<esc>', 'i')
\.restore_buffer_mapping('<tab>', 'i')
inoremap <buffer> <silent> <cr> <c-y><c-\><c-n>:call b:complete_data.conclude()<cr>
inoremap <buffer> <silent> <c-y> <c-y><c-\><c-n>:call b:complete_data.conclude()<cr>
" Unlike usual <tab> behaviour, this time, <tab> inserts the next match
inoremap <buffer> <silent> <tab> <down><c-y><c-\><c-n>:call b:complete_data.conclude()<cr>
" <c-o><Nop> doesn't work as expected...
" To stay in INSERT-mode:
" inoremap <silent> <esc> <c-e><c-o>:<cr>
" To return into NORMAL-mode:
inoremap <buffer> <silent> <esc> <c-e><esc>
" TODO: see to have <Left>, <Right>, <Home>, <End> abort
" Group {{{4
exe 'augroup '.augroup
au!
" Emulate InsertCharPost
" au CompleteDone <buffer> call b:complete_data.logger.log("Completion done")
au InsertLeave <buffer> call b:complete_data.finalize()
au CursorMovedI <buffer> call b:complete_data.cursor_moved()
augroup END
function! s:cursor_moved() abort dict "{{{4
if self.no_more_matches
call self.finalize()
return
endif
if !self.has_text_changed_since_last_move()
call s:logger.log(lh#fmt#printf("cursor %1 just moved (text hasn't changed)", string(getpos('.'))))
return
endif
call s:logger.log(lh#fmt#printf('cursor moved %1 and text has changed -> relaunch completion', string(getpos('.'))))
call feedkeys( "\<C-X>\<C-O>\<C-P>\<Down>", 'n' )
endfunction
let b:complete_data.cursor_moved = function('s:cursor_moved')
function! s:has_text_changed_since_last_move() abort dict "{{{4
let l = line('.')
let line = getline('.')
try
if l != self.last_content[0] " moved vertically
let self.no_more_matches = 1
call s:logger.log("Vertical move => stop")
return 0
" We shall leave complete mode now!
endif
call s:logger.log(lh#fmt#printf("line was: %1, and becomes: %2; has_changed?%3", self.last_content[1], line, line != self.last_content[1]))
return line != self.last_content[1] " text changed
finally
let self.last_content = [l, line]
endtry
endfunction
let b:complete_data.has_text_changed_since_last_move = function('s:has_text_changed_since_last_move')
function! s:complete(findstart, base) abort dict "{{{4
call s:logger.log(lh#fmt#printf('findstart?%1 -> %2', a:findstart, a:base))
if a:findstart
if self.no_more_matches
call s:logger.log("no more matches -> -3")
return -3
call self.finalize()
endif
if self.cursor_pos == getcurpos()
call s:logger.log("cursor hasn't moved -> -2")
return -2
endif
let self.cursor_pos = getcurpos()
return self.startcol
else
return self.get_completions(a:base)
endif
endfunction
let b:complete_data.complete = function('s:complete')
function! s:get_completions(base) abort dict "{{{4
let matching = filter(copy(self.all_matches), 'v:val.word =~ join(split(a:base, ".\\zs"), ".*")')
let self.matches.words = matching
call s:logger.log(lh#fmt#printf("'%1' matches: %2", a:base, string(self.matches)))
if empty(self.matches.words)
call s:logger.log("No more matches...")
let self.no_more_matches = 1
endif
return self.matches
endfunction
let b:complete_data.get_completions = function('s:get_completions')
function! s:conclude() abort dict " {{{4
let selection = getline('.')[self.startcol : col('.')-1]
call s:logger.log("Successful selection of <".selection.">")
if !empty(self.hook)
call lh#function#execute(self.hook, selection)
endif
" call self.hook()
call self.finalize()
endfunction
let b:complete_data.conclude = function('s:conclude')
" Register {{{4
" call b:complete_data
" \.restore('b:complete_data')
" set completefunc=lh#icomplete#func
set omnifunc=lh#icomplete#func
endfunction
" Function: lh#icomplete#new_on(pattern, matches, hook) {{{3
function! lh#icomplete#new_on(pattern, matches, hook) abort
let l = getline('.')
let startcol = match(l[0:col('.')-1], '\v'.a:pattern.'+$')
if startcol == -1
let startcol = col('.')-1
endif
call lh#icomplete#new(startcol, a:matches, a:hook)
endfunction
" Function: lh#icomplete#func(startcol, base) {{{3
function! lh#icomplete#func(findstart, base) abort
return b:complete_data.complete(a:findstart, a:base)
endfunction
Que se puede usar con:
let entries = [
\ {'word': 'un', 'menu': 1},
\ {'word': 'deux', 'menu': 2},
\ {'word': 'trois', 'menu': 3},
\ {'word': 'trentre-deux', 'menu': 32},
\ 'unité'
\ ]
inoremap <silent> <buffer> µ <c-o>:call lh#icomplete#new_on('\w', entries, 'lh#common#warning_msg("nominal: ".v:val)')<cr><c-x><c-O><c-p><down>
Debería poder observar (indirectamente) el resultado aplicado a la selección de fragmentos de C ++ para mi complemento expansor de plantilla en este screencast .