-
Notifications
You must be signed in to change notification settings - Fork 272
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
Snippets should be passed to snippet managers instead of completion plugins #379
Comments
Also happens with cquery v20180302-6 (2, 1.35) build from AUR I'm even not sure that such snippet even make sence, because it should be just Edit: |
I believe in all those cases, you can jump to snippet placeholders with your defined shortcut |
No. It's plain text. Not a snippet placeholder. |
It works with nvim-completion-manager and UltiSnips but it requires a rather complex setup. |
@YaLTeR what you refering to as "works" is just a workaround of the problem. LC inserts snippet placeholders as text, instead of integrating with snippet managers. I already have a complex setup to create seampless completion and jumping via one key for 3 plugins (deoplete, ultisnips, delimitmate), and I'd better avoid another one for another plugin. The main Idea that if plugin searches for snippet managers, means that plugin supports them. Without complex configurations. Like NCM + NCM Clang + Ultisnips do. It just works. Parameter expansion like no problem. Plug and play. But I don't want to use NCM because it has LOT of problems. For example it perceives c style I'd like to replace ALE in my current configuration with LC because LC is much faster, and has more features, like renaming symbols, good semantic completion, etc etc, but I don't want to loose snippets too. |
Right now, the right completion (kind of a dynamic snippet) is provided to completion engine, completion engine indeed inserts requested completion item in the right place, and one can jump to snippet placeholder(s) with predefined shortcut(s) after completion. I do appreciate if the whole experience is smoother, i.e., it should automatically jump to the first placehoder after completion if the completed item is a snippet. But there are a few concerns about making the described behaviour happen in this repo,
To be frank, I don't feel too motivated to work on this area because of those previously mentioned concerns. Maybe https://github.com/Shougo/deoppet.nvim will make the situation better. |
@autozimu You can define a snippet and pass it to snippet manager instead of completion manager. Because snippets that are provided by snippet manager are displayed in completion manager popup, you can expand it via snippet manager plugin mappings, because it is it's work. Not a work of completion plugin. So there is no need to pass snippet to completion system. You need to pass a trigger only to completion engine (or even nothing, because trigger is contained in snippet definition, so it will be listed in completion list automatically), wich in this case will be function name only ( So
Wrong. It is related to Snippet engine only.
And most completion managers are already well integrated with most of snippet managers. So only implementation of passing snippets to snippet managers is needed. Most of snippet engines provide a way to pass a snippet definition to them in realtime. And in case that your plugin already searches for snippet managers, and defines a snippet, you need only to change where to pass it. Because passing it to completion system is wrong in first place. |
As I said, I won't be actively working on this. PR is always welcome. |
Then disable searching for snippet plugins because it impossible to use LCN with it. Or make a setting to disable it, because It is absolutely unusable when you need to edit every completion to get rid of placeholders that are just plain text. |
IIUUC your current implementation looks like this: Lang Server creates a snippet → LCN → Completion plugin → Snippet plugin So if user doesn't use any completion plugin, but uses snippet plugin (as it doesn't need a completion system, because it only displays the triggers in it) User simply can't use snippets because they are passed to nowhere. If your plugin will pass it directly to snippet plugin (wich is make sence, because snippet should be passed to snippet manager, and completion should be passed to completion manager) any user, with or without completion plugin will be able to use such snippets. I'm proposing a change to: Lang Server creates a snippet → LCN → Snippet Manager The rest is up to snippet manager, it will pass snippet's trigger to completion system automatically, because it is usual behavior. And user will decide if he want to expand it, or just plain complete the trigger. I'ts not only makes it easier to everyone to implemet and maintain this, but more logical too, because each plugin will to the work it was designed to do. You may wonder why you would decide to expand function name only? There are some situations like so: // fancy function pointer typedef
typedef int (*lambda)();
// lets use this function via pointer in main()
int vaiv(int a, int b, int c)
{
return a + b + c;
} So for example in main i would like to have function name only, to pass it's address to function pointer: int main()
{
lambda ptr_to_vaiv = vaiv; // Imagine I've completed name only
return ptr_to_vaiv(1, 2, 3); // Now I expand the snippet defined by Lang Server
} But with your current realisation I will have: int main()
{
lambda ptr_to_vaiv = vaiv(${1:int a}, ${2:int b}, ${3:int c})$0| // Jeez...
} Where |
And this is not what i;m asking for at all. Because it is snippet manager work too. And it will work out of the box with proposed change, because user will trigger a snippet himself. Even without completion plugin. |
This does make sense actually, typing the whole name of the function and hitting the expand snippet key doesn't seem to involve a completion manager plugin in any way. It's kinda tied together though because language servers return snippets as part of completion responses, not by themselves. |
@YaLTeR Yes, you even don't need to type the whole name of the function, because you can complete it with vim's builtin completion, wich doesn't even need to integrate with LC, and then hit expand snippet button, because basicaly you completed a trigger's text
Well I guess that language client, wich recieves the completion from language server, should decide where to pass it, as it client's job to handle interfaces between vim, pugins and lang server. If it is snippet - to snippet manager. If it is semantic completion - to completion manager. |
@autozimu @YaLTeR I've made a simple snippet manager, and developed a function that should allow other plugins to communicate with it. Take a look: Here I define a snippet by hand, by calling a function: I'm not asking of implementation of usage of this function in LCN, but just want to illustrate how I think it should work:
|
I think there's need to clarify the responsibility, LCN should better not bundled to specified function/plugin, it could detect the complete item text format from server response and just pass the complete item info with text format to complete plugin. It's complete plugin's job to make the snippet works. |
@autozimu
since it's not working. |
Snippet managers don't need completion plugins at all. You're adding an unneeded route in snippet path from LCN to snippet manager, wich will make snippets unawailible for people who simply not use completion plugins, and are just use lang server for linting and other features like renaming symbol. how it works now without LCN in UltiSnips for example:
lets add LCN. I'm proposing this path:
Now your proposed way of doing it:
Everyone can use ultisnips without completion plugin. Why someone should add a completion plugin as a dependency to LCN snippets? |
@andreyorst the snippet of LSP is one kind of complete item in LSP side, it could comes with other plain text complete items, how could you make user to choose complete item if different kinds of complete item goes to different plugin?
It's not LCN snippets, snippets of LSP comes from complete, but snippet manager doesn't provide complete feature |
I suppose one way of handling this would be to feed the snippets from the language server to the snippet plugin, then strip them from any snippet parts and feed to the completion plugin. This way the user would select the completion from the completion plugin (if they have one) and then expand it with the snippet plugin (if they have one). |
@YaLTeR that could work, but then LanguageClient-neovim would have to implement adapters of each complete plugin for feed the snippet, and the user have to manually trigger complete shortcut to complete the snippet instead of just For user experience and simplification, the snippet support of LSP item should comes from completion plugin or vim/neovim natively. |
I don't feel like putting this job on completion managers is the right choice. I'd love to see this working with LCN because otherwise the placeholders are kinda useless, but I don't think it will work out nicely if we just expect completion engines to suddenly handle snippets too. |
There's no easy way for vim plugin to support this feature, so I created a feature request for neovim: |
Thanks @chemzqm, I'd love to see this work with LCN. It would truly step it up to another level of convenience for me. |
I workaround the problem in UltiSnips side as follow in vimrc. Hope it can help somebody who urgently need a temporary solution. function! ExpandLspSnippet()
call UltiSnips#ExpandSnippetOrJump()
if !pumvisible() || empty(v:completed_item)
return ''
endif
" only expand Lsp if UltiSnips#ExpandSnippetOrJump not effect.
let l:value = v:completed_item['word']
let l:kind = v:completed_item['kind']
let l:abbr = v:completed_item['abbr']
" remove inserted chars before expand snippet
let l:end = col('.')
let l:line = 0
let l:start = 0
for l:match in [l:abbr . '(', l:abbr, l:value]
let [l:line, l:start] = searchpos(l:match, 'b', line('.'))
if l:line != 0 || l:start != 0
break
endif
endfor
if l:line == 0 && l:start == 0
return ''
endif
let l:matched = l:end - l:start
if l:matched <= 0
return ''
endif
exec 'normal! ' . l:matched . 'x'
if col('.') == col('$') - 1
" move to $ if at the end of line.
call cursor(l:line, col('$'))
endif
" expand snippet now.
call UltiSnips#Anon(l:value)
return ''
endfunction
imap <C-k> <C-R>=ExpandLspSnippet()<CR> I'm not familiar with vimL and UltiSnips, it should much simple than what i written. I hope there is an official solution, either LCN or snippet side. |
@lisongmin, this workaround duplicated func name. example, |
steps are the same as yours, but language is golang.
is it possible caused by prefix exec. ? |
@butterflyfish can your try this version? I remove the inserted chars with len() now, and also work fine with me. function! ExpandLspSnippet()
call UltiSnips#ExpandSnippetOrJump()
if !pumvisible() || empty(v:completed_item)
return ''
endif
" only expand Lsp if UltiSnips#ExpandSnippetOrJump not effect.
let l:value = v:completed_item['word']
let l:matched = len(l:value)
if l:matched <= 0
return ''
endif
" remove inserted chars before expand snippet
if col('.') == col('$')
let l:matched -= 1
exec 'normal! ' . l:matched . 'Xx'
else
exec 'normal! ' . l:matched . 'X'
endif
if col('.') == col('$') - 1
" move to $ if at the end of line.
call cursor(line('.'), col('$'))
endif
" expand snippet now.
call UltiSnips#Anon(l:value)
return ''
endfunction
imap <C-k> <C-R>=ExpandLspSnippet()<CR> |
perfectly. works now. thank you very much! |
@lisongmin how about sending MR to upstream UltiSnips? |
I'd like to do so, but i don't known how to remove inserted candidate words in UltiSnips way. It should simpler than the workaround. |
Here's a slight tweak to @lisongmin's tweak to allow the use of the Enter key: Change the initial return to return a newline: if !pumvisible() || empty(v:completed_item)
return '^M'
endif where imap <silent> <CR> <C-r>=ExpandLspSnippet()<CR> And after selecting a completion you can hit Enter to start the expansion. |
Is there a chance to get first placeholder to work as well? If I use the proposed "hack", the cursor is correctly placed on the first placeholder, but it is not selected and thus cannot be replace by starting to type. |
it's recommended to use https://github.com/ncm2/ncm2-ultisnips |
@butterflyfish How does that relate to deoplete, which I have been using so far? |
@autozimu the conversation went off topic. Should I close the issue, or you'll lock the conversation? |
I made an autocommand based on the CompleteDone event which some of you may find useful. let g:ulti_expand_res = 0 "default value, just set once
function! CompleteSnippet()
if empty(v:completed_item)
return
endif
call UltiSnips#ExpandSnippet()
if g:ulti_expand_res > 0
return
endif
let l:complete = type(v:completed_item) == v:t_dict ? v:completed_item.word : v:completed_item
let l:comp_len = len(l:complete)
let l:cur_col = mode() == 'i' ? col('.') - 2 : col('.') - 1
let l:cur_line = getline('.')
let l:start = l:comp_len <= l:cur_col ? l:cur_line[:l:cur_col - l:comp_len] : ''
let l:end = l:cur_col < len(l:cur_line) ? l:cur_line[l:cur_col + 1 :] : ''
call setline('.', l:start . l:end)
call cursor('.', l:cur_col - l:comp_len + 2)
call UltiSnips#Anon(l:complete)
endfunction
autocmd CompleteDone * call CompleteSnippet() These are the mappings I use, although any mappings should work with the autocommand: imap <silent><expr> <tab> pumvisible() ? "\<c-y>" : "\<tab>"
let g:UltiSnipsExpandTrigger="<NUL>"
let g:UltiSnipsListSnippets="<NUL>"
let g:UltiSnipsJumpForwardTrigger="<tab>"
let g:UltiSnipsJumpBackwardTrigger="<s-tab>" |
Why are we parsing the placeholders in vimL when we can do it in LanguageClient-neovim.... |
For these completion/snippet manager, when they claim they have LSP/LanguageClient-neovim integration, they should make clear if https://microsoft.github.io/language-server-protocol/specification
|
FWIW, there're some differences between ultisnips/neosnippet/snipmate syntax and lsp snippet syntax. The best way for integration, I believe, is to first parse the lsp snippet, and then convert it to your native snippet code. I have written a small parser, and as a result, ultisnips/neosnippet/snipmate can integrate perfectly with ncm2. https://github.com/ncm2/ncm2-neosnippet The parser and the plugins listed above are all written from scratch, and MIT licensed. Feel free to reuse some of the code for your own use case. |
It is fixed in the latest version of neosnippet. @autozimu The issue can be closed. |
I just tested this with Rust and it works amazingly well. Thanks for your work @Shougo! |
@Shougo Thanks! |
Verified. Closing. |
Summary
LC Detects UltiSnips snippet manager
LanguageClient-neovim/autoload/LanguageClient.vim
Lines 36 to 40 in d690d2b
and creates snippets, that are listed in completion popup via deoplete, but upon completion it just inserts placeholders as text. See screenshots below:
Just text completed function name:
Now I selected [LC] source in the list:
After completion confirm I left with plaintext placeholders inside function definition:
Reproduction
minimalrc.txt
andmain.rs.txt
(you will need vundle to install plugins (sorry), and:UpdateRemotePlugins
)main.rs.txt
tomain.rs
(damn github)nvim -u /path/to/minimalrc.txt /path/to/main.rs
vaiv()
insidemain
's bodyvaiv(${1:a}, ${2:b})
/tmp/LanguageClient.log
and/tmp/LanguageServer.log
.LanguageClient.log
LanguageServer.log
Current Behavior
Expected Behavior
There were some disscussion about it here: Shougo/deoplete.nvim#724 (comment)
There currently discussion abut supporting LSP snippets in ultisnips here: SirVer/ultisnips#964
The text was updated successfully, but these errors were encountered: