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

Get snippets working in Razor #9327

Merged
merged 27 commits into from
Oct 9, 2023
Merged

Get snippets working in Razor #9327

merged 27 commits into from
Oct 9, 2023

Conversation

ryzngard
Copy link
Contributor

Adds support for the razor LSP to get snippets defined in VS under the following conditions:

  1. They are not already being provided by the C# langauge server. This list
  2. They are an expansion snippet. LSP doesn't support SurroundsWith because of selection needed.
  3. We have some way of translating them.

Caveats here:

  • Right now we haven't added custom delimeter support.
  • Only HTML and CSharp snippets are used. We may need to add Javascript/Typescript, but we have no notion of that as a context right now.
  • The "Kind" is ignored. This treats all snippets like "any". We could potentially add support for this syntactically, but right now it's not added.
  • No Function, Declarations, Id, Import(s), Keyword(s), Literal, Namespace, Object, Reference(s), or Command support. These are in the snippet documentation. If needed we could address each of these individually.

@ryzngard ryzngard requested review from a team as code owners September 27, 2023 00:50
@ryzngard ryzngard marked this pull request as draft September 27, 2023 00:50
@ryzngard
Copy link
Contributor Author

Converting to Draft as there are known test breaks, and need to add unit tests. Finalizing the list of snippets we need to test with PM.

@@ -117,6 +127,8 @@ internal partial class RazorCustomMessageTarget
_telemetryReporter = telemetryReporter;
_languageServerFeatureOptions = languageServerFeatureOptions;
_projectSnapshotManagerAccessor = projectSnapshotManagerAccessor;
_snippetCache = snippetCache;
_xmlSnippetParser = xmlSnippetParser ?? new XmlSnippetParser(null);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why the [Export] didn't work here, but as long as we're always using the same cache it's okay. Also unsure why we don't have ILoggerFactory here but I'm guessing that's just client vs server stuff.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have just enough understanding of our logging to make things appear in logs when I want them, and to recognize that its all weird and probably wrong, but sadly not enough to know how to fix the wrongness :)

I think we would ideally have an ILoggerFactory here, and each method in this class would construct its own logger, since they are each endpoints. I'm sure there is an issue logged for better logging somewhere

@ryzngard ryzngard marked this pull request as ready for review September 27, 2023 23:00
Copy link
Contributor

@allisonchou allisonchou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Kind" is ignored. This treats all snippets like "any". We could potentially add support for this syntactically, but right now it's not added.
No Function, Declarations, Id, Import(s), Keyword(s), Literal, Namespace, Object, Reference(s), or Command support. These are in the snippet documentation. If needed we could address each of these individually.

As someone not super well versed in snippets, what are the implications of leaving out these properties? Do we end up getting snippets offered in places where they might not be valid?

Also, you mentioned that snippets already provided by the C# language server have been excluded. Which new C# ones are being provided with this change?

/// Shamelessly copied from the editor
/// https://devdiv.visualstudio.com/DevDiv/_git/VS-Platform?path=/src/Editor/VisualStudio/Impl/Snippet/CodeSnippet.cs
/// </summary>
internal class CodeSnippet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long term I wonder if we could convince the Editor to ship this class so we wouldn't have to make a copy

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, and now that there are two repo's that have copies of it (Roslyn and Razor) it would add weight to the argument for making a service of some kind to expose this info

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, we might be able to ask them to ship it. I can bring up in an LSP sync

Copy link
Contributor

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a few comments, but I wouldn't be surprised if the answer to some or all of them was "yes it's odd but this just how VS/snippets work"

