-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Semantic Snippets: Console snippet first look #59303
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
Semantic Snippets: Console snippet first look #59303
Conversation
|
This is just an example, isn't it? Because if it is not, I see no reason for making a separate overcomplicated snippet to just write to the console. But it would be excellent, if all existing snippets are transformed to such context dependent form. |
|
@DoctorKrolic as mentioned, it's a first look. |
This comment was marked as off-topic.
This comment was marked as off-topic.
|
@DoctorKrolic For more background on what we are trying to do ther I would recommend reading the document here: https://github.com/dotnet/roslyn/blob/features/semantic-snippets/docs/ide/specs/semantic_snippets.md. Please feel free to post your thoughts on this issue #56541 |
jmarolf
left a comment
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.
Great start! for this feature branch I am hoping that we can do a bunch of small PRs instead of a one big one. In that vein I would ensure that CI passes and that these items are done before merging (which I am interpreting as adding tests :)):
-Need to make sure all cases are being hit
-Need to make sure formatting of the original document stays the same after inserting the snippet
-Add comments
For these I think we can do them in a separate PR
-Need to figure out how to have several possible shortcuts to invoke the completion item
-Possibly should look within the document for something that can be written in the WriteLine?
Yeah that's just an overall TODO list. It seems that CI is looking at some stale code. I need to figure out why it's looking at some snippet completion providers that I had removed for the PR. |
|
Ping me when you would like a review. |
src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs
Outdated
Show resolved
Hide resolved
src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs
Outdated
Show resolved
Hide resolved
| if (await IsValidSnippetLocationAsync(document, position, cancellationToken).ConfigureAwait(false)) | ||
| { | ||
| return new SnippetData(SnippetDisplayName, SnippetIdentifier); | ||
| } | ||
|
|
||
| return null; |
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.
| if (await IsValidSnippetLocationAsync(document, position, cancellationToken).ConfigureAwait(false)) | |
| { | |
| return new SnippetData(SnippetDisplayName, SnippetIdentifier); | |
| } | |
| return null; | |
| if (!await IsValidSnippetLocationAsync(document, position, cancellationToken).ConfigureAwait(false)) | |
| { | |
| return null; | |
| } | |
| return new SnippetData(SnippetDisplayName, SnippetIdentifier); |
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.
general idea here being to be consistent with how you bail out or return values. it makes tracking overal logic and data flow easier.
| protected virtual Task<ImmutableArray<TextSpan>> GetRenameLocationsAsync(Document document, int position, CancellationToken cancellationToken) | ||
| { | ||
| return Task.FromResult(ImmutableArray<TextSpan>.Empty); | ||
| } |
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.
i woudl remove this from the PR for now.
| internal abstract class AbstractSnippetService : ISnippetService | ||
| { | ||
| private readonly ImmutableArray<Lazy<ISnippetProvider, LanguageMetadata>> _lazySnippetProviders; | ||
| private readonly Dictionary<string, ISnippetProvider> _snippetProviderDictionary = new(); |
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.
with dictionaries, it's good to have the name indicate waht this is mapping from/to. e.g. identifierToProviderMap, etc.
| { | ||
| private readonly ImmutableArray<Lazy<ISnippetProvider, LanguageMetadata>> _lazySnippetProviders; | ||
| private readonly Dictionary<string, ISnippetProvider> _snippetProviderDictionary = new(); | ||
| private ImmutableArray<ISnippetProvider> _snippetProviders = new(); |
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.
new() here doesn't do anything. indeed, as a struct, you'll still just get the defautl immutable array.
|
|
||
| public ISnippetProvider GetSnippetProvider(string snippetIdentifier) | ||
| { | ||
| return _snippetProviderDictionary[snippetIdentifier]; |
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.
this seems like it could completely fail. how do you know _snippetProviderDictionary is initialized? consider doc'ing/asserting things you think are true at thsi point.
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.
This should never be called prior to GetSnippetsAsync. Is that sufficient or should there be some other checks at this point?
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.
in this case, it woudl be good to have a Contract call like Contract.ThrowIfFalse(_snippetProviderDictionary.ContainsKey(snippetIdentifier));
|
|
||
| private ImmutableArray<ISnippetProvider> GetSnippetProviders(Document document) | ||
| { | ||
| using var _ = ArrayBuilder<ISnippetProvider>.GetInstance(out var arrayBuilder); |
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.
move this inside the if check.
| { | ||
| var providerData = provider.Value; | ||
| arrayBuilder.Add(providerData); | ||
| _snippetProviderDictionary.Add(providerData.SnippetIdentifier, providerData); |
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.
this is definitely not threadsafe, and will throw if two threads are calling into GetSnippetProvider at the same time.
| { | ||
| internal interface ISnippetProvider | ||
| { | ||
| string SnippetIdentifier { get; } |
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.
Doc.
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.
doc what happens if two snippet providers have hte same ID.
| { | ||
| string SnippetIdentifier { get; } | ||
|
|
||
| string SnippetDisplayName { get; } |
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.
Doc
| /// <summary> | ||
| /// The TextSpans that need to be renamed if we insert a snippet with values that the user may want to change | ||
| /// </summary> | ||
| public readonly ImmutableArray<TextSpan> RenameLocations; |
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.
consider removing for now.
|
|
||
| private async Task<TextChange> GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) | ||
| { | ||
| var symbol = await GetSymbolFromMetaDataNameAsync(document, cancellationToken).ConfigureAwait(false); |
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.
Contract.ThrowIfNull on this (mentioning we only have the snippet if we had this symbol).
| { | ||
| var providerData = provider.Value; | ||
| arrayBuilder.Add(providerData); | ||
| _identifierToProviderMap.Add(providerData.SnippetIdentifier, providerData); |
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.
this will throw if two providers ahve the same snippet id. Is that something we can ensure will never happen? are all snippet IDs under our control? or could a 3rd party add a snippet with teh same ID and crash the system? if this is all under our control, this is fine. but it would likely be better to have this be tolerant so that misauthored snippets don't kill things.
note: you can do a Debug.ASsert here so that if we screw up we at least know about it in our debug builds, but this won't fire and cause problems in release builds.
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.
All snippet IDs should be under our control, but some tolerance can be added here.
| /// <summary> | ||
| /// The type of snippet, equivalent to what gets displayed in the Completion list | ||
| /// </summary> | ||
| public readonly string DisplayText; |
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.
do we need this? what do you use this for?
| /// <summary> | ||
| /// Encapsulates the information that makes up a Snippet. | ||
| /// </summary> | ||
| internal readonly struct Snippet |
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.
looking at this, perhaps you should call this SnippetChange to indicate that it's the change that needs to be made to apply the snippet to user code.
Here's a first look at snippets!
I wanted to work on a basic example first, the console writeline snippet.
Here's a short video showcasing how it works:
Console.Snippet.Recording.mp4
Design
Snippet.cs:Stores information for the type of snippet it is (what is displayed on the completion list), the TextChange that is created by this snippet, the position we want the cursor to end up, and locations we would want to call upon a rename.
SnippetData.cs:Acts as a middle-man for ProvideCompletionsAsync() and GetChangesAsync() in the
AbstractSnippetCompletionProvider. We do not want complete all the logic to determine the new TextChange and cursor position at the point that we are simply determining if the completions should exist at a certain position. This is then used to retrieve the actualSnippet.ISnippetService.cs:Interface implemented by the
CSharpSnippetServicewhich gets available snippets at a particular position.ISnippetProvider.cs:Interface implemented by the
AbstractSnippetProviderAbstractSnippetCompletionProvider.cs:Looks at the context and calls upon the
ISnippetServiceto return all available snippets at that location to be added to the completion list. GetChangesAsync() calls upon theISnippetServiceto get theISnippetProviderrelated to the CompletionItem that was selected. TheISnippetProviderretrieves the correspondingSnippetto create the CompletionChange.AbstractSnippetProvider:To be implemented by every snippet we want created. Generates the
Snippet. Each snippet will need to implement a method to generate the snippet TextChange, annotate the newly added syntax for the cursor location, annotate the newly added syntax to be reformatted, get the integer position we want the cursor to appear, and return all the rename spans if applicable.Todo:
-Need to figure out how to have several possible shortcuts to invoke the completion item
-Need to make sure all cases are being hit
-Need to make sure formatting of the original document stays the same after inserting the snippet
-Possibly should look within the document for something that can be written in the WriteLine?
-Add comments