Shougo / neosnippet.vim

neo-snippet plugin
Other
1.12k stars 108 forks source link

neosnippet#expandable_or_jumpable() returns true after any ncm2 completion #455

Closed anelson closed 5 years ago

anelson commented 5 years ago

Hello: I am not sure if this is a bug in neosnippet or in the ncm2-neosnippet integration plugin. I've opened this issue here to start with based on my best guess of where the problem lies.

I'm using neosnippet with ncm2 for completion and ncm2-neosnippet to integrate the two. All plugins are the latest version as of about an hour ago. I'm running this on nvim 0.3.3 on Arch Linux.

In my .vimrc I've configured a key binding which I intend to make C-k work consistently regardless of the current state of autocomplete. So if there's an expandable snippet selected in ncm2, then ncm2 is called to expand that snippet. If instead there is a neosnippet snippet trigger, or the cursor is in an expanded snippet and can be moved to the next placeholder, C-k also works.

To do that I've written this function and bindings (note the echom diagnostic code I added; the output from that will be shown shortly):

func! ExpandOrJumpSnippet()
  " Expand a snippet if at all possible.
  "
  " There are a few possibilities:
  " * ncm2 autocomplete is being used and a snippet item is currently
  " selected.  In that case expand that snippet.  Note that ncm2 exposes some
  " snippets beyond those neosnippet knows about, even though it uses
  " neosnippet to expand them.  These are "anonymous" snippets like the ones
  " it gets from an LSP.  Thus if ncm2 has been used to pick a snippet it's
  " important to use ncm2's neosnippet API to do the completion.  IF it is a
  " built-in neosnippet snippet then it'll call down into neosnippet anyway.
  "
  " * ncm2 is not active, but a neocomplete snippet trigger has been typed.
  " In that case expand the snippet.
  "
  " * a neosnippet snippet has been expanded, and there is another placeholder
  " still to navigate to.  In that case, jump to the next placeholder.
  if ncm2_neosnippet#completed_is_snippet()
    echom "ncm2_neosnippet expanding"
    return "\<Plug>(ncm2_neosnippet_expand_completed)"
  elseif neosnippet#expandable_or_jumpable()
    echom "neosnippet expanding"
    if neosnippet#mappings#expandable()
      echom "snippet expandable"
    endif

    if neosnippet#mappings#jumpable()
      echom "snippet jumpable"
    endif

    echom "completed_item: " . json_encode(v:completed_item)

    return "\<Plug>(neosnippet_expand_or_jump)"
  elseif pumvisible()
    " No kind of snippet but the autocomplete is visible.  Probably I
    " accidentally or out of force of habit hit C-k on an autocomplete item
    " that wasn't actually a snippet.  If that happens just dismiss the
    " autocomplete but stay in insert mode so it doesn't disrupt my workflow
    echom "dismissing popup"
    return "\<C-y>"
  else
    echom "nothing to do; passing through to underlying C-k binding"
    return "\<C-k>"
  endif
endfunction

" When pressing <C-k> in insert or select mode, be smart about snippet
" expansion/jumping no matter the state
imap <expr><C-k> ExpandOrJumpSnippet()
smap <expr><C-k> ExpandOrJumpSnippet()
xmap <C-k>     <Plug>(neosnippet_expand_target)

This works great, except for one serious problem: If I pick any completion item from ncm2, and then press C-k, the call to neosnippet#expandable_or_jumpable() returns 1, even if that particular item is not a snippet at all. That's not ideal because if <Plug>(neosnippet_expand_or_jump) is invoked when there isn't actually a snippet trigger at the cursor, it exits insert mode and returns to normal mode which really breaks my flow.

I think I know why this is happening. Here's the debug output from my script when I select a non-snippet autocomplete item in ncm2 and press C-k:

neosnippet expanding
snippet expandable
completed_item: {"word": "sync_get_text_as_bytes", "menu": "[b] ", "user_data": "{\"word\": \"sync_get_text_as_bytes\", \"ncm2\": 1, \"source\": \"bufwo
rd\", \"match_highlight\": [[0, 1], [5, 6], [9, 10], [14, 15], [17, 18]], \"match_key\": \"abbr\", \"snippet\": \"\", \"is_snippet\": 0}", "info": "", "
kind": "", "abbr": "𝕤ync_𝕘et_𝕥ext_𝕒s_𝕓ytes"}

Note that I did use ncm2 to complete an item, so completed_item is populated with the item I completed. In this case, it was just another word that appears in the buffer. Also note that user_data includes a snippet key, set to an empty string, and is_snippet is set to 0. But this code in neosnippet is being used to determine if the just-completed item is a snippet:

(The offending line is here)

function! s:get_completed_snippets(cur_text, col) abort
  ...
  if get(v:completed_item, 'user_data', '') !=# ''
    let user_data = json_decode(v:completed_item.user_data)
    if type(user_data) == v:t_dict && has_key(user_data, 'snippet') " <-- HERE!
      let snippet = user_data.snippet
      if has_key(user_data, 'snippet_trigger')
        let cur_text = cur_text[: -1-len(user_data.snippet_trigger)]
      endif
      return [cur_text, snippet]
    endif
  endif
  ...
endfunction

This code looks for the presence of the snippet key in user_data and if found it assumes that the completed item was a snippet. But that doesn't work, at least not with ncm2's implementation, which indicates snippet-ness with the is_snippet key.

I don't know if the semantics of user_data in v:completed_item in Vim is specified somewhere, and if so whether snippet is meant to be used to determine if an item is a snippet or not. If you think this is correct behavior and the problem lies in the ncm2 integration plugin then I'll take this issue over there.

Thanks for any help.