-
Notifications
You must be signed in to change notification settings - Fork 4k
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
[LSP] Add Razor options provider to Roslyn #53879
Conversation
src/Features/LanguageServer/Protocol/Handler/Completion/CompletionResolveHandler.cs
Outdated
Show resolved
Hide resolved
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.
Have one main piece of feedback before I look at the rest
src/Features/LanguageServer/Protocol/Handler/Completion/CompletionResolveHandler.cs
Outdated
Show resolved
Hide resolved
return Task.FromResult(CompletionChange.Create(new TextChange(item.Span, item.DisplayText))); | ||
} | ||
public virtual Task<CompletionChange> GetChangeAsync(Document document, CompletionItem item, char? commitKey, CancellationToken cancellationToken) | ||
=> Task.FromResult(CompletionChange.Create(new TextChange(item.Span, item.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.
I just changed this to expression body to be more consistent with the style of the other methods in this class
I totally overhauled this PR based on your super helpful feedback @davidwengier and @dibarbet. The check for Razor options is now performed in the formatter, and the specific tabs/spaces options logic is now done on the Razor side (via dotnet/razor#3719). If either of you could take another look whenever you have time, that would be appreciated. Thanks 😀 |
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.
So much cleaner 😁
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.
🎉 totally agree with David, I much prefer this iteration
var documentOptionSetProvider = document.Services.GetService<IDocumentOptionSetProvider>(); | ||
if (documentOptionSetProvider is not null) | ||
{ | ||
options = await documentOptionSetProvider.GetOptionsForDocumentAsync(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.
Hmmm, one interesting implication is that if you pass in options to this method they'll be ignored because the document options will win. With that in mind the options on line 157 account for this; in an ideal world all call sites to this method wouldn't actually pass options in "within" Roslyn and would then fall back to the appropriate document options services. All that can be done in the future though, just my 2cents
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.
Yeah this seems really weird here. Couldn't the LSP layer or something deal with coupling this at that layer versus something else?
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.
Currently Razor is the only implementer of IDocumentOptionSetProvider
so in practice I don't think there's currently an issue, however I totally see both of your points about the check being weird here. I'm exploring alternate options, will hold off on merging in the meantime.
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 approach feels really funky and it feels like we still don't have this wired up at the right layer....?
// Languages such as Razor can specify their own options instead of using the document's options. | ||
var documentOptionSetProvider = document.Services.GetService<IDocumentOptionSetProvider>(); | ||
if (documentOptionSetProvider is not 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.
So why is this having to be checked here rather than in the core document options logic we have, like through https://github.com/dotnet/roslyn/blob/main/src/Workspaces/Core/Portable/Options/IDocumentOptionsProvider.cs?
@jasonmalinowski @NTaylorMullen Based on your feedback, I refactored out the Razor options logic from the formatter over to the indentation service. This required once again overhauling a large portion of the PR, namely due to having to change the return type of the added Razor external access method. If either of you (or anyone else reading this) have any concerns with the updated approach, please let me know! |
src/Tools/ExternalAccess/Razor/RazorDocumentOptionsServiceWrapper.cs
Outdated
Show resolved
Hide resolved
src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs
Outdated
Show resolved
Hide resolved
src/EditorFeatures/TestUtilities/LanguageServer/AbstractLanguageServerProtocolTests.cs
Outdated
Show resolved
Hide resolved
=> Task.FromResult<IDocumentOptions?>(new DocumentOptions(document.Project.Solution.Workspace, document.Id, _indentationManagerService)); | ||
public async Task<IDocumentOptions?> GetOptionsForDocumentAsync(Document document, CancellationToken cancellationToken) | ||
{ | ||
// Languages such as Razor can specify their own formatting options. |
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.
So it's a bit odd that we are having to modify the InferredIndentationDocumentOptionsProviderFactory here when we could have a new MEF export. The Razor ExternalAccess could directly export it's own IDocumentOptionsProviderFactory that imports the Razor service directly. This would mean you can keep almost the entire change in the Razor layer and not have to introduce the extra layer in the middle: just rename IDocumentOptionsService to IRazorDocumentOptionsService and you can get away with bit less wrapping.
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.
(it's also otherwise odd we'd be having this at an EditorFeatures layer, since what happens for VS Code?)
@@ -179,5 +179,13 @@ public static ImmutableArray<AbstractFormattingRule> GetFormattingRules(this Doc | |||
|
|||
return rules.AddRange(Formatter.GetDefaultFormattingRules(document)); | |||
} | |||
|
|||
public static bool IsRazorDocument(this Document document) |
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.
Method was moved
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.
You moved it here for ExternalAccess.Razor right? If you all are "ok" with this living here then I don't have an issue but I also wouldn't be opposed to duplicating this in the ExternalAccess.Razor layer
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 think I would prefer leaving in external access if possible - while currently 'true' the presence of a span mapping service means it's razor, there's nothing saying that spans can't be mapped in other cases. So I think I'd prefer to not let it leak too far.
Also, not sure if checking for this might be more appropriate? - https://sourceroslyn.io/#Microsoft.CodeAnalysis.ExternalAccess.Razor/RazorDocumentPropertiesServiceWrapper.cs,23 because span mappers are used in legacy razor as well and I'm guessing we don't want to override options there
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.
Created a new ExternalAccess class for Razor extensions - https://github.com/dotnet/roslyn/blob/b9eb126fc9c7539f6e7cd3ee31dd1da318ac2f1c/src/Tools/ExternalAccess/Razor/Extensions.cs
Hope this is a little cleaner!
@jasonmalinowski I moved to a MEF-based approach in response to your feedback. One thing I was unsure about was whether there's a way to exclusively activate the Razor options factory in Razor scenarios, as right now it activates with every type of project/solution. To mitigate this, I added a |
src/VisualStudio/Core/Def/Implementation/LanguageClient/VisualStudioInProcLanguageServer.cs
Outdated
Show resolved
Hide resolved
namespace Microsoft.CodeAnalysis.ExternalAccess.Razor | ||
{ | ||
[Shared] | ||
[ExportMetadata("Extensions", new string[] { "cshtml", "razor", })] |
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.
What does this bit do exactly? Only trigger when there is a cshtml or razor file in the project? And you still need the razor check down below since generated documetns don't fit here, is that right?
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 point, I checked with Taylor just now and it seems the line is actually unnecessary so I removed it - seems like it's required for Razor's dynamic file info provider but not here, it just tells Roslyn to ask Razor for dynamic file info when a .cshtml or .razor file is added.
@@ -179,5 +179,13 @@ public static ImmutableArray<AbstractFormattingRule> GetFormattingRules(this Doc | |||
|
|||
return rules.AddRange(Formatter.GetDefaultFormattingRules(document)); | |||
} | |||
|
|||
public static bool IsRazorDocument(this Document document) |
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 think I would prefer leaving in external access if possible - while currently 'true' the presence of a span mapping service means it's razor, there's nothing saying that spans can't be mapped in other cases. So I think I'd prefer to not let it leak too far.
Also, not sure if checking for this might be more appropriate? - https://sourceroslyn.io/#Microsoft.CodeAnalysis.ExternalAccess.Razor/RazorDocumentPropertiesServiceWrapper.cs,23 because span mappers are used in legacy razor as well and I'm guessing we don't want to override options there
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.
One nit-pick, otherwise looks OK, though I lack context.
Thanks everyone for the reviews! I think I've gotten to all the feedback, if there's anything else please let me know. If everything looks good, I plan to merge this by EOD so we can start getting the Razor side PR going as well. |
Fixes (once Razor side is also merged):
https://github.com/dotnet/aspnetcore/issues/32555