s_CSharpLanguageId,
ImmutableHashSet.Create(
"~", "Attribute", "checked", "class", "ctor", "cw", "do", "else", "enum", "equals", "Exception", "for", "foreach", "forr",
"if", "indexer", "interface", "invoke", "iterator", "iterindex", "lock", "mbox", "namespace", "#if", "#region", "prop",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for now, but I'm excited by the prospect that removing "if" from this list might solve #6927 and let us turn on semantic snippets

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooo, maybe.

{
{
// These are identified as the snippets that are provided by the C# Language Server. The list is found
// in https://github.com/dotnet/roslyn/blob/8eb40e64564a2a8d44be2fde9b079605f1f10e0f/src/Features/LanguageServer/Protocol/Handler/InlineCompletions/InlineCompletionsHandler.cs#L41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious why we are doing snippets in normal completion, when Roslyn does them in inline completion?

(Note I am basing this question entirely off the filename in the comment here. I have no idea what the difference between the two is, or how the Roslyn code actually works 😛)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I don't know the difference between normal and inline either :/

private static readonly Dictionary<Guid, ImmutableHashSet<string>> s_ignoredSnippets = new()
{
{
// These are identified as the snippets that are provided by the C# Language Server. The list is found
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random thought: Should we remove the restriction on the Roslyn side, and let them give us all C# snippets, and then contribute this code, or some form of it, to Web Tools for html snippets?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. I think if we can get the editor to allow access to the snippet parsing code for everyone, then adding them to the html server and csharp server likely would be the right move.

@ryzngard
Copy link
Contributor Author

ryzngard commented Oct 2, 2023

The "Kind" is ignored. This treats all snippets like "any". We could potentially add support for this syntactically, but right now it's not added.
No Function, Declarations, Id, Import(s), Keyword(s), Literal, Namespace, Object, Reference(s), or Command support. These are in the snippet documentation. If needed we could address each of these individually.

As someone not super well versed in snippets, what are the implications of leaving out these properties? Do we end up getting snippets offered in places where they might not be valid?

Also, you mentioned that snippets already provided by the C# language server have been excluded. Which new C# ones are being provided with this change?

These properties give a more complex way of providing snippets, and making sure they only show up in certain contexts. So yes, in some cases we will show snippets that are invalid for the location.

No new C# built in snippets are provided, but if a user provides a custom snippet they will now be included.

Copy link
Contributor

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good, pending tests and/or integration tests.

Still not sure about whether this will pass RPS either, so perhaps a validation insertion?

ryzngard and others added 2 commits October 3, 2023 18:18
…nippetService.cs

Co-authored-by: David Wengier <david.wengier@microsoft.com>
@ryzngard
Copy link
Contributor Author

ryzngard commented Oct 5, 2023

Waiting on RPS Validation before I merge

@davidwengier
Copy link
Contributor

before I merge

Tests?
IsformeIsMineGIF

Is an integration test not really easy? All we need is one Html snippet that is shipped in-box, right?

Copy link
Member

@DustinCampbell DustinCampbell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very glad to see this happening?

using System;
using System.Collections.Generic;

namespace Microsoft.AspNetCore.Razor;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change namespace to System.Collections.Generic as we've done for other extension methods in Shared.Utilties to facilitate discoverability.

Comment on lines 58 to 59
dictionary[key] = defaultValue;
return dictionary[key];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid a double-lookup in the dictionary:

Suggested change
dictionary[key] = defaultValue;
return dictionary[key];
dictionary.Add(key, defaultValue);
return defaultValue;

@ryzngard
Copy link
Contributor Author

ryzngard commented Oct 5, 2023

before I merge

Tests? IsformeIsMineGIF IsformeIsMineGIF

Is an integration test not really easy? All we need is one Html snippet that is shipped in-box, right?

I appreciate this comment. I added an integration test and apparently pushed it to the internal branch but not this one 🤦

@ryzngard ryzngard enabled auto-merge (squash) October 9, 2023 20:38
@ryzngard ryzngard merged commit b95f430 into dotnet:main Oct 9, 2023
12 checks passed
@ghost ghost added this to the Next milestone Oct 9, 2023
davidwengier added a commit that referenced this pull request Oct 13, 2023
@davidwengier davidwengier mentioned this pull request Oct 13, 2023
@jjonescz jjonescz modified the milestones: Next, 17.9 P1 Oct 31, 2023
@Herdo
Copy link

Herdo commented Aug 17, 2024

@ryzngard Just found the limitations listed in this PR here. The listed limitations only apply to custom snippets, correct?
Built-in snippets seem to work correctly in the Razor editor, e.g. prop is correctly tabbing through the individual Declarations after inserting.

For custom snippets, if they have a Declaration, they don't insert at all.

@DustinCampbell
Copy link
Member

@Herdo: could you file a new issue that references this pull request? Questions on a PR resolved 10 months ago might easily be missed!

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

Successfully merging this pull request may close these issues.

6 participants