Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a possibility to determine whether an entry in the popupmenu is selected or not? #2004

Closed
kiryph opened this issue Aug 22, 2017 · 9 comments

Comments

@kiryph
Copy link

kiryph commented Aug 22, 2017

I have following mapping in my vimrc:

" inoremap <CR> --- expand snippet if possible, close pum+insert newline or insert newline {{{2
function! s:ExpandSnippetOrReturnEmptyString()
    if pumvisible()
        echom "pumvisible"
        let snippet = UltiSnips#ExpandSnippetOrJump()
        if g:ulti_expand_or_jump_res > 0
          " TODO expand only if selected
            return snippet
        else
            return "\<C-y>\<CR>"
        endif
    else
        return "\<CR>"
    endif
endfunction
inoremap <silent> <CR> <C-r>=<SID>ExpandSnippetOrReturnEmptyString()<CR>

I would like to replace pumvisible() with something like pumselected(). By default no entry is selected in the popup menu. As long as the user does not select one, pressing <CR> should insert a newline. However, this behavior should change if the user selects an entry. In this case ultisnips should try to expand if possible.

@nuko8
Copy link

nuko8 commented Aug 23, 2017

Technically, that could be possible, but...

As long as the user does not select one, pressing should insert a newline. However, this behavior should change if the user selects an entry. In this case ultisnips should try to expand if possible.

Basically, when a pop-up menu returns, it should convey only the decision the user made with the menu, i.e., whether an item or items were selected or whether the selection itself was canceled, to the upper part of the software hierarchy, not anything more than that. Pop-up menus should not dictate what the upper part has to do.

Otherwise, the responsibility and independency of each module would get made light, ending up a codebase hard to maintain or extend in the long run. For instance, how can pop-up menus know about the notion of newline a priori? That's surely a thing they needn't know about.

So, IMHO, the introduction of "pumselected()" would depend partly on whether or not it is worth doing at the cost of such design principle.

@kiryph
Copy link
Author

kiryph commented Aug 23, 2017

Thanks for your thoughts on this.

I can see the point of your first paragraph. But I do not necessarily agree that a popupmenu shouldn't do more than what you say. Furthermore, I am not sure about your anticipated problems you described in the second paragraph. Can you explain it more explicitly? What do you mean with module? Which codebase is hard to maintain (vim or plugin)? Why does a popupmenu must know something about the notion of newline?

I want to expand my motivation a little bit more by the means of an example: I have defined for LaTeX a snippet named item to insert an itemize environment:

snippet item "Itemize" b
\begin{itemize}
	\item $0
\end{itemize}
endsnippet

However, the word item can occur also in a regular text paragraph where I possibly just want to
complete it to item. So the popupmenu can have two entries for it: 1. the ultisnips trigger word item and 2. the word item if it is already in the current buffer.

My point here is that a popupmenu could provide more advanced functionality by attaching to a word an optional behaviour which is only available if the popupmenu is active. A popupmenu provides instantaneous explanation about this optional behaviour and therefore should be predictable by the user.

Also for clarification I had auto-completion plugins such as neocomplete/deoplete in mind where the popumenu is not opened by the user. I think as long as the user does not select anything in the popupmenu, he wants to ignore the suggestions or functionality provided by it. If the user decides there is a something in the popupmenu he wants to complete he uses a key such i_<TAB> to get to the entry. In the case of a simple text completion, selecting the entry is sufficient, however, snippet expansion is usually triggered by a separate key. IMHO <CR> seems to be a natural choice of confirmation since i_<Tab> is already in use for popupmenu cycling.

From my point of view the popmenu is a separate layer/mode the user is in. Certain mappings have a different meaning compared to when the popupmenu is closed (e.g. <C-y>). And yes, the mode is already active regardless if something is selected or not. But that doesn't mean, it shouldn't be possible to distinguish different situations when the popupmenu is open. See for example the vim documentation :h ins-completion-menu:

There are three states:

  1. A complete match has been inserted, e.g., after using CTRL-N or CTRL-P.
  2. A cursor key has been used to select another match. The match was not
    inserted then, only the entry in the popup menu is highlighted.
  3. Only part of a match has been inserted and characters were typed or the
    backspace key was used. The list of matches was then adjusted for what is
    in front of the cursor.

And as far as I can see, no one has to use pumselected() and change the code of their plugin (either ultisnips or neocomplete/deoplete). One can leave this up to the user.

@lifepillar
Copy link
Contributor

lifepillar commented Aug 23, 2017

I think that, for what you want to achieve, you may use v:completed_item, which is a Dictionary providing information about the selected item:

  fun! TestCompletedItem()
    if pumvisible()
      echomsg string(v:completed_item)
    else
      return "\<cr>"
    endif
  endf

  inoremap <expr> <cr> TestCompletedItem()

See also :help complete-items.

@kiryph
Copy link
Author

kiryph commented Aug 23, 2017

@lifepillar THANK YOU VERY MUCH! This looks very much like what I want.

My new <CR> mapping is now

" inoremap <CR> --- {{{2
" 1. when pumvisible & entry selected, which is a snippet, <CR> triggers snippet expansion,
" 2. when pumvisible & entry selected, which is not a snippet, <CR> only closes pum
" 3. when pumvisible & no entry selected, <CR> closes pum and inserts newline
" 4. when pum not visible, <CR> inserts only a newline
function! s:ExpandSnippetOrClosePumOrReturnNewline()
    if pumvisible()
        if !empty(v:completed_item)
            let snippet = UltiSnips#ExpandSnippet()
            if g:ulti_expand_res > 0
                return snippet
            else
                return "\<C-y>"
            endif
        else
            return "\<C-y>\<CR>"
        endif
    else
        return "\<CR>"
    endif
endfunction
inoremap <silent> <CR> <C-r>=<SID>ExpandSnippetOrClosePumOrReturnNewline()<CR>

I've clarified the behaviour of <CR> a little bit more.

Do you know if it is sufficient to verify that the dictionary is empty?

@kiryph kiryph closed this as completed Aug 23, 2017
@lifepillar
Copy link
Contributor

Do you know if it is sufficient to verify that the dictionary is empty?

That should be enough: as far as I know, when the dictionary is empty, it means that the user has not selected an item or has cancelled the completion with ctrl-e.

@ghqhxnfizloub
Copy link

Do you know if it is sufficient to verify that the dictionary is empty?

That should be enough: as far as I know, when the dictionary is empty, it means that the user has not selected an item or has cancelled the completion with ctrl-e.

I found in some cases, after the completion is cancelled with C-e, the v:completd_item dictionary is not empty.

@ghqhxnfizloub
Copy link

After some testing, I found that noinsert must be in completeopt in order to make !empty(v:completed_item) work.

@lifepillar
Copy link
Contributor

lifepillar commented Apr 29, 2019

@brglng From :help v:completed_item:

Dictionary containing the complete-items for the most recently completed word after CompleteDone.

You may want to check v:completed_item after a CompleteDone event, and not as I have shown above (my apologies for misleading example):

fun! TestCompletedItem()
  echomsg string(v:completed_item)
endf

autocmd CompleteDone * call TestCompletedItem()

@paulolieuthier
Copy link

@brglng after 8.1.1068 you can use complete_info()["selected"].

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants