Razor snippets#58857
Conversation
56417e2 to
3517692
Compare
9f05f30 to
2a64a09
Compare
2a64a09 to
50931d2
Compare
| namespace Microsoft.CodeAnalysis.CSharp; | ||
|
|
||
| [ExportLanguageService(typeof(SnippetFunctionService), LanguageNames.CSharp), Shared] | ||
| internal class CSharpSnippetFunctionService : SnippetFunctionService |
There was a problem hiding this comment.
extracted this code from the c# snippet functions
https://github.com/dotnet/roslyn/tree/main/src/VisualStudio/CSharp/Impl/Snippets/SnippetFunctions
| using Microsoft.CodeAnalysis.Text; | ||
|
|
||
| namespace Microsoft.CodeAnalysis; | ||
| internal abstract class SnippetFunctionService : ILanguageService |
There was a problem hiding this comment.
this class was extracted from https://github.com/dotnet/roslyn/tree/main/src/VisualStudio/Core/Def/Implementation/Snippets/SnippetFunctions and converted into a service to be shared by lsp
| namespace Microsoft.CodeAnalysis; | ||
| internal class SnippetUtilities | ||
| { | ||
| public static bool TryGetWordOnLeft(int position, SourceText currentText, ISyntaxFactsService syntaxFactsService, [NotNullWhen(true)] out TextSpan? wordSpan) |
There was a problem hiding this comment.
extracted from
|
|
||
| namespace Microsoft.CodeAnalysis.LanguageServer.Handler.InlineCompletions; | ||
|
|
||
| internal partial class InlineCompletionsHandler |
There was a problem hiding this comment.
code here was extracted from similar editor side code that reads snippet xml files and modified a bit to support our use case.
If we need to support XML based snippets long term, it would be nice if an API existed on the editor side to retrieve these. We would have to still do some conversion since some snippets are context dependent, but reading and parsing the XML could be left to the editor.
That being said, semantic snippets will probably make this irrelevant for built in c# snippets, and the editor is re-thinking the snippet experience so I don't believe it's worth doing anything right now.
| } | ||
| } | ||
|
|
||
| public ParsedXmlSnippet? Parse() |
There was a problem hiding this comment.
much of the parsing logic remains the same as the battle-tested editor code, the major change is to convert the snippet into 'SnippetParts' instead of just a pure string with default values.
| // Generate switch cases requires a multi-step snippet interaction, where the snippet is inserted first then the | ||
| // client calls back to on commit so that we can generate the cases for the specified switch value. | ||
| // This is not yet supported via LSP. | ||
| return this; |
There was a problem hiding this comment.
Didn't realize this is a two step process at the time of writing the LSP api :(
Future work here is to figure out a way for LSP to call back on the 'commit' of a tab stop and ask the server for more edits.
There was a problem hiding this comment.
Interesting, is this also handled in the new semantic snippets?
There was a problem hiding this comment.
semantic snippets would have to support it in some manner - the cases can only be known when the field is committed
| /// </summary> | ||
| [ExportRoslynLanguagesLspRequestHandlerProvider, Shared] | ||
| [ProvidesMethod(VSInternalMethods.TextDocumentInlineCompletionName)] | ||
| internal partial class InlineCompletionsHandler : AbstractStatelessRequestHandler<VSInternalInlineCompletionRequest, VSInternalInlineCompletionList> |
There was a problem hiding this comment.
the meat and potatoes of the actual new logic (that wasn't copied from the editor)
| var expansion = new ExpansionTemplate(matchingSnippet); | ||
|
|
||
| // Parse the snippet XML | ||
| var parsedSnippet = expansion.Parse(); |
There was a problem hiding this comment.
the parsed snippet needs to be cached. Am working on that, but will do as a separate PR to reduce the size here
Note that we cannot cache the actual LSP snippet, as it is context dependent (class names / formatting), but we can cache the snippet parts we read from XML.
There was a problem hiding this comment.
We also may want to build specific error handling around this given it's reading user content. Also might make sense to do the same with RetrieveSnippetFromXml above given that reads content from disk
There was a problem hiding this comment.
I think I'm OK with the amount of error handling in there right now. currently we do some basic checks ot make sure the file exists. But otherwise if there are malformed snippets, imho it is ok to let those exceptions bubble up to the queue and let them be automatically reported and logged.
Happy to hear other thoughts though.
There was a problem hiding this comment.
I did find a few cases I should probably handle actually, so will update updated
|
|
||
| namespace Microsoft.VisualStudio.LanguageServices.CSharp.Snippets.SnippetFunctions | ||
| { | ||
| internal sealed class SnippetFunctionClassName : AbstractSnippetFunctionClassName |
There was a problem hiding this comment.
the logic here can now live in a single impl for each language since the language specific logic is now in the snippetfunctionservice (and children)
| /// C:\Program Files\Microsoft Visual Studio\2022\VS_INSTANCE\VC#\Snippets\1033\Visual C# | ||
| /// These are currently the only snippets supported. | ||
| /// </summary> | ||
| public static ImmutableHashSet<string> BuiltInSnippets = ImmutableHashSet.Create( |
There was a problem hiding this comment.
I've limited these to built in snippets for now. While the parsing could probably handle most custom snippets, there are a few things that would not work, namely snippets with additional references (requires lsp response to support additional text edits) and selection (currently snippets are only invocable via tab-tab, so selection does not make sense in that context).
Depending on the direction that the editor takes xml snippets and where we go with semantic snippets, we can potentially open this up to more with a few improvements on the lsp api.
NTaylorMullen
left a comment
There was a problem hiding this comment.
This is super exciting and great work! I imagine the path you're taking here would also be used by our friends over on the XAML language service side too given it could potentially enable custom snippets too. Quite flexible.
I had some overarching concerns around the snippet parsing tech but reaching out offline about that.
| // Generate switch cases requires a multi-step snippet interaction, where the snippet is inserted first then the | ||
| // client calls back to on commit so that we can generate the cases for the specified switch value. | ||
| // This is not yet supported via LSP. | ||
| return this; |
There was a problem hiding this comment.
Interesting, is this also handled in the new semantic snippets?
|
|
||
| namespace Microsoft.CodeAnalysis.LanguageServer.Handler.InlineCompletions; | ||
|
|
||
| internal partial class InlineCompletionsHandler |
There was a problem hiding this comment.
Do you typically utilize partial classes heavily in Roslyn? They typically fall over in some source linked debug scenarios + make understanding implementation details a bit more tedious. Would it be awful to pull all the XML snippet tech into a service that the primary InlineCompletionsHandler utilizes?
There was a problem hiding this comment.
partial classes are not uncommon in roslyn. It sounds like source link debug scenarios need to be more resilient ;)
I don't mind either way really
| var expansion = new ExpansionTemplate(matchingSnippet); | ||
|
|
||
| // Parse the snippet XML | ||
| var parsedSnippet = expansion.Parse(); |
There was a problem hiding this comment.
We also may want to build specific error handling around this given it's reading user content. Also might make sense to do the same with RetrieveSnippetFromXml above given that reads content from disk
Totally agree! I haven't fully wrapped my mind around how to utilize this for custom snippets in XAML but I'm excited to see that the foundation has been laid. |
|
/cc @jimmylewis @ToddGrun if you're curious on snippets 😄 |
eaa0bb0 to
9be6faa
Compare
Related to dotnet/razor#6012
Implements support for built in snippets for Razor LSP using inline completions. This reads the XML snippet information, applies function results and then converts into an LSP formatted snippet.
While this should work for many XML snippets including custom ones, there are edge cases that I haven't tackled yet (no selection support, no add imports/references support). As such I've restricted this to built in snippets . Support for the remaining will require further iteration on both the lsp client portion and server world.
I will additionally have a followup PR to cache the snippets as we parse from XML.
