-
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
Create a Razor language server cohosted in Roslyn #70819
Conversation
@@ -35,19 +35,20 @@ public GetTextDocumentWithContextHandler() | |||
Contract.ThrowIfNull(context.Workspace); | |||
Contract.ThrowIfNull(context.Solution); | |||
|
|||
// We specifically don't use context.Document here because we want multiple | |||
var documents = context.Solution.GetDocuments(request.TextDocument.Uri); |
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.
Changing this to call GetTextDocuments
seemed overly wasteful given only project info is needed, so I changed this handler a bit. Mainly wanted to avoid calling GetDocument
and GetAdditionalDocument
on every Id (and this endpoint is called a fair bit)
ExportProvider exportProvider, | ||
[Import(AllowDefault = true)] IRazorCohostCapabilitiesProvider? razorCapabilitiesProvider = null, | ||
[Import(AllowDefault = true)] IRazorCustomMessageTarget? razorCustomMessageTarget = null, | ||
[Import(AllowDefault = true)] IRazorCohostLanguageClientActivationService? razorCohostLanguageClientActivationService = 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.
Made all of these nullable to avoid dual insertions etc. and so nothing activates if the Razor bits aren't installed. Though hopefully this never activates in that case, due to the content type :)
@@ -31,7 +31,6 @@ | |||
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServices.Razor" Key="$(RazorKey)" /> | |||
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServerClient.Razor" Key="$(RazorKey)" /> | |||
<InternalsVisibleTo Include="Microsoft.VisualStudio.LanguageServerClient.Razor.Test" Key="$(RazorKey)" /> | |||
<InternalsVisibleTo Include="Microsoft.VisualStudio.Mac.LanguageServices.Razor" Key="$(RazorKey)" /> |
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.
Snuck this change in here, and the above repo uri change :)
|
||
namespace Microsoft.CodeAnalysis.ExternalAccess.Razor.Cohost; | ||
|
||
internal abstract class AbstractRazorCohostRequestHandler<TRequestType, TResponseType> : ILspServiceRequestHandler<TRequestType, TResponseType> |
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.
Endpoints on the Razor side will inherit from one of these two abstract types.
|
||
protected override RazorTextDocumentIdentifier? GetRazorTextDocumentIdentifier(TextDocumentPositionParams request) | ||
{ | ||
return new RazorTextDocumentIdentifier(request.TextDocument.Uri, (request.TextDocument as VSTextDocumentIdentifier)?.ProjectContext?.Id); |
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 of the endpoints in the Razor repo will have to do this, in particular the second parameter, in order to ensure project context flows correctly. Not too bad, and I couldn't see a way around it. Will create a nice extension method on the Razor side for it anyway, so it would be return request.TextDocument.ToRazorTextDocumentIdentifier()
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.
Generally fine with this
/// <summary> | ||
/// Razor LSP server for Razor document requests (.razor and .cshtml files) | ||
/// </summary> | ||
RazorCohostServer, |
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 trying to understand the steps here
- Create this server to handle Razor files (instead of the current Razor server). Migrate Razor support to here.
- (maybe) Once the rest of Marco's work goes in, we can unify this Razor server with the RazorCSharp server, and have a single server that handles both
- Once dynamic registration goes in we merge this with the normal C# server
Does that sound right? Potentially we can also skip 2) entirely if requests about C# are converted to non-LSP entirely before 3?
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.
Item 2 hadn't really occurred to me, but I guess we could do that. Without dynamic registration though, we probably can't do that behind a feature flag though, as it would just be controlled by the content type attribute on the server. Interesting idea though.
The rest is correct though. Essentially this lets us work on migration, find bugs, maybe get some benefit, but without being blocked on dynamic registration.
src/Tools/ExternalAccess/Razor/Cohost/RazorCohostLanguageClient.cs
Outdated
Show resolved
Hide resolved
[Export(typeof(RazorLspServiceProvider)), Shared] | ||
[method: ImportingConstructor] | ||
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] | ||
internal sealed class RazorLspServiceProvider( |
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 of these should go away once we can merge this into the C# server (with dynamic registration) 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.
Yep! Thats why I tried to make them do as little as possible
@CyrusNajmabadi I heard a rumour you wanted to review this too? I imagine there will be follow ups regardless, as I start implementing the Razor side |
return base.ActivateAsync(cancellationToken); | ||
} | ||
|
||
return Task.FromResult<Connection?>(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.
Sadly, this doesn't work. The platform helpfully checks this for null and just immediately throws an exception 🤦♂️
So options are ClientName, or just return no capabilities. Thoughts @dibarbet ?
I'm guessing ClientName, since activating a server to nothing isn't great, but there is a slight downside with that, that we'd be unable to do progressive moves to this server, it would be all or nothing. I don't know how important that will be though.
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 I'm not super opposed to having a server with no capabilities. We'll just have to make sure that the server isn't trying to do anything (e.g. sending refresh notifications).
As you mention it would allow you to migrate features one at a time which I think is fairly valuable.
...es/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs
Outdated
Show resolved
Hide resolved
I've addressed all of the feedback, and am using this fine from the Razor repo. Just to confirm @dibarbet is this good to merge? Or do you want to hold off for a specific version etc.? |
Made a few more updates to this for issues found when implementing the first endpoint from Razor (in dotnet/razor#9674). Would be good if you can take a look and re-approve @dibarbet, then I'll merge so I can get some packages going. |
...es/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/AbstractLanguageServer.cs
Show resolved
Hide resolved
src/Features/LanguageServer/Protocol/WellKnownLspServerKinds.cs
Outdated
Show resolved
Hide resolved
context.UpdateTrackedDocument(request.TextDocument.Uri, text); | ||
|
||
// Razor can't handle this request because they don't have access to the RequestContext, but they might want to do something with it | ||
if (didChangeHandler 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.
Wondering here if we want a different mechanism to expose changes to Razor, if Razor needs to do special handling on didChange. For example similar to the IOnInitialized services that get called by the base InitializedHandler.
Especially when we unify with the Roslyn server- I'm not sure we want to allow separate didChange handlers per language.
Does the work Razor needs to do here require the queue to be blocked while its happening?
Same for open/close
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 good thoughts, and I see this as definitely a point-in-time thing.
- When we have dynamic registration, yes I would expect the "normal" text document sync handlers work for all languages and we'd remove these.
- When Razor can pull on the source generator for our .g.cs files then we won't need to be notified about changes or opens.
For now though, we need something here, as the Razor side that responds to this is effectively unchanged from our existing code, we kick off a compile of the razor file as it changes.
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.
Actually, even when we can use the source generator and even use the Roslyn API rather than LSP to ask questions about it etc, we'd still need some way to get notified of open and change of Razor files, and run code while the queue is blocked, in order to make sure the html virtual buffers are up to date. Maybe some way to hook into the LspWorkspaceManager? Haven't really thought about the specifics of that far ahead yet.
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.
Hmm. Probably depends on what needs to happen. I wouldn't be opposed to something like IOnInitialized where we expose a hook inside the handler. I think putting it there makes it more clear that its a blocking hook (than putting in LspWorkspaceManager0
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'm missing something. Isn't that exactly what this is currently doing?
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 I meant was putting code inside the main DidChangeHandler that allows for arbitrary additional callers, instead of duplicating teh handler for Razor and having a special Razor hook.
e.g. https://github.com/dotnet/roslyn/blob/main/src/Features/LanguageServer/Protocol/Handler/ServerLifetime/InitializedHandler.cs#L27
We'd have a generic hook that razor could implement. However I think you need to export a separate Razor didChange handler already - so I'm fine if we do that later when we merge the servers.
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 yep, can definitely follow up with that once we merge. The existing handlers won't activate in this server though, due to the export attribute being used.
@dibarbet any reason not to merge this? |
First part of #9519 Razor side of dotnet/roslyn#70819 This PR consumes the new Cohost server from Roslyn, and when enabled, moves DocumentColor requests from being handled by our existing Razor Language Server, to instead be handled by the new server. There is currently no sharing of project system data or anything like that yet. A few TODOs for follow ups, ~can't be merged, and indeed won't build, until a Roslyn package is available etc. but~ it's reviewable while we keep working, and more important its shippable in its "off" state. ~Ignore the last commit, obviously won't be merged :)~
Part of dotnet/razor#9519
This creates a basic LSP client/server for the "Razor" content type, inheriting from the existing Roslyn language server. Tried to keep the code as minimal as possible, as all of the endpoints will be in the Razor repo, but we need document sync in this repo, in order to access internals on RequestContext, and project context in this repo to match the data the RequestContext expects to receive. Some minor complexity in order to avoid exposing VS Protocol types in the API, to avoid dependency annoyances.
This also includes a very small portion of #70170, which is support for LSP requests against
TextDocument
s. Thank you @mgoertz-msft!I've tested locally with local build Roslyn and Razor, but not used it in anger to try to implement a "real" endpoint in the Razor repo yet. Wanted to put this up early-ish in case there is a lot of feedback.