Finalización dinámica

12

Estoy tratando de mejorar una función de finalización antigua. Me gustaría actualizar las opciones que se muestran en el menú emergente a medida que escribo nuevos caracteres

Mi función de finalización es

function! lh#icomplete#ecm(findstart, base) abort
  if a:findstart
    let l = getline('.')
    let startcol = match(l[0:col('.')-1], '\v\S+$')
    if startcol == -1
      let startcol = col('.')-1
    endif
    " let g:debug+= ["findstart(".a:base.") -> ".(startcol)]
    return startcol
  else
    " let g:debug += ["matching(".a:base.")"]
    let words = ['un', 'deux', 'trois', 'trente-deux', 'unité']
    call filter(words, 'v:val =~ a:base')
    " return { 'words' : words}
    return { 'words' : words, 'refresh' : 'always'}
  endif
endfunction

Que uso con

:set completefunc=lh#icomplete#ecm
:inoremap µ <c-x><c-u><c-p>

Desde mi comprensión de la documentación, el hecho que uso <c-p>, entro en el tercer estado (de acuerdo con | ins-complete-menu |), y cuando escribo "cualquier carácter no blanco imprimible" debería poder "Agregar este personaje y reducir el número de partidos ".

Cuando escribo en modo de inserción , aparece el menú de finalización como se esperaba. Por desgracia, cuando escribo x(justo después de µ), estoy fuera del modo de finalización y uxes lo que obtengo en mi búfer.

¿Qué hice mal o me perdí en la documentación?

NB: He visto que sin refresh=always, los resultados se filtran, excepto que me gustaría volver a llamar a la función para aplicar un filtro personalizado.

(Por si acaso, estoy usando gvim 7.4-908)

Luc Hermitte
fuente
Esto parece un error. Tampoco funciona para mí (estoy en Vim 7.4.944).
Karl Yngve Lervåg
¿Debería funcionar? Eso es lo que no sé.
Luc Hermitte
La documentación parece implicar que debería funcionar.
Karl Yngve Lervåg

Respuestas:

3

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 CursorMovedIque 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 .

Luc Hermitte
fuente