-
Notifications
You must be signed in to change notification settings - Fork 417
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
Add async completion support #1986
Conversation
@filipw @JoeRobich @david-driscoll I've opened this as a draft because I'm not 100% sure we actually want to take this. It'll require implementation work in every client as LSP does not support this. LSP can use the Pictures speak 1000 words, and animated gifs even more so. |
vscode side of OmniSharp/omnisharp-roslyn#1986.
Completion is getting better resolve support in 3.16. |
@david-driscoll better, but not good enough for our purposes. Unless they're planning on changing it, vscode only supports additionalTextEdits and detal/documentation fields for lazy resolve, not the main textEdit. |
Because, to be clear, I do not like this code. It's a race condition waiting to happen. But the vscode lsp implementation does not report |
command should be sufficient, however I don't know if there is a good logical flag for if an client supports it or not. |
vscode: supports it via native commands cc @nickspoons has vim knowledge |
There are many LSP clients for vim:
I flicked through a few of them and can't find any reference to this As for OmniSharp-vim, we're not an LSP client so we can adapt 😁 |
c921afd
to
9b21fe6
Compare
I think since this is based on a request flag, there is absolutely no problem in adding this and thus giving more advanced (more O# protocol aware) clients an option to use a superior approach for extra edits |
Note, I think I can (and probably should) make this a startup flag for omnisharp instead, so LSP clients that support the |
In fact, I think this is a must if Omnisharp and VSCode needs any chance to be feasible instead of Visual Studio. |
@333fred what are the next steps for this? Can it be marked as a real PR instead of a draft? I think this is super important. |
@ffMathy I'm really not happy with this code and think it's very likely to result in a subpar experience, which is why I haven't moved forward with it yet. |
What can be done to help though? The current experience is insanely slow (as seen in the first GIF). |
Frankly, I'm not certain. There's an inherent race condition with this PR and we really can't do anything to solve it. |
Hmm, but how does the full Visual Studio do it then? It manages to be much faster in terms of completions and suggestions. I think it's crucial to get to the same speed for larger projects. Is this still the plan abd/or vision? |
Similarly one could ask, how does TypeScript do it? It also remains super fast for very large projects. Could we get inspired by it? |
VS has native support for this operation. VS Code does not have support for this type of asynchronous completion, and thus this remains a hack around functionality that VS Code does not have.
Typescript doesn't do this type of operation, so they don't have to be fast for it :) |
Thank you for answering my questions so far. Is there an issue in the VS Code repo for supporting this type of async operation? |
I've talked with @jrieken about it before, I'm not sure if there is any specific issue open about making |
I made an issue microsoft/vscode#112527 for it now. |
I have closed the issue as out-of-scope and this is why: accepting a completion is a synchronous operation and no further blocking or waiting can happen. You have to assume that users are typing and that typing has accepted a completion. Needing to asynchronously resolve the text edit would mean that we (a) block typing or (b) employ some kind of OT strategy which inserts the text edit when resolving. Option (a) is not possible because you don't block in JS and (b) is complex to implement and yields in a subpar experience, e.g when I accept a completion I wanna see it now, not after a delay. |
FWIW, and I've brought this up before (though not in talking with you Johannes) I fundamentally disagree with this assertion. The experience today is subpar because bringing up the completion list takes 10+ seconds in these complex cases due to the nature of how we need to calculate the changes up front for every single completion in the list. I'd much rather have a 100 ms delay after insertion, rather than a 10+ second delay on bringing up the list. |
@333fred indeed. The current experience is simply unusable. Not just for the average user - for everyone. Every C# developer I know (which is a lot), wants to use VS Code, but they always back out because Omnisharp's autocompletion is slow. This is across all kinds of solutions that are just a little bigger than the average Hello World example (multiple projects, dependencies, etc). The current project I have is only 7000 lines of code and spans 3 projects. Visual Studio can produce suggestions instantly. In VS Code, it takes me 10+ seconds for the completions to arrive. @jrieken I agree blocking is probably not the option. That's why many people love VS Code in the first place in favor of Visual Studio for every other language than C#. However, whatever solution we end up with, it can't possibly be worse than the current state of things, and in that, I agree with @333fred. |
I think it's as simple as asking "does anyone want 10+ seconds of wait time for sguiggly error lines and/or warning lines to disappear, and the same wait time for waiting for completions and code-fixes?" The answer would clearly be no. What we have now is essentially Notepad with syntax highlighting, because of that wait time. And don't get me wrong. Omnisharp and VS Code are great. Just slow. The feature set is amazing and an excellent piece of work. I just think it's a waste when it doesn't get the opportunity to be used on a larger scale. It's almost like an API that presents an amazing feature, but doesn't have documentation for it. |
Yes, this is how import completion works in omnisharp today (and in TS, I believe). The issue with override completion is that the expensive part is calculating the minimum text necessary for the argument types and what the auto-inserted base call should look like. These edits intersect the current cursor location, and indeed where the cursor should finally end up is based on this calculation. This means that additionalTextEdits is not suitable, as we cannot control the cursor location or intersect the cursor location. There's other types of completion that become impossible or very hard to do well without asynchronicity. I know that rust-analyzer has brought this up in the past as well, where they want to have a |
The recent completion rewrite makes completion faster for a large number of cases, but it has a couple of issues: * Any completion provider that needs to make edits beyond the few we've special cased doesn't work correctly. Roslyn is looking to add more of these, such as dotnet/roslyn#47511. * Completion providers that we do special case can be _slow_. Override and partial method completion in big types is painful, taking over a minute to show up sometimes. To resolve this, I've added support for async edits by adding a new completion end point: /completion/afterInsert. It takes the item that was inserted, the file, and the new cursor position. It relies on the InsertText that was inserted still allowing for resolving the completion in the same way as before, so I had to force a few providers that don't behave well here to be eagerly resolved, regardless. I also kept import completion using the standard /completion/resolve step to resolve extra edits, as they don't need to modify the current cursor location at all.
9b21fe6
to
4dbfde2
Compare
vscode side of OmniSharp/omnisharp-roslyn#1986.
src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Sync.cs
Outdated
Show resolved
Hide resolved
/// <summary> | ||
/// Maximum number of completion lists allowed in cache. Must be >= 1. | ||
/// </summary> | ||
private const int MaxCacheSize = 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we used to have a single statically cached item, I am not sure I can follow why is there now two?
is this "just in case"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We now have an after insert step, and the user could technically have triggered a completion session in between. I want to make sure that we handle it somewhat gracefully.
src/OmniSharp.Roslyn.CSharp/Services/Completion/CompletionListBuilder_Async.cs
Show resolved
Hide resolved
|
||
if (lastCompletionItem.GetProviderName() is not (CompletionItemExtensions.OverrideCompletionProvider or | ||
CompletionItemExtensions.PartialMethodCompletionProvider) | ||
and var name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I didn't know you could do something like this 🧐
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Composition!
@filipw any more comments? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for me this is good to go - thanks!
@filipw what's the next step? |
it failed the last test run, I've enabled auto merge now |
@333fred congrats on getting this out, and thank you for making it happen! 😍 When can this be enabled, and how? |
When the vscode side is merged, and using the setting. |
The recent completion rewrite makes completion faster for a large number of cases, but it has a couple of issues:
To resolve this, I've added support for async edits by adding a new completion end point: /completion/afterInsert. It takes the item that was inserted, the file, and the new cursor position. It relies on the InsertText that was inserted still allowing for resolving the completion in the same way as before, so I had to force a few providers that don't behave well here to be eagerly resolved, regardless. I also kept import completion using the standard /completion/resolve step to resolve extra edits, as they don't need to modify the current cursor location at all.