lifepillar/vim-mucomplete

Possible to detect if a completion-request has results other than using timer?

Closed this issue · 16 comments

Hi. Is it correct that mucomplete is using a timer to check if a completion-request has results to determine if it should invoke the next completion-method? Does vim really not have a native way to notify if a completion-request (like <c-n>) opened a pum-window with results in it? Thanks.

No, it does not use timers for that. In fact, MUcomplete works without +timers: it should be compatible with Vim as old as v7.2. As you have guessed, it performs a completion request and then checks whether the pop-up menu has opened.

You can check whether the pumwindow opens but how do you know if it never opens because there are no results so you know when to trigger the next completion-method?

Vim is single-threaded, so it's as simple as sending the completion characters to Vim (say, by calling feedkeys("\<c-x>\<c-n>")) then check if pumvisible() returns true.

Info

NVIM v0.8.3

Minimal vimrc

function! Complete()
  call feedkeys("\<c-x>\<c-n>", 'n')
  if pumvisible()
    echo 'pum'
  else
    echo '!pum'
  endif
endfunction

inoremap <tab> <cmd>call Complete()<cr>
set noshowmode shortmess+=c

Example

  1. Open nvim with a new empty buffer
  2. Type foo faa f* where * indicates the cursor-position
  3. Press <tab> to trigger Complete() based on the vimrc above

Expected

Pum-window should be opened with the two completion-suggestions foo and faa and the message pum should be printed to indicate that the pum-window is open.

Actual Results

The message !pum is printed and indicates that the pum-window is not visible although it is visible like can be seen in the following image.

pum

mg979 commented
:h feedkeys

feedkeys(string) is non-blocking, try feedkeys(string, "nx").

feedkeys("\<c-x>\<c-n>", 'nx') doesn't open the pum-window at all and moves the cursor one character to the left such that I end up with foo faa *f without a pum-window and the message !pum in the above example. Thank you for your time by the way. The only way I've gotten it to work is to run a timer to check sometime later if a pum-window is open (which is really not optimal).

mg979 commented

This works:

function! Complete()
  if pumvisible()
    echo 'pum'
  else
    echo '!pum'
  endif
endfunction

inoremap <buffer> <tab> <c-x><c-n><cmd>call Complete()<cr>
set noshowmode shortmess+=c

feedkeys(..., "x") can't work because it feeds keys in normal mode, not insert mode. feedkeys(...) can't work because it's non-blocking. So you can't use feedkeys here.

mg979 commented

Anyway if you tell what exactly you're trying to do it would help.

mg979 commented

If you simply want to check pumvisible after <tab>, you can do

function! CheckPum()
  if pumvisible()
    echo 'pum'
  else
    echo '!pum'
  endif
endfunction

inoremap <Plug>(CheckPum) <cmd>call CheckPum()<cr>

imap <tab> <plug>(MUcompleteFwd)<Plug>(CheckPum)

Thanks for chiming in! The example with feedkeys() was misleading, sorry. @mg979 's last posted example nicely illustrates how MUcomplete actually does it (except for some technicalities).

My goal is to create a minimal example where I can trigger a completion and check if it produced any results. If the completion has no results I want to automatically trigger a 2nd completion-method. Basically I am trying to understand how chain-completion works under the hood.

I came up with the following minimal example to check if pum is open after triggering a completion:

function! Complete()
  return "\<c-x>\<c-n>"
endfunction

function! CheckPum()
  if pumvisible()
    echo 'pumvisible'
  else
    echo '!pumvisible'
  endif
endfunction

inoremap <expr> <plug>(Complete) Complete()
imap <plug>(CompleteAndCheck) <plug>(Complete)<cmd>call CheckPum()<cr>
imap <tab> <plug>(CompleteAndCheck)

set noshowmode shortmess+=c

This works but I wonder if this really is idiomatic vim. Next I will try to somehow reuse <plug>(CompleteAndCheck) to write a function which triggeres a 2nd completion-method in case of !pumvisible.

Thank you for your help. The final solution works although it feels a little bit hacky because now I have to misuse mappings as functions because feedkeys() does not block.

I wouldn't call MUcomplete's approach idiomatic, but I don't think that yours is a misuse of mappings either.

MUcomplete was started to satisfy exactly your need: I wanted to complete with omnifunc and fallback to keyword completion. This is what I had in my vimrc before MUcomplete was developed.

How does mucomplete deal with the fact that v:lua.vim.lsp.omnifunc async is? The code above doesn't work in this case.

mg979 commented

It doesn't. But it's a problem of neovim implementation, more than this plugin's fault. In neovim you can implement a blocking lsp omnifunc, but the guy who wrote the default implementation probably thought that async is cooler. It's not the only example of this in neovim unfortunately.

Note that Neovim is not a drop-in replacement for Vim: at this point the two projects have diverged in significant ways. Neovim compatibility is not a goal of this project, nor has this project ever stated Neovim compatibility.

Regarding the async nature of some completion plugins: some people have seemingly succeeded in using MUcomplete with LanguageClient-neovim, and possibly other async plugins, but, afaik, only with +timers (and a couple of MUcomplete settings: see :help mucomplete-compatibility). Your mileage may vary.

I should also stress that the Readme clearly states the minimalistic nature of MUcomplete: that is, it's scope is limited to providing a more streamlined interface to Vim's built-in completion methods. It's also worth searching for “Does MUcomplete's implementation have any limitations?” in :help mucomplete.txt.